C# 中socket根据Receive判断 TcpClient关闭连接的处理
程序脚手架
在C#中,开发网络应用,.Net Framework提供了各种便利。
- TcpListener用于服务端的监听服务。
- TcpClient 用于网络客户端的便利开发
服务端和客户端网络连接后,进行数据通信。
其中服务端的接收程序框架如下:
while (true)
{
try
{
byte[] buffer = new byte[1024];
int count = socket.Receive(buffer);
// ......
// 对接收到数据进行处理
}
catch (SocketException ex)
{
int err = ex.ErrorCode;
string str = socket.RemoteEndPoint.ToString();
logger.Warn(str + " socketErrorCode: "+ err+ " -- exception: " + ex.Message);
// ... ... 处理
}
catch(ObjectDisposedException closedException)
{
logger.Error(closedException);
// ... ... 处理
break;
}
catch (Exception ex)
{
logger.Error(ex);
// ... ... 处理
}
}
如果通信完成,需要结束,进行连接关闭。这是如果TcpClient进行关闭操作
tcpClient.Close();
tcpClient.Dispose();
问题
这里期望在客户端主动关闭后,服务端的socket会得到异常,然后在异常中进行后续处理,比如关闭服务端的对应socket,释放分配的数据缓冲器……。
但发现了一个问题,客户端TcpClient关闭后,接收处理线程并没有异常抛出。
Receive函数
在Socket类中,Receive函数是一个阻塞操作,没有数据receive到,则会阻塞。在接收到数据后,函数返回。
函数的说明如下:
//
// 摘要:
// 从绑定接收数据 System.Net.Sockets.Socket 到接收缓冲区中。
//
// 参数:
// buffer:
// 类型的数组 System.Byte ,它是接收到的数据的存储位置。
//
// 返回结果:
// 收到的字节数。
//
// 异常:
// T:System.ArgumentNullException:
// buffer 为 null。
//
// T:System.Net.Sockets.SocketException:
// 尝试访问套接字时出错。 有关详细信息,请参阅备注部分。
//
// T:System.ObjectDisposedException:
// System.Net.Sockets.Socket 已关闭。
//
// T:System.Security.SecurityException:
// 调用堆栈中的调用方没有所需的权限。
public int Receive(byte[] buffer);
当然可以设置receive的socket的receive-timeout;这样在timeout后,会有SocketExcept发生。但这个异常并不是客户端主动关闭或者网络中断。
//
// 摘要:
// 获取或设置一个值,指定的后的时间量同步 Overload:System.Net.Sockets.Socket.Receive 调用将会超时。
//
// 返回结果:
// 超时值(以毫秒为单位)。 默认值为 0,表示超时期限无限。 指定-1 还指示超时期限无限。
//
// 异常:
// T:System.Net.Sockets.SocketException:
// 尝试访问套接字时出错。
//
// T:System.ObjectDisposedException:
// System.Net.Sockets.Socket 已关闭。
//
// T:System.ArgumentOutOfRangeException:
// 设置操作为指定的值是小于-1。
public int ReceiveTimeout { get; set; }
但是如果连接的客户端主动关闭后,Receive函数会理解返回,而且返回值为0;这时就表示客户端主动关闭了。
如果此时服务端不关闭,则对应的网络连接处于 FIN_WAIT_2,CLOSE_WAIT状态,如图。
此时,服务端执行相应的socket关闭操作,就可以正常关闭连接了,否则,会有socket残余留下。正常关闭下的结果,如图: