国外虚拟主机对比评论-国外虚拟主机购买指

顶级最好的最新国外虚拟主机对比评论最便宜的国外虚拟主机购买指南最近更新于:9月12日

在国外购买主机已经有六年的历史了,此间虚拟主机个人空间服务发展很快,用过了很多虚拟主机空间,对比过不少外国虚拟主机服务商,在刚开始寻找虚拟主机网络空间的时候在中国使用国外虚拟主机的还很少,有关国外虚拟主机的介绍评论等资料几乎没有,只有通过在国外的虚拟主机网站一页一页寻找对比,希望能找到便宜的理想的的虚拟主机空间。

六年过去了,在虚拟主机购买上花了很多钱也积累了很多经验,对国外虚拟主机工业的不管是个人网页空间,虚拟主机空间,VPS主机(虚拟私人服务器),还是专用商业服务器,从价格、功能、商家支持服务等各方面都有了一个不少的了解,在这里略作总结,挑选了10几家国外最便宜的虚拟主机,和最高性能的虚拟主机商介绍给大家,希望对那些打算购买国外虚拟主机空间使用的朋友们能有所帮助,免除多花钱的烦恼。

这里介绍的是虚拟主机,和共享主机空间,VPS主机,即Virtual Private Server, 虚拟私人服务器类似。VPS 一般都可以放置多个域名,多个网站,对于一些经营多个虚拟主机网站的站长来说毫无疑问是很好的选择。VPS一般除了你购买的容量、流量大小有限制外,其他 的所有功能都是不限的,有的会限制一下总共可以放置网站的数量,比如10个,但多数虚拟主机都是不限的。是介于专用服务器和虚拟空间之间的一种虚拟主机类型,专用服务器所具有的功能他都有,而价格又没有那么高,很适合有精明头脑的用户选择。
一. 为什么要买外国的虚拟主机网站空间呢?

继续阅读“国外虚拟主机对比评论-国外虚拟主机购买指”

利用ajax判断web用户是否在线(离线)

今天看到一个ajax无刷新的php聊天室程序。里面的用户在线状态比较准时。

细想起来,其实实现起来也是非常简单的。如果依靠session来判断的话,可能不是那么准确。
于是以前见到有人说 iframe一个,定时刷新的小窗口
其实在ajax的聊天室也有XMLHttpRequest来周期性获取新消息,
完全可以利用这个周期性的请求,来判断用户是不是 关闭了浏览器。

这里只是说明可行性,如果实际上做,可能需要更多的设计,比如说如何尽可能的利用好那个周期性的刷新,是不是可以在这个上面做更加多的工作。而不仅仅是判断用户状态。

SQL存储过程在.NET数据库中的应用

一.前言:

存储过程(Stored Procedure)是一组为了完成特定功能的SQL语句集,经编译后存储在数据库中。用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数) 来执行它。存储过程是数据库中的一个重要对象,任何一个设计良好的数据库应用程序都应该用到存储过程。总的来说,存储过程具有以下一些优点:

◆存储过程允许标准组件式编程

◆存储过程能够实现较快的执行速度

◆存储过程能够减少网络流量

◆存储过程可被作为一种安全机制来充分利用

本文作者将向大家介绍.NET数据库应用程序中存储过程的应用,以及如何将它与ADO.NET中的SqlDataAdapter对象、DataSet对象等结合使用以提高.NET数据库应用程序的总体性能。二.系统要求:

开发工具:Visual Studio.NET

数据库管理系统:SQL Server 2000(其中包含了示例程序所用到的Pubs数据库)

三.创建一个简单的存储过程:

这 里我将向大家介绍如何运用Visual Studio.NET IDE来创建存储过程。运用Visual Studio.NET IDE创建存储过程是非常容易和直观的,你只要在服务器资源管理器中导向到Pubs数据库并展开节点,就会发现包括存储过程在内的各种数据库对象,如图1 所示。

在存储过程节点上点击右键便可弹出一个菜单,其中包含了“新建存储过程”的命令。新建一个存储过程后,IDE中的代码编辑窗口便出现如下所示的代码模板:

CREATE PROCEDURE dbo.StoredProcedure1
/*
(
@parameter1 datatype = default value,
@parameter2 datatype OUTPUT )
*/
AS
/* SET NOCOUNT ON */
RETURN

上面的代码模板符合简化的创建存储过程的语法规则,完整的语法规则如下:

CREATE PROC [ EDURE ] procedure_name [ ; number ]
[ { @parameter data_type }
[ VARYING ] [ = default ] [ OUTPUT ]
] [ ,…n ]
[ WITH
{ RECOMPILE | ENCRYPTION | RECOMPILE , ENCRYPTION } ]
[ FOR REPLICATION ]
AS sql_statement [ …n ]

限于篇幅,各个参数的含义在此就不多作介绍了,有兴趣的读者可以参考有关SQL Server 2000数据库管理系统的资料。

下 面我对该代码模板中的各个语法成分略作介绍。CREATE PROCEDURE声明创建一个存储过程,后面跟着该存储过程的名称。“/*……*/”中的成分是该存储过程的参数,可包括输入参数和输出参数。AS关键 字后面的内容是该存储过程的主体部分,其中是任何数量和类型的包含在存储过程中的SQL语句。RETURN关键字表明存储过程结束并能返回整型状态值给调 用者。下面我们就来创建一个简单的不带参数的存储过程并运用之:

CREATE PROCEDURE dbo.up_GetPublisherInfo
AS
SELECT pub_id, pub_name, city, state, country
FROM publishers
RETURN

创 建以上存储过程后,保存之。保存完毕,与该存储过程相对应的节点就会出现在服务器资源管理器中。同时请注意代码编辑窗口中的CREATE关键字变为 ALTER关键字了,该关键字是用于更改任何现有的存储过程的。要运行上述存储过程,只要点击其节点并在右键弹出菜单中选择“运行存储过程”,运行的结果 图示如下:

四.创建一个带参数的存储过程:

以上我们创建了一个简单的不带参数的存储过程,而在实际的应用中往往会用到很多带有参数的存储过程。带有参数的存储过程一般是用于更新数据或是插入数据的。下面我们可以运用同样的操作方法创建一个带参数的存储过程:

CREATE PROCEDURE dbo.up_UpdatePublisherInfo
(
@pub_id char (4),
@pub_name varchar (40),
@city varchar (20),
@state char (2),
@country varchar (30)
)
AS
UPDATE publishers
SET pub_name = @pub_name, city = @city, state = @state,
country = @country
WHERE ( pub_id = @pub_id )
RETURN

在 上面的创建存储过程的代码中,我们通过在名称前添加一个“@”标志来声明存储过程的局部变量-参数,同时还声明了各个参数的类型,确定了各个参数的方向 值,也即表明该参数是输入型的还是输出型的或者是输入输出型的或者是返回值型的。用户通过相应的存储过程名称以及正确有效的参数便可调用该存储过程了。还 有,你可以通过运用OUTPUT关键字在参数中添加输出型的参数,具体方法请参考上面的语法规则。输出型的参数能返回给调用者相关的信息。

上 面的存储过程能更新publishers表中相应出版商的信息。你可以通过点击该存储过程的节点,在右键弹出菜单中选择“运行存储过程”来执行它。一旦执 行,IDE中便弹出一个输入出版商信息的对话框(如图3所示)。在该对话框中填入正确有效的更新信息,注意pub_id的值在原来的表中必须存在,然后点 击“确定”按钮便可更新数据了。

五.创建简单存储过程的数据库应用程序:

下面我们就运用上述的不带参数的存储过程 来一个数据库应用程序,其中还用到了ADO.NET中的SqlDataAdapter对象以及DataSet对象。其中的SqlDataAdapter对 象作为SQL Server数据库和DataSet对象的桥梁将两者联系在一起。SqlDataAdapter对象包含了两个常用的方法:Fill()方法和 Update()方法。其中的Fill()方法能从数据库中获取相应数据并填充到DataSet对象中,而Update()方法顾名思义就是更新数据集的 意思了。在调用Fill()方法以前,我们必须设置好SqlDataAdapter对象的SelectCommand属性,该属性其实是一个 SqlCommand对象。SelectCommand属性中包含有效的SQL语句,并能据此从数据库中获取相应数据并填充到DataSet对象中。

首先,我们创建一个Windows Forms应用程序,编程语言为C#。在Visual Studio.NET中创建一个新的项目后,给该项目添加一个新的类-Publishers类,该类封装了连接到后台数据库并获取数据集对象的业务逻辑。步骤如下:

1.添加必要的命名空间引用:using System.Data.SqlClient;

2.给该类添加如下一些必要的变量:

private SqlConnection cnPubs;
private SqlCommand cmdPubs;
private SqlDataAdapter daPubs;
private DataSet dsPubs;

3.在该类的构造函数中完成连接后台数据库,获取SqlDataAdapter对象等业务逻辑:

public Publishers()
{
try
{
// 创建一个数据库连接对象
cnPubs = new SqlConnection( "server=localhost;integrated security=true;database=pubs" );
// 创建一个SqlCommand对象,并指明其命令类型为存储过程
cmdPubs = new SqlCommand();
cmdPubs.Connection = cnPubs;
cmdPubs.CommandType = CommandType.StoredProcedure;
cmdPubs.CommandText = "up_GetPublisherInfo";
// 创建一个SqlDataAdapter对象,设定其SelectCommand属性为上面的SqlCommand对象
daPubs = new SqlDataAdapter();
daPubs.SelectCommand = cmdPubs;
// 创建一个DataSet对象
dsPubs = new DataSet();
}
catch( Exception ) {}
}

4. 最后为该类提供一个GetPublisherInfo()方法,该方法用SqlDataAdapter对象填充DataSet对象并返回填充后的 DataSet对象,方法如下(值得注意的是:SqlDataAdapter对象会隐式地打开数据库连接并在获取数据后隐式地关闭连接,这就是说 DataSet对象是工作在非连接模式下的。而当你显式地打开数据库连接并获取数据后,SqlDataAdapter对象并不会将该连接关闭):

public DataSet GetPublisherInfo()
{
// 调用SqlDataAdapter对象的Fill()方法并返回数据集对象
daPubs.Fill( dsPubs );
return dsPubs;
}

完成Publishers类的设计后,我们给主窗体添加一个DataGrid控件并用它来显示DataSet对象中的数据。首先给主窗体类添加如下成员变量:

private Publishers pubs;
private DataSet ds;
之后,修改主窗体类的构造函数如下:
public Form1()
{
//
// Windows 窗体设计器支持所必需的
//
InitializeComponent();
//
// TODO: 在 InitializeComponent 调用后添加任何构造函数代码
// pubs = new Publishers();
ds = pubs.GetPublisherInfo();
dataGrid1.DataSource = ds.Tables[0];
}

这样该应用程序一启动主窗体的DataGrid控件中便显示了运用上述不带参数的存储过程从Pubs数据库中获取的相应数据,程序运行图示如下:

六.创建带参数的存储过程的数据库应用程序:

上 面我们创建了一个不带参数的存储过程的应用程序,下面我们就来创建一个更加复杂的数据库应用程序。在实际的数据库应用中,我们往往需要获取数据并更新、插 入或删除数据,这时我们就需要用到带有参数的存储过程了,同时在运用SqlDataAdapter对象时,我们会调用它的Update()方法。该 Update()方法会自动根据DataSet对象中的DataTable对象内各条记录的变化情况完成相应操作。SqlDataAdapter对象还包 含了UpdateCommand、InsertCommand、DeleteCommand等属性,这些属性其实都是SqlCommand对象。 Update()方法会根据操作的类型选用相应的属性。

在运用带有参数的存储过程建立数据库应用程序时,我们一般都要用到 SqlParameter类,该类封装了各种与Sql参数相关的属性和方法。其中的属性包括了ParameterName,SqlDBType, Direction,Size,Value,SourceColumn以及SourceVersion等。其中ParameterName, SqlDBType,Direction,Size等属性是用于匹配存储过程中定义的参数的。比如下面定义的SqlParameter对象就是用来匹配前 面定义的up_UpdatePublisherInfo存储过程中的“@pub_id”参数的。

SqlParameter updParam = new SqlParameter( "@pub_id", SqlDbType.Char, 4 );

在 上面的定义中,虽然Direction属性没有明确地给出,但是它的默认值为Input,所以也就满足了我们的需要。而如果一个SqlParameter 对象的Direction属性为InputOutput或Output或ReturnValue,那么其Direction属性就必须明确地说明了,比如 下面的代码就明确地声明了一个SqlParameter对象的Direction属性为Output。

oParam.Direction = ParameterDirection.Output;

其 中的SourceColumn属性是用于匹配一个DataTable对象中的DataColumn对象的,这种匹配能在调用Update()方法更新 DataTable对象时隐式地导入所需的SqlParameter对象。如果在定义时没有声明该属性,那么你必须在代码中显式地说明 SqlParameter对象的SourceColumn属性。

其中的SourceVersion属性的默认值是DataRow对象 相应字段中的当前值,也就是要更新到数据库中的值。当然,SourceVersion属性也可以指向DataRow对象相应字段中的原始值,也即从数据库 中获取的初始值。在数据库事务处理系统中,数据的同步性问题非常重要,下面我们来建立一个能检测数据同步性的存储过程。

CREATE PROCEDURE dbo.up_UpdatePublisherName
(
@pub_id char(4),
@pub_name varchar(40),
@Original_pub_name varchar(40)
)
AS
if exists(select pub_id
from publishers
where (pub_id = @pub_id) AND (pub_name = @Original_pub_name))
Begin
UPDATE publishers SET pub_name = @pub_name
WHERE (pub_id = @pub_id)
End
RETURN

接着,我们在上面的应用程序中调用该存储过程以更新发行商的名称。首先,在原有应用程序的基础上完善其业务逻辑类-Publishers类:

1.添加一个新的SqlCommand对象,该对象能作为SqlDataAdapter对象的 UpdateCommand属性被使用:
private SqlCommand cmdUpdPubs;
2.更新该类的构造函数Publishers()函数,添加以下内容:
// 创建另一个SqlCommand对象,该对象引用更新发行商名称的存储过程
cmdUpdPubs = new SqlCommand();
cmdUpdPubs.Connection = cnPubs;
cmdUpdPubs.CommandType = CommandType.StoredProcedure;
cmdUpdPubs.CommandText = "up_UpdatePublisherName";
// 为上面的SqlCommand对象添加必要的参数
cmdUpdPubs.Parameters.Add( "@pub_id", SqlDbType.Char, 4, "pub_id" );
cmdUpdPubs.Parameters.Add( "@pub_name", SqlDbType.VarChar, 40, "pub_name" );
SqlParameter updParam = new SqlParameter
( "@Original_pub_name", SqlDbType.VarChar, 40, "pub_name" );
updParam.SourceVersion = DataRowVersion.Original;
cmdUpdPubs.Parameters.Add( updParam );
3.指定SqlDataAdapter对象的UpdateCommand属性为上面定义的SqlCommand对象:
daPubs.UpdateCommand = cmdUpdPubs;
4.添加方法UpdatePublisherName():
public void UpdatePublisherName( DataSet dsChanges )
{
// 更新所有改动
daPubs.Update( dsChanges );
}

应用程序的业务逻辑类完善之后,在主窗体上添加一个名为“更新数据集”的按钮,并添加该按钮的事件响应函数如下:

private void button1_Click(object sender, System.EventArgs e) { if( ds.HasChanges() ) { pubs.UpdatePublisherName( ds.GetChanges() ); ds.Clear(); ds = pubs.GetPublisherInfo(); } }

到此为止,应用程序的业务逻辑类和主窗体类都已经更新完毕,现在的应用程序能根据用户的改用更新数据库中的相关内容了。运行程序如图5所示,你在更新了数据后还可以打开SQL Server中的相应表并验证数据有没有更新成功。

七.总结:

本 文向大家介绍了存储过程的基本知识以及在.NET数据库应用程序中如何结合SqlDataAdapter对象、DataSet对象等构建数据驱动的应用程 序。在本文中,我们运用到了两类存储过程:一类为简单的不带参数的存储过程,其运用方法相对容易;另一类为带有参数的存储过程,在调用该类存储过程时还得 运用到SqlParameter对象。同时,我们不难发现将数据更新业务逻辑封装在存储过程中是一种很好的设计方法,它能提高应用程序的可管理性、可扩展 性以及数据库的安全性。类似的,插入数据以及删除数据的业务逻辑也可以封装在存储过程中并以相似的方法在应用程序中被运用。最后,希望本文对大家有不少帮 助。

浅析C#(.net)中的套接字socket编程

   C#是微软随着VS.net新推出的一门语言。它作为一门新兴的语言,有着C++的强健,又有着VB等的 RAD特性。而且,微软推出C#主要的目的是为了对抗Sun公司的Java。大家都知道Java语言的强大功能,尤其在网络编程方面。于是,C#在网络编 程方面也自然不甘落后于人。本文就向大家介绍一下C#下实现套接字(Sockets)编程的一些基本知识,以期能使大家对此有个大致了解。首先,我向大家 介绍一下套接字的概念。

套接字基本概念:

套接字是通信的基石,是支持TCP/IP协议的网络通信的基本操作单元。 可以将套接字看作不同主机间的进程进行双向通信的端点,它构成了单个主机内及整个网络间的编程界面。套接字存在于通信域中,通信域是为了处理一般的线程通 过套接字通信而引进的一种抽象概念。套接字通常和同一个域中的套接字交换数据(数据交换也可能穿越域的界限,但这时一定要执行某种解释程序)。各种进程使 用这个相同的域互相之间用Internet协议簇来进行通信。

套接字可以根据通信性质分类,这种性质对于用户是可见的。应用程序一般仅在同一类的套接字间进行通信。不过只要底层的通信协议允许,不同类型的套接字间也照样可以通信。套接字有两种不同的类型:流套接字和数据报套接字。
套接字工作原理:

要通过互联网进行通信,你至少需要一对套接字,其中一个运行于客户机端,我们称之为ClientSocket,另一个运行于服务器端,我们称之为ServerSocket。

根据连接启动的方式以及本地套接字要连接的目标,套接字之间的连接过程可以分为三个步骤:服务器监听,客户端请求,连接确认。

所谓服务器监听,是服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。

所谓客户端请求,是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。

所 谓连接确认,是指当服务器端套接字监听到或者说接收到客户端套接字的连接请求,它就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发 给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

C#中的套接字编程实例:

通 过向大家简单的介绍套接字的基本概念和实现套接字编程的基本原理,我想大家对套接字编程已有了初步的了解。不过,上面介绍的仅仅是基本概念和原理,要真正 运用还是需要一定的工作的。对基本概念和原理的真正理解的最好方法莫过于自己动手做一个实例,下面我就向大家介绍一个很好的用C#实现套接字编程的实例 ――聊天室程序。

本程序是基于C/S(服务器/客户端)构架的,程序包含一个服务器端的应用程序和一个客户端的应用程序。首先,在服 务器上运行服务器端的应用程序,该程序一运行就开始服务器监听。然后,在客户机上就可以打开客户端的应用程序。程序打开后可以与服务器端应用程序进行连 接,即进行客户端请求。在连接确认后,客户端用户可以和其他的客户端用户进行聊天。客户端人数没有限制,同时还支持“悄悄话”聊天模式,支持聊天记录。所 以这是一个学习套接字编程的相当不错的例子。而且,程序中为了处理每个客户端的信息还用到了多线程机制。在每个客户端与服务器端连接成功后,它们之间就建 立一个线程。这样运用了多线程之后,客户端之间就不会相互影响,即使其中一个出了错误也不会影响到另一个。

下面,我就向大家具体介绍该实例:

服务器端程序:

1. 打开VS.net,新建一个C#的模板为“Windows 应用程序”的项目,不妨命名为“ChatServer”。

2. 布置界面。只需在界面上添加一个ListBox控件即可,该控件主要用于显示客户端的用户的一些信息的。图象如下:

3. 服务器端程序的代码编写。

对于服务器端,主要的作用是监听客户端的连接请求并确认其请求。程序一开始便打开一个StartListening()线程。

private void StartListening() 



  listener 
= new TcpListener(listenport); 

  listener.Start(); 

  
while (true

  


try 



  Socket s 
= listener.AcceptSocket(); 

  clientsocket 
= s; 

  clientservice 
= new Thread(new ThreadStart(ServiceClient)); 

  clientservice.Start(); 

}
 

catch(Exception e) 



  Console.WriteLine(e.ToString() ); 

}
 

  }
 

}
 

该 线程是一直处于运行状态的。当服务器端接收到一个来自客户端的连接请求后,它就打开一个ServiceClient()线程来服务客户端。当一个连接被建 立后,每个客户端就被赋予一个属于它自己的套接字。同时,一个Client类的对象被建立。该对象包含了客户端的一些相关信息,该信息被保存在一个数组列 表中。 Client类如下:

using System; 

using System.Threading; 

namespace ChatServer 



using System.Net.Sockets; 

using System.Net; 

/// 

/// Client 的摘要说明。 

/// 


public class Client 



private Thread clthread; 

private EndPoint endpoint; 

private string name; 

private Socket sock; 

public Client(string _name, EndPoint _endpoint, Thread _thread, Socket _sock) 



// TODO: 在此处添加构造函数逻辑 

clthread 
= _thread; 

endpoint 
= _endpoint; 

name 
= _name; 

sock 
= _sock; 

}
 

public override string ToString() 



return endpoint.ToString()+ " : " + name; 

}
 

public Thread CLThread 



get{return clthread;} 

set{clthread = value;} 

}
 

public EndPoint Host 



get{return endpoint;} 

set{endpoint = value;} 

}
 

public string Name 



get{return name;} 

set{name = value;} 

}
 

public Socket Sock 



get{return sock;} 

set{sock = value;} 

}
 

}
 

}
 

程 序的主体部分应是ServiceClient()函数。该函数是一个独立的线程,其主要部分是一个while循环。在循环体内,程序处理各种客户端命令。 服务器端接收来自客户端的以ASCII码给出的字符串,其中包含了一个“|”形式的分隔符。字符串中“|”以前的部分就是具体的命令,包括CONN、 CHAT、PRIV、GONE四种类型。CONN命令建立一个新的客户端连接,将现有的用户列表发送给新用户并告知其他用户有一个新用户加入。CHAT命 令将新的信息发送给所有用户。PRIV命令将悄悄话发送给某个用户。GONE命令从用户列表中除去一个已离开的用户并告知其他的用户某某已经离开了。同 时,GONE命令可以设置布尔型的变量keepalive为false从而结束与客户端连接的线程。ServiceClient()函数如下:

 

private void ServiceClient() 

{ Socket client = clientsocket; 

bool keepalive = true

while (keepalive) 



Byte[] buffer 
= new Byte[1024]; 

client.Receive(buffer); 

string clientcommand = System.Text.Encoding.ASCII.GetString(buffer); 

string[] tokens = clientcommand.Split(new Char[]{|}); 

Console.WriteLine(clientcommand); 

if (tokens[0== "CONN"



for(int n=0; n 



Client cl 
= (Client)clients[n]; 

SendToClient(cl, 
"JOIN|" + tokens[1]); 

}
 

EndPoint ep 
= client.RemoteEndPoint; 

Client c 
= new Client(tokens[1], ep, clientservice, client); 

clients.Add(c); 

string message = "LIST|" + GetChatterList() +" "

SendToClient(c, message); 

lbClients.Items.Add(c); 

}
 

if (tokens[0== "CHAT"



for(int n=0; n 



Client cl 
= (Client)clients[n]; 

SendToClient(cl, clientcommand); 

}
 

}
 

if (tokens[0== "PRIV"



string destclient = tokens[3]; 

for(int n=0; n 



Client cl 
= (Client)clients[n]; 

if(cl.Name.CompareTo(tokens[3]) == 0

SendToClient(cl, clientcommand); 

if(cl.Name.CompareTo(tokens[1]) == 0

SendToClient(cl, clientcommand); 

}
 

}
 

if (tokens[0== "GONE"



int remove = 0

bool found = false

int c = clients.Count; 

for(int n=0; n 



Client cl 
= (Client)clients[n]; 

SendToClient(cl, clientcommand); 

if(cl.Name.CompareTo(tokens[1]) == 0



remove 
= n; 

found 
= true

lbClients.Items.Remove(cl); 

}
 

}
 

if(found) 

clients.RemoveAt(remove); 

client.Close(); 

keepalive 
= false

}
 

}
 

}
 

 

这样,服务器端程序就基本完成了。(其他略次要的代码可以参见源代码中的Form1.cs文件)程序运行图示如下:

 

客户端程序:

1. 打开VS.net,新建一个C#的模板为“Windows 应用程序”的项目,不妨命名为“ChatClient”。 

2. 布置界面。往界面上添加一个ListBox控件(用于显示用户列表),一个RichTextBox控件(用于显示聊天消息以及系统消息),一个 TextBox控件(用于发送消息),一个CheckBox控件(确定是否为悄悄话),一个StatusBar控件以及四个Button控件(分别为“连 接”、“断开连接”、“开始记录”、“发送”)。各个控件的属性设置可以参见源代码中的具体设置,这里从略。界面设计好后的图象如下:

3. 客户端程序的代码编写。

当 客户端试图和服务器端进行连接时,一个连接必须建立而且得向服务器端进行注册。 EstablishConnection()函数运用一个TcpClient来和服务器端取得连接,同时创建一个NetworkStream来发送消息。 还有,端口号和服务器端的是保持一致的,均为5555。EstablishConnection()函数如下:

 

private void EstablishConnection() 



statusBar1.Text 
= "正在连接到服务器"

try 



clientsocket 
= new TcpClient(serveraddress,serverport); 

ns 
= clientsocket.GetStream(); 

sr 
= new StreamReader(ns); 

connected 
= true

}
 

catch (Exception) 



MessageBox.Show(
"不能连接到服务器!","错误"

MessageBoxButtons.OK, MessageBoxIcon.Exclamation); 

statusBar1.Text 
= "已断开连接"

}
 }
 

 

在和服务器端连接成功后,程序就用RegisterWithServer()函数向服务器端发送一个CONN命令。该命令先是发送该用户的名称,然后从服务器端获得其他所有用户的列表,所有用户列表是在ListBox控件中显示的。该函数如下:

 

private void RegisterWithServer() 



try 



string command = "CONN|" + ChatOut.Text; 

Byte[] outbytes 
= System.Text.Encoding.ASCII.GetBytes(command.ToCharArray()); 

ns.Write(outbytes,
0,outbytes.Length); 

string serverresponse = sr.ReadLine(); 

serverresponse.Trim(); 

string[] tokens = serverresponse.Split(new Char[]{|}); 

if(tokens[0== "LIST"



statusBar1.Text 
= "已连接"

btnDisconnect.Enabled 
= true

}
 

for(int n=1; n 

lbChatters.Items.Add(tokens[n].Trim(
new char[]{ , })); 

this.Text = clientname + ":已连接到服务器"

}
 

catch (Exception) 



MessageBox.Show(
"注册时发生错误!","错误"

MessageBoxButtons.OK, MessageBoxIcon.Exclamation); 

}
 

}
 

 

在 此之后,当然就是用户之间的聊天了,由ReceiveChat()函数来完成。该函数是一个独立的线程,它处理所有用户获得的消息和用户发送的消息。它主 要处理了CHAT、PRIV、JOIN、GONE、QUIT等命令,处理的方法和服务器端的类似。具体函数实现如下:

 

private void ReceiveChat() 



bool keepalive = true

while (keepalive) 



try 



Byte[] buffer 
= new Byte[2048]; 

ns.Read(buffer,
0,buffer.Length); 

string chatter = System.Text.Encoding.ASCII.GetString(buffer); 

string[] tokens = chatter.Split(new Char[]{|}); 

if (tokens[0== "CHAT"



rtbChatIn.AppendText(tokens[
1]); 

if(logging) 

logwriter.WriteLine(tokens[
1]); 

}
 

if (tokens[0== "PRIV"



rtbChatIn.AppendText(
"Private from "); 

rtbChatIn.AppendText(tokens[
1].Trim() ); 

rtbChatIn.AppendText(tokens[
2+ " "); 

if(logging) 



logwriter.Write(
"Private from "); 

logwriter.Write(tokens[
1].Trim() ); 

logwriter.WriteLine(tokens[
2+ " "); 

}
 

}
 

if (tokens[0== "JOIN"



rtbChatIn.AppendText(tokens[
1].Trim() ); 

rtbChatIn.AppendText(
" has joined the Chat "); 

if(logging) 



logwriter.WriteLine(tokens[
1]+" has joined the Chat"); 

}
 

string newguy = tokens[1].Trim(new char[]{ , }); 

lbChatters.Items.Add(newguy); 

}
 

if (tokens[0== "GONE"



rtbChatIn.AppendText(tokens[
1].Trim() ); 

rtbChatIn.AppendText(
" has left the Chat "); 

if(logging) 



logwriter.WriteLine(tokens[
1]+" has left the Chat"); 

}
 

lbChatters.Items.Remove(tokens[
1].Trim(new char[]{ , })); 

}
 

if (tokens[0== "QUIT"



ns.Close(); 

clientsocket.Close(); 

keepalive 
= false

statusBar1.Text 
= "服务器端已停止"

connected
= false

btnSend.Enabled 
= false

btnDisconnect.Enabled 
= false

}
 

}
 

catch(Exception){} 

}
 }
 

 

通过以上的一些函数,客户端程序之间就可以进行自由地聊天了,各个用户之间还可以互相发送悄悄话。所以程序已经实现了聊天室的基本功能了,不过最后各个用户还要正常地退出,那就要用到QuitChat()函数了。该函数的具体实现如下:

 

private void QuitChat() 



if(connected) 



try 



string command = "GONE|" + clientname; 

Byte[] outbytes 
= System.Text.Encoding.ASCII.GetBytes(command.ToCharArray()); 

ns.Write(outbytes,
0,outbytes.Length); 

clientsocket.Close(); 

}
 

catch(Exception) 



}
 

}
 

if(logging) 

logwriter.Close(); 

if(receive != null && receive.IsAlive) 

receive.Abort(); 

this.Text = "客户端"

}
 

 

到此为止,客户端程序的主要部分都已经介绍完毕。还有一些按钮控件的消息处理函数可以参见源代码。同时,程序中还有一个聊天记录功能,该功能和现在流行的聊天软件的记录功能类似。不过限于篇幅,在这里就不一一介绍了,有兴趣的读者可以研究一下本文后面的源代码。

这样,客户端程序就完成了。程序运行图示如下: