Socket异步通信——使用IAsyncResult
异步这个词以前在课堂上也听过,那时候只是听,直到在做项目的时候用到多线程,在体会到异步是怎样的,到最近做的东西对异步更加深刻了,进程通信时调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编程理解和了解尚浅,上面有什么说错的请各位指出,有什么说漏的,请各位提点,多多指导。谢谢!
最后附上源码
1 class ConnectInfo 2 { 3 public byte[] buffers; 4 public Socket clientSocket; 5 public Socket serverSocket; 6 7 public ArrayList tmpAl; 8 } 9 10 class Program 11 { 12 static int BUFFER_SIZE = 1024; 13 14 private static SocketError _socketError; 15 16 /// <summary> 17 /// Server 18 /// </summary> 19 /// <param name="args"></param> 20 static void Main(string[] args) 21 { 22 IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 8081);//本机预使用的IP和端口 23 Socket newsock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 24 newsock.Bind(ipep);//绑定 25 newsock.Listen(10);//监听 26 Console.WriteLine("waiting for a client"); 27 28 newsock.BeginAccept(AsyncAccept, newsock); 29 Console.ReadLine(); 30 } 31 32 static void AsyncAccept(IAsyncResult e) 33 { 34 Socket serverSocket = e.AsyncState as Socket; 35 if (serverSocket == null) return; 36 Socket clientSocket = serverSocket.EndAccept(e); 37 38 ConnectInfo info = new ConnectInfo(); 39 info.buffers = new byte[BUFFER_SIZE]; 40 info.tmpAl = new ArrayList(BUFFER_SIZE); 41 info.clientSocket = clientSocket; 42 info.serverSocket = serverSocket; 43 clientSocket.BeginSend(Encoding.ASCII.GetBytes("welocome"),0,8, SocketFlags.None, new AsyncCallback(AsyncSend), info); 44 clientSocket.BeginReceive(info.buffers, 0, info.buffers.Length, 0, out _socketError, AsyncReceive, info); 45 } 46 47 static void AsyncSend(IAsyncResult e) 48 { 49 ConnectInfo info = e.AsyncState as ConnectInfo; 50 if (info == null) return; 51 Socket clientSocket = info.clientSocket; 52 clientSocket.EndSend(e, out _socketError); 53 } 54 55 static void AsyncReceive(IAsyncResult e) 56 { 57 ConnectInfo info = e.AsyncState as ConnectInfo; 58 if (info == null) return; 59 Socket clientSocket = info.clientSocket; 60 61 int bytesRead = 0; 62 try 63 { 64 bytesRead = clientSocket.EndReceive(e,out _socketError); 65 if (_socketError == SocketError.Success) 66 { 67 if (bytesRead > 0) 68 { 69 byte[] tmpbytes = new byte[bytesRead]; 70 Array.Copy(info.buffers, 0, tmpbytes, 0, bytesRead); 71 info.tmpAl.AddRange(tmpbytes); 72 73 if (clientSocket.Available > 0) 74 { 75 info.buffers = new byte[clientSocket.Available]; 76 clientSocket.BeginReceive(info.buffers, 0, clientSocket.Available, 0, out _socketError, new AsyncCallback(AsyncReceive), info); 77 } 78 else 79 { 80 byte[] endreceive = new byte[info.tmpAl.Count]; 81 info.tmpAl.CopyTo(endreceive); 82 int recLength=info.tmpAl.Count; 83 info.tmpAl.Clear(); 84 string content = Encoding.ASCII.GetString(endreceive); 85 Array.Clear(endreceive, 0, endreceive.Length); 86 87 string senddata = string.Format("rec={0}\r\nreceivedata={1}",recLength , content); 88 89 90 if (!string.IsNullOrEmpty(senddata)) 91 { 92 if (senddata.Length > BUFFER_SIZE) senddata = senddata.Substring(0, BUFFER_SIZE); 93 byte[] data = Encoding.ASCII.GetBytes(senddata); 94 info.clientSocket.BeginSend(data, 0, data.Length, 0, out _socketError,AsyncSend ,info); 95 } 96 97 Array.Clear(info.buffers, 0, info.buffers.Length); 98 clientSocket.BeginReceive(info.buffers, 0, BUFFER_SIZE, 0, out _socketError, new AsyncCallback(AsyncReceive), info); 99 } 100 } 101 } 102 } 103 catch(Exception ex) 104 { 105 clientSocket.BeginReceive(info.buffers, 0, BUFFER_SIZE, 0, out _socketError, new AsyncCallback(AsyncReceive), info); 106 } 107 108 109 } 110 }