在服务器端,如何判断客户端的一个连接是否断开?
查找相关资料,得出较好的解决方案是使用socket对象的poll函数。
poll函数分析
下面是poll函数的官方描述:
public bool Poll (int microSeconds, System.Net.Sockets.SelectMode mode);
1
这是一个确定socket状态的函数。
参数
microSeconds Int32
等待响应的时间(以微妙为单位)。
mode SelectMode
SelectMode枚举类型中的一个,可选类型为SelectRead,SelectWrite,SelectError
返回值
Boolean 基于mode参数中传递的轮询模式值的socket的状态。
问题产生
首先,我的问题是从服务器端获得客户端的某个连接是否断开,作为服务器端,是需要对socketClient进行read操作的,因此对于poll函数中的第二个参数,选择SelectRead,这点毋庸置疑。
我们来看下,关于SelectRead模式的返回值的官方文档解析:
SelectRead: 如果已调用 Listen(Int32) 并且有挂起的连接,则为 true。
- 或 - 如果有数据可供读取,则为 true。
- - 或 - 如果连接已关闭、重置或终止,则返回 true;
- 否则,返回 false。
对于该问题,网上的常规解决方法是:
使用Socket类中的Poll方法,就可以。
Socket client //假如已经创建好了,连接到服务器端得Socket的客户端对象。
我们只要client.Poll(10,SelectMode.SelectRead)判断就行了。只要返回True是。就可以认为客户端已经断开了。
对于上述的解决方法,我存在质疑,即返回为True的情况明明有三种,为什么只要是True就代表是连接已关闭、重置或终止呢?
问题解决
基于网上常规的解决方案,我进行了分析和实验,代码和结果如下:
while (true)
{
if (tcpClientSocket.Poll(10, SelectMode.SelectRead))
{
Console.WriteLine("进入了if条件");
break;
}
int length = tcpClientSocket.Receive(data);
Console.WriteLine(" 接受到的信息是: " + Encoding.UTF8.GetString(data, 0, length));
}
这是简单的服务器接收客户端发送过来消息的代码。(客户端发消息的代码就不贴了,用个死循环send就好了)。
经过测试,发现if条件是能进去的,也就是说,poll返回True的时候,并不代表是连接断开了,也有可能是存在可读的数据。那么,网上的解决方法难道不对吗?
2. 其实,上面的方法用来判断连接断开也是可以的,并不完全不对,只是适用面窄了点。它有两个关键问题没有解决。
一、它只适用于客户端发送消息具有相对较长间隔的情况。也就是说,在大于poll函数响应时间间隔的情况下,send消息,那么就不会进入if循环了。因为,poll函数的第一个参数为响应时间,在这个响应时间内,相当于执行到poll这里,进程被“阻塞”了,在此时间内若接受到消息,或连接断开,那么poll返回的确实是True。所以在客户端中不停的使用while循环send消息,前面send的消息客户端receive还没有接收掉,相当于是存在“可读的数据了”,所以poll返回了True。
综上,想要使用上述模式,需要把响应时间设置小点;并且,客户端发送消息不能太快,至少要大于响应时间+系统receive的时间以保证不存在“可读的数据”。这样一来,每次执行上述的while循环时,可以确定,socket中已经不存在“可读的数据”了,数据都已经在上一次循环中被receive接受。那么进入if条件只有一种可能:连接断开。
二、上述模式不适用于连接错误断开的情况。究其原因,还是因为响应时间太短,10微妙的响应时间,导致基本上程序都是“阻塞”在int length = tcpClientSocket.Receive(data);这句代码上。此时若连接正常关闭(指使用socket.shutdown(both);socket.close()来关闭客户端),那么receive将收到0个字节的数据并输出,循环将在下一次的if中跳出;若不正常关闭(如控制台程序强制关闭等),那么receive将抛出异常,程序中断。我不想多输出这个0字节的数据,并想处理不管是正常或不正常的关闭,所以使用了如下的改进模式。
优化代码
while (true)
{
if (tcpClientSocket.Poll(-1, SelectMode.SelectRead))
{
try
{
int length = tcpClientSocket.Receive(data);
if (length == 0)
{
Console.WriteLine("连接正常断开!!");
break;
}
Console.WriteLine(" 接受到的信息是: " + Encoding.UTF8.GetString(data, 0, length));
}
catch
{
Console.WriteLine("连接异常断开!!");
break;
}
}
}
将poll的第一个参数改为负数,那么程序运行到此处一定会阻塞,直到有可读的内容,或者连接断开。
能进入if循环,说明有三种情况:连接没断开,有数据需要接收或连接正常断开或连接异常断开。
对读取长度length进行判断,能确定正常断开情况;若异常断开,则通过try receive方法异常,来确定。