[转载]以异步的方式操作TCP/IP套接字——以异步方式实现简单的聊天室 – 风尘浪子 – 博客园.
普通的TCP/IP开发方式大家都应该非常熟练,但在系统开发的时候往往会遇到问题。
比如:在开发一个简单的聊天室的时候,一般情况下,Windows应用程序会处于同步方式运行,当监听的客户端越多,服务器的负荷将会越重,信息发送与接收都会受到影响。这时候,我们就应该尝试使用异步的TCP/IP通讯来缓解服务器的压力。
下面以一个最简单的聊天室服务器端的例子来说明异步TCP/IP的威力,先开发一个ChatClient类作为客户管理的代理类,每当服务器接收到信息时,就会把信息处理并发送给每一个在线客户。
void Main()
{
IPAddress ipAddress = IPAddress.Parse(“127.0.0.1”); //默认地址
TcpListener tcpListener = new TcpListener(ipAddress,500);
tcpListener.Start();
while (isListen) //以一个死循环来实现监听
{
ChatClient chatClient = new ChatClient(tcpListener.AcceptTcpClient()); //调用一个ChatClient对象来实现监听
}
tcpListener.Stop();
}
ChatClient中存在着一个Hashtabel类的静态变量 clients,此变量用来存贮在线的客户端信息,每当对一个客户端进行监听时,系统就生成一个ChatClient对象,然后在变量clients中加 入此客户端的信息。在接收客户端信息时,信息会调用Receive(IAsyncResult async)方法,把接收到的信息发送给每一个在线客户。
值得注意的是,每当接收到客户信息时,系统都会利用Stream.BeginRead()的方法去接收信息,然后把信息发送到每一个在线客户,这样做就可以利用异步的方式把信息进行接收,从而令主线程及早得到释放,提高系统的性能。
public class ChatClient
{
private TcpClient _tcpClient;
private byte[] byteMessage;
private string _clientEndPoint;
public volatile string message;
public static Hashtable clients= new Hashtable(); //以此静态变量存处多个客户端地址
public ChatClient(TcpClient tcpClient)
{
_tcpClient = tcpClient;
_clientEndPoint = _tcpClient.Client.RemoteEndPoint.ToString();
Console.WrtieLine(“连接成功,客户端EndPoint为”+_clientEndPoint);
ChatClient.clients.Add(_clientEndPoint, this); //每创建一个对象,就会将客户端的ChatClient对象存入clients;
byteMessage=new byte[_tcpClient.ReceiveBufferSize];
lock (_tcpClient.GetStream()) //接收信息,使用lock避免数据冲突
{
_tcpClient.GetStream().BeginRead(byteMessage, 0, _tcpClient.ReceiveBufferSize, new AsyncCallback(Receive), null);
//就在此处使用异步的IO线程进行数据读取,这样每个一客户端的都处于一个IO线程中处理,使主线程及早得到释放
//这样做就缓解了服务器端压力。
}
}
public void Receive(IAsyncResult iAsyncResult)
{
try
{
int length;
lock (_tcpClient.GetStream()) //信息接收,使用lock避免数据冲突
{
length= _tcpClient.GetStream().EndRead(iAsyncResult);
}
if (length < 1)
{
MessageBox.Show(_tcpClient.Client.RemoteEndPoint + “已经断线”);
clients.Remove(_tcpClient);
return ;
}
message=Encoding.Unicode.GetString(byteMessage,0,length);
SendToEveryone(message);
//在此时我们可以在此处调用SendToEveryone方法,利用clients变量以Stream.Write方法为每个客户端发送信息。
lock (_tcpClient.GetStream()) //再次监听,使用lock避免数据冲突
{
_tcpClient.GetStream().BeginRead(byteMessage, 0, _tcpClient.ReceiveBufferSize, new AsyncCallback(Receive), null);
//再次调用Stream.BeginRead方法,以监听以下次客户的信息
}
}
catch (Exception ex)
{
clients.Remove(_tcpClient);
_tcpClient.GetStream().Close();
_tcpClient.Close();
}
}
//通过Send方法把信息转换成二进制数据,然后发送到客户端
public void Send(string message)
{
try
{
NetworkStream ns;
lock (_tcpClient.GetStream())
{
ns = _tcpClient.GetStream();
}
byte[] byteMessage = Encoding.ASCII.GetBytes(message);
ns.Write(byteMessage, 0, byteMessage.Length);
ns.Flush();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
//由于客户端信息记录在HashTabel变量clients中,当信息接收后,就会通过此变量把信息发送给每一个在线客户。
public void SendToEveryone(string message)
{
foreach (DictionaryEntry client in clients)
{
ChatClient chatClient = (ChatClient)client.Value;
chatClient.Send(message);
}
}
}
测试结果:
至于窗口的设计和客户端的设计在这里就省略不说,这里的目的只是要你了解服务器端多线程TCP/IP信息接收的原理。
这个例子里,ChatClient类使用异步的IO线程进行数据读取,这样每个一客户端的都处于一个IO线程中处理,使主线程及早得到释放,这样做就缓解了服务器端压力。
这时候你可以做一个测试,此聊天室在默认情况下可接受大约3000个客户端连接,仍然能够正常工作。