好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

Socket异步通信

Socket异步通信

异步这个词以前在课堂上也听过,那时候只是听,直到在做项目的时候用到多线程,在体会到异步是怎样的,到最近做的东西对异步更加深刻了,进程通信时调Windows API SendMessage和PostMessage的区别。最近搞的Socket编程也是有异步的,Socket当然要有异步才行,不然服务端Accept一次就卡一次在那里,客户端Connect一次就卡一次。每Send一次,Receive一次都会卡一次,这样不好。

  在网上谷歌过一下,发现Socket的异步可以有两种方式,一种是用 SocketAsyncEventArgs 配合AcceptAsync,SendAsync,ReceiveAsync,ConnectAsync等方法实现的。通信要用到的二进制数组byte[]就通过SocketAsyncEventArgs实例的的SetBuffer方法设置,在网上看见有人评价SocketAsyncEventArgs很占用资源,在一个通讯过程中,要用到多个SocketAsyncEventArgs的实例, 当初以为SocketAsyncEventArgs比BeginXXX浪费资源 ,所以我就用另一种方式了。用BeginXXX / EndXXX 方法的 。 后来有位员友指点,BeginXXX反而是浪费资源的每次必须创建一个IAsyncResult ,而SocketAsyncEventArgs是可以提供重复使用的,不需要每次创建。想了想的确是这样。

  BeginXXX / EndXXX 的方式用到的是AsyncCallback委托异步调用一些自己定义的方法。此外BeginXXX / EndXXX 方法的一个重载可以传入一个Object类型的参数,这样可以把一些需要用到的对象传进去,在方法内部,通过IAsyncResult类型的参数的 AsyncState 属性把那个Object类型的参数取出来。

  通过这次的学习还知道了数据包过大和粘包要处理,之前同步的时候也会有这个情况的,只是当时不知道没考虑到这个情况。

  正如上面所说的,通信过程会遇到数据包过大,因此异步接收会只是一次就能接收完,需要有地方存储那些已接收的数据,下面先定义一个数据结构

 1       class   ConnectInfo
  2       {
  3           public   byte  [] buffers;
  4           public   Socket clientSocket;
  5           public   Socket serverSocket;
  6           public   ArrayList tmpAl;
  7      }

  这个数据结构中buffers就用来存放最终接收完的完整数据,tempAl是暂时存放已经接收的数据,其余两个Socket的不用说了。

  另外定义两个变量

 1           static   int  BUFFER_SIZE =  1024  ;
  2           private   static  SocketError _socketError;

  上面的BUFFER_SIZE是一次接收的数据量,另一个SocketError的是在异步通信时用到的。

  Main方法里的代码,跟往常的差别不大,在Accept方面就调用BeginAccept方法。传入的State参数是serverSocket,服务端的Socket。在客户端Connet之后还需要用这个Socket实例。

 1              IPEndPoint ipep =  new  IPEndPoint(IPAddress.Any,  8081 ); //  本机预使用的IP和端口 
 2              Socket serverSocket =  new   Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  3              serverSocket.Bind(ipep); //  绑定 
 4              serverSocket.Listen( 10 ); //  监听 
 5              Console.WriteLine( "  waiting for a client  "  );
  6  
 7               serverSocket.BeginAccept(AsyncAccept, serverSocket);
  8              Console.ReadLine();

    服务端Accept到客户端的Connect之后,就会调用到下面这个方法,也就是上面BeginAccept方法的第一个参数的那个方法名的方法

  1           static   void   AsyncAccept(IAsyncResult e)
   2           {
   3              Socket serverSocket = e.AsyncState  as   Socket;
   4               if  (serverSocket ==  null )  return  ;
   5              Socket clientSocket = serverSocket.EndAccept(e); //  结束这次异步Accept 
  6  
  7              ConnectInfo info =  new   ConnectInfo();
   8              info.buffers =  new   byte  [BUFFER_SIZE];
   9              info.tmpAl =  new   ArrayList(BUFFER_SIZE);
  10              info.clientSocket =  clientSocket;
  11              info.serverSocket =  serverSocket;
  12              
 13               //  异步发送消息 
 14              clientSocket.BeginSend(Encoding.ASCII.GetBytes( "  welocome  " ), 0 , 8 , SocketFlags.None,  new   AsyncCallback(AsyncSend), info);
  15               //  异步接收消息 
 16              clientSocket.BeginReceive(info.buffers,  0 , info.buffers.Length,  0 ,  out   _socketError, AsyncReceive, info);
  17          }

e.AsyncState as Socket就是样取回BeginAccept传进来的serverSocket。调用这类BeginXXX的方法,都会在与AsyncCallBack委托绑定的方法里调用一次EndXXX方法。这里就可以开始设置通信了,都是异步的,所以都调用BeginXXX的方法,但接收的方法BeginReceive还需要传入一个byte[]的buffer放接收到的数据,在接收到消息之后还需要对这些数据作处理,又要用到clientSocket,而State参数只有一个,所以这里才需要定义一个数据结构把这些对象集合起来传到相应的回调方法里头。

  接下来是异步发送和异步接收的回调方法。

  1           static   void   AsyncReceive(IAsyncResult e)
   2           {  
   3              ConnectInfo info = e.AsyncState  as   ConnectInfo;
   4               if  (info ==  null )  return  ;
   5              Socket clientSocket =  info.clientSocket;
   6              //  暂存本次接受时接收到的数据量 
  7              int  bytesRead =  0  ;
   8              try 
  9              {
  10               //  终止本次异步接收 
 11                 bytesRead = clientSocket.EndReceive(e, out   _socketError);
  12                   if  (_socketError ==  SocketError.Success)
  13                  {
  14                      if  (bytesRead >  0  )
  15                      {
  16                       //  因未知本次接收的数据是客户端发来数据的
  17                      //  那一部分,因此暂时建一个byte[]把它提取出来,
  18                       //  存放到ArrayList里面去,等到所有数据都接收完时统一取出 
 19                          byte [] tmpbytes =  new   byte  [bytesRead];
  20                          Array.Copy(info.buffers,  0 , tmpbytes,  0  , bytesRead);
  21                           info.tmpAl.AddRange(tmpbytes);
  22  
 23  
 24                       //  查看还有没有数据没读完,有则用建一个新的buffer,再次进行异步读取 
 25                          if  (clientSocket.Available >  0  )
  26                           {
  27                               //  已知还有多少数据还没读取,就按数据量的大小
  28                               //  新建buffer,这样子就可以减少循环读取的次数了 
 29                              info.buffers =  new   byte  [clientSocket.Available];
  30                              clientSocket.BeginReceive(info.buffers,  0 , clientSocket.Available,  0 ,  out  _socketError,  new   AsyncCallback(AsyncReceive), info);                   
  31                           }
  32                           //  数据已经读取完了,接下来就按积累下来的所有数据
  33                           //  量新建一个byte[],把所有的数据一次取出来,同时把暂
  34                           //  存数据的ArrayList清空,以备下次使用 
 35                           else 
 36                           {
  37                               byte [] endreceive =  new   byte  [info.tmpAl.Count];
  38                               info.tmpAl.CopyTo(endreceive);
  39                               int  recLength= info.tmpAl.Count;
  40                               info.tmpAl.Clear();
  41                               string  content =  Encoding.ASCII.GetString(endreceive);
  42                              Array.Clear(endreceive,  0  , endreceive.Length);
  43  
 44                               //  把数据稍作处理,回传一些消息给客户端。 
 45                               string  senddata =  string .Format( "  rec={0}\r\nreceivedata={1}  "  ,recLength , content);
  46  
 47                               if  (! string  .IsNullOrEmpty(senddata))
  48                               {
  49                                   if  (senddata.Length > BUFFER_SIZE) senddata = senddata.Substring( 0  , BUFFER_SIZE);
  50                                   //  Send(handler, senddata); 
 51                                   byte [] data =  Encoding.ASCII.GetBytes(senddata);
  52                                  info.clientSocket.BeginSend(data,  0 , data.Length,  0 ,  out   _socketError,AsyncSend ,info);
  53                               }
  54                               //  再次调用异步接收以接收下一条客户端
  55                               //  发来的消息,继续跟客户端通信过程 
 56                              Array.Clear(info.buffers,  0  , info.buffers.Length);
  57                              clientSocket.BeginReceive(info.buffers,  0 , BUFFER_SIZE,  0 ,  out  _socketError,  new   AsyncCallback(AsyncReceive), info);
  58                           }
  59                       }
  60                   }
  61               }
  62               catch  (Exception ex)
  63               {
  64                  clientSocket.BeginReceive(info.buffers,  0 , BUFFER_SIZE,  0 ,  out  _socketError,  new   AsyncCallback(AsyncReceive), info);
  65               }
  66  
 67  
 68          }

下面这个则是发送消息的回调方法

 1           static   void   AsyncSend(IAsyncResult e)
  2           {
  3              ConnectInfo info = e.AsyncState  as   ConnectInfo;
  4               if  (info ==  null )  return  ;
  5              Socket clientSocket =  info.clientSocket;
  6              clientSocket.EndSend(e,  out   _socketError);
  7          }

  发送倒是比接收的简单,因为不用判断数据是否读取完毕,少了粘包的情况。

  客户端的代码就不放了,大致上也跟服务端的差不多,客户端用同步或异步也没啥问题,经过了这次的学习,发现无论同步和异步,都得对粘包的情况进行处理。

  还是那句话,本人的对Socket编程理解和了解尚浅,上面有什么说错的请各位指出,有什么说漏的,请各位提点,多多指导。谢谢!

 

 

分类:  C# ,  Socket编程

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于Socket异步通信的详细内容...

  阅读:33次