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 }
最后一点客户端并发的发送,服务端接收的数据可能需要解决粘包问题,这个问题,有二种方式处理,一、特殊字符 二、定义包的协议。