1 优雅关闭套接字简介
在《VS2015套接字编程断开套接字连接》中提到,可以使用shutdown()、closesocket()和WSACleanup()三个函数组合在一起关闭已经连接的套接字。在关闭套接字时,对于流套接字,可能会在数据队列中还存在未发送的数据,在关闭套接字之后,对端将无法接收到这些数据。优雅关闭套接字的作用是,当关闭套接字之前,将数据队列中还未发送的数据发送给对方,再关闭套接字。
在《VS2015中基于TCP客户端的实现》和《VS2015中基于TCP/IP的服务端的实现》中提到了基于TCP/IP通信的客户端和服务端的流程。服务端创建套接字、绑定套接字、设置套接字的监听模式并等待客户端的连接。而客户端创建套接字之后,就通过该套接字连接服务端,服务端在接收了客户端的连接之后,创建一个新的套接字,并使用while()循环接收来自客户端的数据,客户端向服务端发送数据,服务端接收数据并处理,之后再次等待客户端数据的到来。
优雅关闭套接字就是基于以上前提的,此时客户端的数据已经发送完毕,而服务端正处于while()循环中,等待服务端发送数据,而此时服务端也有可能向客户端发送数据。在服务端发送数据完毕后,要断开套接字时,应当考虑此时服务端是否有数据正向自己发送,所以要使用优雅断开套接字的方式来解决这个问题。
2 优雅关闭套接字的具体流程
图1 优雅关闭套接字流程框图
在图1中,红色表示客户端,黑色表示服务端。由客户端首先通过shutdown()关闭本端的发送功能,此时服务端会收到FD_CLOSE指令(该指令由系统内核处理,服务端代码无需关系),服务端的recv()函数返回值为0,此时服务端知道客户端已经关闭了发送数据的功能,则退出while()循环。而客户端在通过shutdown()关闭本端的发送功能后,为了能接收到服务端还未来得及发送的数据,需要通过while()循环接收来自服务端的数据。而服务端在退出了while()循环之后,通过shutdown()关闭本端的发送功能,此时客户端会收到FD_CLOSE指令,说明服务端的数据已经发送完毕,则客户端退出while()循环。这样,服务端和客户端在套接字被断开之前都可以完全收到了对端的数据,实现了套接字的优雅关闭。最后,双方调用closesocket()函数和WSACleanup()函数,关闭套接字和释放Winsock动态库资源。
3相关代码
3.1 客户端代码
iResult = shutdown(ConnectSocket, SD_SEND);
if (iResult == SOCKET_ERROR)
{
closesocket(ConnectSocket);
WSACleanup();
return 1;
}
do {
iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
} while( iResult > 0 );
closesocket(ConnectSocket);
WSACleanup();
在以上代码中,ConnectSocket是客户端与服务端通信的套接字,SD_SEND表示shutdwon()函数关闭了套接字的发送功能,recvbuf用于保存从服务端中接收到的数据。
3.2 服务端代码
do {
iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
} while (iResult > 0);
iResult = shutdown(ClientSocket, SD_SEND);
if (iResult == SOCKET_ERROR)
{
closesocket(ClientSocket);
WSACleanup();
return 1;
}
closesocket(ClientSocket);
WSACleanup();
在以上代码中ClientSocket是服务端用于与客户端通信的套接字。