终止一个连接的正常方式是发送FIN。在发送缓冲区中所有排队数据都已发送之后才发送FIN,正常情况下没有任何数据丢失。
但我们有时也有可能发送一个RST报文段而不是FIN来中途关闭一个连接。这称为异常关闭。
进程关闭socket的默认方式是正常关闭,如果需要异常关闭,利用SO_LINGER选项来控制。
异常关闭一个连接对应用程序来说有两个优点:
(1)丢弃任何待发的已经无意义的数据,并立即发送RST报文段;
(2)RST的接收方利用关闭方式来区分另一端执行的是异常关闭还是正常关闭。
值得注意的是RST报文段不会导致另一端产生任何响应,另一端根本不进行确认。收到RST的一方将终止该连接。程序行为如下:
阻塞模型下,内核无法主动通知应用层出错,只有应用层主动调用read()或者write()这样的IO系统调用时,内核才会利用出错来通知应用层对端RST。
非阻塞模型下,select或者epoll会返回sockfd可读,应用层对其进行读取时,read()会报错RST。
haproxy的实现中用到了这个选项。
游戏测试过程中发现某些socket错误经常出现,以下是测试游戏服务器时通常考虑的case.
服务器端:
1.
Case:客户端程序正常运行的情况下,拔掉网线,杀掉客户端程序
目的:模拟客户端死机、系统突然重启、网线松动或网络不通等情况
结论:这种情况下服务器程序没有检测到任何异常,并最后等待“超时”才断开TCP连接
2.
Case:客户端程序发送很多数据包后正常关闭Socket并exit进程(或不退出进程)
目的:模拟客户端发送完消息后正常退出的情况
结论:这种情况下服务器程序能够成功接收完所有消息,并最后收到“对端关闭”(Recv返回零)消息
3.
Case:客户端程序发送很多数据包后不关闭Socket直接exit进程
目的:模拟客户端程序退出而忘记关闭Socket的情况(比如通过Windows窗口的关闭图标退出进程,而没有捕获相应关闭事件做正常退出处理等)
结论:这种情况下服务器程序能够收到部分TCP消息,然后收到“104: Connection reset by peer”(Linux下)或“10054: An existing connection was forcibly closed by the remote host”(Windows下)错误
4.
Case:客户端程序发送很多数据包的过程中直接Kill进程
目的:模拟客户端程序崩溃或非正常方式结束进程(比如Linux下”kill -9″或Windows的任务管理器杀死进程)的情况
结论:这种情况下服务器程序很快收到“104: Connection reset by peer”(Linux下)或“10054: An existing connection was forcibly closed by the remote host”(Windows下)错误
5.
Case:客户端程序发送很多数据包后正常关闭Socket并exit进程(或不退出进程)
目的:模拟客户端正常关闭Socket后,服务器端在检查到TCP对端关闭前向客户端发送消息的情况
结论:这种情况下服务器程序接收和发送部分TCP消息后,在Send消息时产生“32: Broken pipe”(Linux下)或“10053: An established connection was aborted by the software in your host machine”(Windows下)错误
总结:
当TCP连接的进程在忘记关闭Socket而退出、程序崩溃、或非正常方式结束进程的情况下(Windows客户端),会导致TCP连接的对端进程产生“104: Connection reset by peer”(Linux下)或“10054: An existing connection was forcibly closed by the remote host”(Windows下)错误
当TCP连接的进程机器发生死机、系统突然重启、网线松动或网络不通等情况下,连接的对端进程可能检测不到任何异常,并最后等待“超时”才断开TCP连接
当TCP连接的进程正常关闭Socket时,对端进程在检查到TCP关闭事件之前仍然向TCP发送消息,则在Send消息时会产生“32: Broken pipe”(Linux下)或“10053: An established connection was aborted by the software in your host machine”(Windows下)错误