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

 

 对 JAVA 开发有兴趣的朋友欢迎加入QQ群:174850571 共同探讨!
对 .NET  开发有兴趣的朋友欢迎加入QQ群:162338858 共同探讨 !

 

.NET基础篇

以异步的方式操作TCP/IP套接字——以异步方式实现简单的聊天室
合理使用“.NET扩展方法”来简化代码(例子:空值判断,利用扩展方法实现LINQ操作符ForEach)
分部类和分部方法
反射的奥妙
利用泛型与反射更新实体(ADO.NET Entity Framework)

 

作者:风尘浪子
http://www.cnblogs.com/leslies2/archive/2011/01/27/1935860.html

原创作品,转载时请注明作者及出处






posted on 2011-01-27 10:52  风尘浪子  阅读(6989)  评论(14编辑  收藏  举报

导航