[转载]以异步的方式操作TCP/IP套接字—以异步方式实现简单的聊天室

[转载]以异步的方式操作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个客户端连接,仍然能够正常工作。

赞(0) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏