首页 > 代码库 > Socket编程实践(9) --TCP服务器常见问题(4)
Socket编程实践(9) --TCP服务器常见问题(4)
TCP/IP协议的11种状态
说明:
1.如下图(客户端与服务器都在本机:双方(server的子进程,与client)链接已经建立(ESTABLISHED),等待通信)
2.最先调用close的一端,后面会进入TIME_WAIT的状态(下图,server端首先关闭)
3.TIME_WAIT 时间是2MSL(报文的最长存活周期的2倍)
原因:(ACK y+1)如果发送失败可以重发。
服务器端处于closed状态,不等于客户端也处于closed状态。。
4.TCP/IP协议的第1种状态:图上只包含10种状态,还有一种CLOSING状态
产生CLOSING状态的原因:
Server端与Client端同时关闭(同时调用close,此时两端同时给对端发送FIN包),将产生closing状态,最后双方都进入TIME_WAIT状态(如下图)。
SIGPIPE
如果对方socket已关闭,对等方再发写数据,则会产生SIGPIPE信号
往一个已经接收FIN的套接中写是允许的,接收到FIN仅仅代表对方不再发送数据;但是在收到RST段之后,如果还继续写,调用write就会产生SIGPIPE信号,对于这个信号的处理我们通常忽略即可。
signal(SIGPIPE, SIG_IGN);
Client端测试代码:(请注意观察27-39,44,53行代码)
//client端完整代码实现及解析 #include "commen.h" //return a socket that have connected to server. int mkATCPClient(int serverPort, string serverIPAddr) { //first. create a socket int sockfd = socket(AF_INET,SOCK_STREAM,0); if (sockfd == -1) { err_exit("socket error"); } //second. connect to a server struct sockaddr_in serverAddr; serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(serverPort); serverAddr.sin_addr.s_addr = inet_addr(serverIPAddr.c_str()); if (connect(sockfd,(struct sockaddr *)&serverAddr,sizeof(serverAddr)) == -1) { err_exit("connect error"); } return sockfd; } //捕获SIGPIPE信号的测试代码 void onSignalCatch(int signalNumber) { switch(signalNumber) { case SIGPIPE: cout << "receive SIGPIPE = " << SIGPIPE << endl; _exit(0); default: cout << "UnKnow signal" << endl; break; } } int main() { //安装捕获SIGPIPE信号的处理服务 signal(SIGPIPE,onSignalCatch); int serverSocket = mkATCPClient(8002,"127.0.0.1"); char sendBuf[BUFSIZ]; //从键盘输入数据 while (fgets(sendBuf,sizeof(sendBuf),stdin) != NULL) { //向server发送数据(注意在server端关闭时也要发送数据,注意观察现象) if (writen(serverSocket,sendBuf,strlen(sendBuf)) == -1) { err_exit("write socket error"); } } close(serverSocket); return 0; }
说明:在server端关闭(或者是处理该客户端的子进程关闭)后,仍继续往socket中写数据,则会观察到如下现象:
Close VS. Shutdown
Man-Page
SYNOPSIS
#include <unistd.h> int close(int fd); #include <sys/socket.h> int shutdown(int sockfd, int how);
DESCRIPTION
close() closes a file descriptor, so that it no longer refers to any file and may be reused(引用计数减至0). Any record locks (记录锁[文件锁]see fcntl(2)) held on the file it was associated with, and owned by the process, are removed (regardless of the file descriptor that was used to obtain the lock).
If fd is the last file descriptor referring to the underlying open file description (see open(2)), the resources associated with the open file description are freed; if the descriptor was the last reference to a file which has been removed using unlink(2) the file is deleted.
The shutdown() call causes all or part of a full-duplex connection on the socket associated with sockfd to be shut down. If how is SHUT_RD, further receptions will be disallowed. If how is SHUT_WR, further transmissions will be disallowed. If how is SHUT_RDWR, further receptions and transmissions will be disallowed.
说明:
1.close终止了数据传送的两个方向;而shutdown可以有选择的终止某个方向的数据传送或者终止数据传送的两个方向。
2.shutdown可以保证对等方接收到一个EOF字符,而不管其他进程是否已经打开了套接字(shutdown不管套接字中的引用计数!)。而close不能保证,close需要直到套接字引用计数减为0时才发送。也就是说直到所有的进程都关闭了套接字。
思考1
客户端向服务器发送:FIN(close) E D C B A
问:服务器还能收到数据吗?服务器还可以向客户端回报文吗?
客户端想在关闭之后,仍然能接收到回射服务器应答。
思考2
父进程中close;会不会向客户端发送FIN报文段呢?
文件的引用计数-1,当减少为0,才会发送引用计数。
思考3:
客户端//shutdown(sock, SHUT_WR);只关闭了写;
实践(server端业务处理部分代码,其余代码同前):
else if (pid == 0) //子进程,处理业务 { close(serverSockfd); //子进程关闭监听套接字,因为子进程不负责监听任务 char recvBuf[BUFSIZ]; ssize_t readCount = 0; while (true) { memset(recvBuf,0,sizeof(recvBuf)); if ((readCount = read(peerSockfd,recvBuf,sizeof(recvBuf))) == -1) { err_exit("readn error"); } else if (readCount == 0) { peerClosePrint("client connect closed"); } if (strncmp(recvBuf,"close",5) == 0) { //close(peerSockfd); shutdown(peerSockfd,SHUT_WR); } //将整体报文回写回客户端 if (writen(peerSockfd,recvBuf,strlen(recvBuf)) == -1) { err_exit("writen error"); } recvBuf[readCount] = 0; //写至终端 fputs(recvBuf,stdout); } } else if (pid > 0) //父进程 { //close(peerSockfd); }
/**注意:
1.父进程中close调用已经被注释,也就是在只有一个子进程的情况下,peerSockfd的引用计数为2
2.在子进程中,如果调用的是shutdown,通信截图如下:
客户端自动关闭(说明客户端能够收到对方发来的FIN报文,说明shutdown确实将peerSockfd给关掉了,它并不理会peerSockfd的引用计数不为1)
3.如果调用的是close函数(谨记:此时peerSockfd的引用计数并未减为0),通信截图如下:
客户端需要显示的终止(说明客户端并未受到FIN报文,说明close函数需要考虑到peerSockfd引用计数不为1的情况)
*/
Socket编程实践(9) --TCP服务器常见问题(4)