C#基础知识篇:C#网络编程(Socket)使用poll函数判断连接断开问题

C# Socket使用poll函数判断连接断开问题

最近在学习c#的网络编程内容,遇到这样一个问题:在服务器端,如何判断客户端的一个连接是否断开?
查找相关资料,得出较好的解决方案是使用socket对象的poll函数

poll函数分析

下面是poll函数的官方描述:

public bool Poll (int microSeconds, System.Net.Sockets.SelectMode mode);

这是一个确定socket状态的函数。

参数

microSeconds Int32
等待响应的时间(以微妙为单位)。
mode SelectMode
SelectMode枚举类型中的一个,可选类型为SelectRead,SelectWrite,SelectError

返回值

Boolean 基于mode参数中传递的轮询模式值的socket的状态。

问题产生

  1. 首先,我的问题是从服务器端获得客户端的某个连接是否断开,作为服务器端,是需要对socketClient进行read操作的,因此对于poll函数中的第二个参数,选择SelectRead,这点毋庸置疑。
  2. 我们来看下,关于SelectRead模式的返回值的官方文档解析:
SelectRead:	如果已调用 Listen(Int32) 并且有挂起的连接,则为 true。
- 或 - 如果有数据可供读取,则为 true。 
- - 或 - 如果连接已关闭、重置或终止,则返回 true;
-  否则,返回 false。
  1. 对于该问题,网上的常规解决方法是:
    使用Socket类中的Poll方法,就可以。
    Socket client //假如已经创建好了,连接到服务器端得Socket的客户端对象。
    我们只要client.Poll(10,SelectMode.SelectRead)判断就行了。只要返回True是。就可以认为客户端已经断开了。
  2. 对于上述的解决方法,我存在质疑,即返回为True的情况明明有三种,为什么只要是True就代表是连接已关闭、重置或终止呢?

问题解决

  1. 基于网上常规的解决方案,我进行了分析和实验,代码和结果如下:
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;
                    }
                }
            }    
  1. 将poll的第一个参数改为负数,那么程序运行到此处一定会阻塞,直到有可读的内容,或者连接断开。
  2. 能进入if循环,说明有三种情况:连接没断开,有数据需要接收连接正常断开连接异常断开
  3. 对读取长度length进行判断,能确定正常断开情况;若异常断开,则通过try receive方法异常,来确定。

上述新模式基本能满足我能想到的所有情况了
该问题花费了我好长时间去研究测试,在此记录下,望对未来的自己和大家有所帮助~

你可能感兴趣的:(C#基础)