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),而客户没有在这之前关闭,程序有崩溃的风险。
一个高效的发送是客户发送端一直发,而不管状态,(接收状态通过异步方式)
while (B!= ConsoleKey.Q)
{
if (B == ConsoleKey.R)
{
for (int i = ; i < ; i++)
{
byte[] bytes = Encoding.UTF8.GetBytes("欢迎你"+i.ToString());
sender.Send(bytes);
sender.BeginReceive();
}
}
else if (B == ConsoleKey.C)
{
sender.Close(); } B = Console.ReadKey().Key;
}
接收端异步接收,异步发回状态。也可以同步发回。
try
{
cnt = wsocket.EndReceive(result); if (cnt == )
{
Console.WriteLine(cnt);
wsocket.Shutdown(SocketShutdown.Both);
wsocket.Close();
}
else
{ string str = Encoding.UTF8.GetString(receiveBuf, , cnt);
Console.WriteLine(str);
workSocket.Send(Encoding.UTF8.GetBytes("服务收到"+str)); wsocket.BeginReceive(receiveBuf, , receiveBuf.Length, SocketFlags.None, EndReceive, workSocket);
} }
catch (Exception oe)
{
Console.WriteLine(oe.Message);
}
最后一点客户端并发的发送,服务端接收的数据可能需要解决粘包问题,这个问题,有二种方式处理,一、特殊字符 二、定义包的协议。