socket 异步通信的一些问题
socket通信在使用时被封装很简单,像操作文件一样简单,正是因为简单里面好多细节需要深入研究一下。
windows下通信有select和iocp方式,select是传统方式,在socket里使用receive send这种方式出现,iocp是一种高效的方式,不会产生过多线程,被socket使用beginreceive和endreceive和这种异步委托方式实现。
在开发中,最麻烦的是写接收服务(receive),因为发送方总是不可控的,一个稳定receive服务,需要了解,连接,接收机制,关闭,数据缓冲,错误处理。当然还有最麻烦的包边界问题。连接BeginAccept 是一个新连接必然进入的方法主要是EndAccept得到一个工作的socket(且叫它worksocket,就是与指定客户连接的一个socket),这个worksocket远程地址就是连接的客户端口发出的地址。 这时workSocket != null && workSocket.Connected 是true的。beginReceive 是在接收端循环接收的异步方式(只要这个worksocket不断)它会一直徘徊在beginReceive 和EndReceive中,beginreceive用来从端口缓冲区读到指定缓冲区,(socket通信有二个缓冲区),缓冲区不可能无限的大,所以每次可以读到最大也就是指定缓冲区尺寸<=EndReceive返回的值。
socket关闭,有以下三种情况。
一,如果客户端口直接关闭,这时服务端在EndReceive时直接抛错,不回有0的返回,只会抛出一个远程连接被关闭了。
二、如果客户端口调用shutdown 与close而服务端口EndReceive 返回 0(返回0说明远程已经关闭了)这时必须worksocket.shutdown和close方法,如果不及时关闭 一直返回0,这个连接一直不会断。在beginreceiv中不要用Connected判断,这个状态一直是false,还有Available也是不准的,
三、如果服务端把一个worksocket先给关闭了(不是整个socket),而客户没有在这之前关闭,程序有崩溃的风险。
一个高效的发送是客户发送端一直发,而不管状态,(接收状态通过异步方式)
1 while (B!= ConsoleKey.Q) 2 { 3 if (B == ConsoleKey.R) 4 { 5 for (int i = 0; i < 50; i++) 6 { 7 byte[] bytes = Encoding.UTF8.GetBytes("欢迎你"+i.ToString()); 8 sender.Send(bytes); 9 sender.BeginReceive(); 10 } 11 } 12 else if (B == ConsoleKey.C) 13 { 14 sender.Close(); 15 16 } 17 18 B = Console.ReadKey().Key; 19 }
接收端异步接收,异步发回状态。也可以同步发回。
1 try 2 { 3 cnt = wsocket.EndReceive(result); 4 5 if (cnt == 0) 6 { 7 Console.WriteLine(cnt); 8 wsocket.Shutdown(SocketShutdown.Both); 9 wsocket.Close(); 10 } 11 else 12 { 13 14 string str = Encoding.UTF8.GetString(receiveBuf, 0, cnt); 15 Console.WriteLine(str); 16 workSocket.Send(Encoding.UTF8.GetBytes("服务收到"+str)); 17 18 wsocket.BeginReceive(receiveBuf, 0, receiveBuf.Length, SocketFlags.None, EndReceive, workSocket); 19 } 20 21 } 22 catch (Exception oe) 23 { 24 Console.WriteLine(oe.Message); 25 }
最后一点客户端并发的发送,服务端接收的数据可能需要解决粘包问题,这个问题,有二种方式处理,一、特殊字符 二、定义包的协议。