分享

以异步的方式操作TCP/IP套接字

 ThinkTank_引擎 2014-12-01

普通的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个客户端连接,仍然能够正常工作


 


 


.NET基础篇


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







    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多