理解tcp顺序释放操作和tcp的半关闭

来源:互联网 发布:js href 跳转 target 编辑:程序博客网 时间:2024/05/22 09:20

理解tcp顺序释放操作和tcp的半关闭 时间: 2008.12.30 13:56:00 标签: Shutdown的调用 在关闭socket的时候,可以有两种方式closesocket和shutdown,这两个函数的区别在什么地方呢? #include /*UNIX*/ #include /*Windows*/ int shutdown(int s, int how) /*UNIX*/ int shutdown(SOCKET s, int how) /*Windows*/ how 操作 数字 POSIX Winsock 0 SHUT_RD SD_RECEIVE 关闭连接接收方 1 SHUT_WR SD_SEND 关闭连接的发送方 2 SHUT_RDWR SD_BOTH 关闭双方 Shutdown参数how值介绍说明: 1. shutdown根本没有关闭socket,任何与socket关联的资源直到调用closesocket才释放。 2. TCP连接的socket 是全双工的,也就是说它可以发送和接收数据,但是一个方向上的数据流动和另一个方向上的数据流动是不相关的,shutdown函数的功能也就是体现在这里,它通过设置how选择关闭哪条数据通道(发送通道和接收通道),如果关闭了发送通道,那么这个socket其实还可以通过接收通道接受数据. 3. 当通过以how=1的方式调用shutdown,就可以保证对方收到一个EOF,而不管其他的进程是否已经打开了套接字,而调用close或closesocket就不能保证,因为直到套接字的引用计数减为0时才会发送FIN消息给对方,也就是说,直到所有的进程都关闭了套接字。例子说明 客户端(192.168.1.122): 客户端连接上服务器后向服务器发送两条数据(数据发送间隔为2秒),等待两秒后立即调用shutdown关闭发送数据通道或者closesocket,然后客户端在一个while循环中等待接受服务器发送回来的数据. 服务器(192.168.1.112): 接受到客户端发送的数据后,休眠4秒后,把得到的数据发送会客户端 1. shutdown关闭发送数据通道后,通过抓包程序获取的tcp处理过程。 Shutdown关闭socket时,tcp的状态处理过程. 对上面的处理过程进行分析: l 前面的三条记录是tcp的三次握手. l 第四条记录就是客户端第一次向服务器发送数据 l 第五条记录是服务器的确认信息 l 第六条记录就是客户端间隔2秒后第二次向服务器发送数据 l 第七条记录时服务器的确认信息 l 第八条记录是在等待2秒后,调用了shutdown,客户端向服务器发送了FIN标志 l 第九条记录时服务器的确认信息 l 第十条记录是服务器接收到客户端的一条记录后休眠4秒,发回给客户端 l 第十一条记录是客户端的确认 l 第十二条记录是服务器接受到客户端的第二条数据后休眠4秒,发回给客户端 l 第十三条记录是客户端的确认 l 第十四条记录是服务器发送完tcp对列中的数据后,向客户端发送FIN标志 l 第十五条记录时客户端对FIN标志的确认 2. closesocket关闭socket,通过抓包程序获取的tcp处理过程 Closesocket关闭socket时,tcp的状态处理过程 对上面的处理过程进行分析: l 前面的三条记录是tcp的三次握手. l 第四条记录就是客户端第一次向服务器发送数据 l 第五条记录是服务器的确认信息 l 第六条记录就是客户端间隔2秒后第二次向服务器发送数据 l 第七条记录时服务器的确认信息 l 第八条记录是在等待2秒后,调用了shutdown,客户端向服务器发送了FIN标志 l 第九条记录时服务器的确认信息 l 第十条记录是服务器接收到客户端的一条记录后休眠4秒,发回给客户端 l 第十一条记录是由于客户端已经通过closesocket关闭了连接,连接已经不存在,因此客户端向服务器发送了RST标志总结 为了保证在连接撤销之前,保证双方都能接收到对等方的所有数据,在调用closesocket之前,先调用shutdown关闭发送数据通道. 例子代码客户端代码: #define WIN32_LEAN_AND_MEAN #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #pragma comment(lib,"ws2_32.lib") #pragma comment(lib,"Kernel32.lib") #define PORTNUM 4500 #define HOSTNAME "localhost" int main() { TCHAR szError[100]; // Error message string SOCKET ServerSock = INVALID_SOCKET; SOCKADDR_IN destination_sin; PHOSTENT phostent = NULL; WSADATA WSAData; // Initialize Winsocket. if (WSAStartup (MAKEWORD(1,1), &WSAData) != 0) { wsprintf (szError, TEXT("WSAStartup failed. Error: %d"), WSAGetLastError ()); MessageBox (NULL, szError, TEXT("Error"), MB_OK); return FALSE; } if ((ServerSock = socket (AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) { wsprintf (szError, TEXT("Allocating socket failed. Error: %d"), WSAGetLastError ()); MessageBox (NULL, szError, TEXT("Error"), MB_OK); return FALSE; } destination_sin.sin_family = AF_INET; if ((phostent = gethostbyname (HOSTNAME)) == NULL) { wsprintf (szError, TEXT("Unable to get the host name. Error: %d"), WSAGetLastError ()); MessageBox (NULL, szError, TEXT("Error"), MB_OK); closesocket (ServerSock); return FALSE; } memcpy ((char FAR *)&(destination_sin.sin_addr), phostent->h_addr, phostent->h_length); destination_sin.sin_port = htons (PORTNUM); if (connect (ServerSock, (PSOCKADDR) &destination_sin, sizeof (destination_sin)) == SOCKET_ERROR) { DWORD dw = GetLastError(); closesocket (ServerSock); return FALSE; } send(ServerSock,"ABCDEFG", 7, 0); //第一次发送数据 Sleep(2000); send(ServerSock,"1234567", 7, 0); //第二次发送数据 Sleep(2000); shutdown (ServerSock, 0x01); //关闭发送方的连接或者调用closesocket(ServerSock); char buffer[100] = {""}; while(1) { int rc = recv(ServerSock,buffer,99,0); if(rc < 0) { fprintf(stderr,"recv error,error code: %d/n",GetLastError()); fflush(stderr); break; } else if( rc == 0) { fprintf(stderr,"server disconnected/n"); fflush(stderr); break; } else { fputs(buffer,stdout); fputs("/n",stdout); fflush(stdout); } } closesocket (ServerSock); WSACleanup (); return TRUE; } 服务器端代码: #define WIN32_LEAN_AND_MEAN #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #pragma comment(lib,"ws2_32.lib") #pragma comment(lib,"Kernel32.lib") #define PORTNUM 4500 // Port number #define MAX_PENDING_CONNECTS 4 // Maximum length of the queue int main() { char szServerA[100] = {0}; // ASCII string TCHAR szError[100] = {0}; // Error message string SOCKET WinSocket = INVALID_SOCKET, // Window socket ClientSock = INVALID_SOCKET; // Socket for communicating // between the server and client SOCKADDR_IN local_sin, // Local socket address accept_sin; // Receives the address of the // connecting entity int accept_sin_len; // Length of accept_sin WSADATA WSAData; // Contains details of the Winsock // implementation // Initialize Winsock. if (WSAStartup (MAKEWORD(1,1), &WSAData) != 0) { wsprintf (szError, TEXT("WSAStartup failed. Error: %d"), WSAGetLastError ()); MessageBox (NULL, szError, TEXT("Error"), MB_OK); return FALSE; } // Create a TCP/IP socket, WinSocket. if ((WinSocket = socket (AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) { wsprintf (szError, TEXT("Allocating socket failed. Error: %d"), WSAGetLastError ()); MessageBox (NULL, szError, TEXT("Error"), MB_OK); return FALSE; } // Fill out the local socket's address information. local_sin.sin_family = AF_INET; local_sin.sin_port = htons (PORTNUM); local_sin.sin_addr.s_addr = htonl (INADDR_ANY); // Associate the local address with WinSocket. if (bind (WinSocket, (struct sockaddr *) &local_sin, sizeof (local_sin)) == SOCKET_ERROR) { wsprintf (szError, TEXT("Binding socket failed. Error: %d"), WSAGetLastError ()); MessageBox (NULL, szError, TEXT("Error"), MB_OK); closesocket (WinSocket); return FALSE; } // Establish a socket to listen for incoming connections. if (listen (WinSocket, MAX_PENDING_CONNECTS) == SOCKET_ERROR) { wsprintf (szError, TEXT("Listening to the client failed. Error: %d"), WSAGetLastError ()); MessageBox (NULL, szError, TEXT("Error"), MB_OK); closesocket (WinSocket); return FALSE; } accept_sin_len = sizeof (accept_sin); // Accept an incoming connection attempt on WinSocket. ClientSock = accept (WinSocket, (struct sockaddr *) &accept_sin, (int *) &accept_sin_len); if (ClientSock == INVALID_SOCKET) { wsprintf (szError, TEXT("Accepting connection with client failed.") TEXT(" Error: %d"), WSAGetLastError ()); MessageBox (NULL, szError, TEXT("Error"), MB_OK); closesocket (WinSocket); return FALSE; } char RecvBuf[100] = {""}; int lReadCount = 100; while(1) { int size = sizeof(RecvBuf); int nRes = recv(ClientSock,RecvBuf,lReadCount,0); if(nRes == 0) { fprintf(stderr,"client disconnected /n"); break; } else if( nRes < 0) { fprintf(stderr,"recv failed"); break; } printf("Recv: data: %s datalen: %d /r/n",RecvBuf,nRes); Sleep(4000); nRes = send(ClientSock,RecvBuf,nRes,0); if(nRes <= 0) { fprintf(stderr,"send failed,error code: %d/r/n",errno); } memset(RecvBuf,NULL,lReadCount); } // Close ClientSock. closesocket (ClientSock); WSACleanup (); return TRUE; } TCP结束的过程如下: Server Client -------------- FIN --------------> server: fin_wait_1 <------------- ACK --------------- client: close_wait server:fin_wait_2 <------------- FIN --------------- client发出fin之后就关闭 -------------- ACK -------------> server发出ack后进入time_wait状态 Time_Wait的默认时间是2倍的MLS,就是240秒钟。MLS是TCP 片在网上的最长存活时间。 TIME_Wait的主要作用是保证关闭的TCP端口不立即被使用。因为当网络存在延迟时,可能当某个端口被关闭后,网络中还有一些重传的TCP片在发向这个端口,如果这个端口立即建立新的TCP连接,则可能会有影响。所以使用2倍的MSL时间来限制这个端口立即被使用。 现在的问题在于,4分钟的时间有点长。因此,Time_wait的影响,我想,首先每个TCP连接都各自有个数据结构,叫TCP Control Block.Time_wait的时候这个数据结构没有被释放。所以当有太多的TCP连接时,内存可能会被占用很多。