四次挥手状态:
close终止了套接字传送数据的方向。假如我们的客户端和服务器端进行通信,我们在客户端将socket套接字close,那么我们无法再利用这个套接字向服务器端发送信息,也无法再利用这个套接字从服务器中接受信息。但是shutdown不同,我们可以自己选择shutdown之后套接字的功能。
在多进程服务器中,如果父进程close conn套接字,那么服务器不会像客户端发送FIN信号,因为套接字有一个引用计数。每创建一个子进程,这个引用计数就加1。shutdown不管在哪个进程中,只要shutdown就一定能发送FIN信号。
客户端cpp
客户端 client.cpp —————————————————————————————————— #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include<sys/select.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include<iostream> using namespace std; int main(){ int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -1) { perror("socket() err"); return -1; } struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(8888); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) { perror("socket() err"); return -1; } char sendbuf[1024]={0}; char recvbuf[1024]={0}; fd_set rset; FD_ZERO(&rset); int nready; int fd_stdin=fileno(stdin); while(1){ FD_SET(fd_stdin,&rset); FD_SET(sockfd,&rset); int maxfd; if(sockfd>fd_stdin) maxfd=sockfd; else maxfd=fd_stdin; nready=select(maxfd+1,&rset,NULL,NULL,NULL); if(nready==-1) perror("nready"); if(nready==0) continue; if(FD_ISSET(sockfd,&rset)){ int rc=read(sockfd,recvbuf,sizeof(recvbuf)); if(rc<0){ perror("read() error"); break; }else if(rc==0){ printf("connect is cloesd !\n"); break; } } printf("recv message:%s\n",recvbuf); memset(sendbuf,0,sizeof(sendbuf)); memset(recvbuf,0,sizeof(recvbuf)); if(FD_ISSET(fd_stdin,&rset)){ if(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL) write(sockfd,sendbuf,sizeof(sendbuf)); else { close(sockfd);\\我们在此处设置关闭客户端套接字,ctrl+D }; } } return 0; }
服务器端cpp
#include<stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include<iostream> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include<sys/select.h> #include<errno.h> using namespace std; int main() { int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -1) { perror("socket() err"); return -1; } int on = 1; if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { perror("setsockopt() err"); return -1; } char recvbuf[1024]; int conn; int client[FD_SETSIZE]; for(int i=0;i<FD_SETSIZE;i++){ client[i]=-1; } struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(8888); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); if (bind(sockfd, (struct sockaddr *) &addr, sizeof(addr)) == -1) { perror("bind() err"); return -1; } if (listen(sockfd, SOMAXCONN) == -1) { perror("bind() err"); return -1; } struct sockaddr_in peeraddr; // socklen_t peerlen = sizeof(peeraddr); /* conn = accept(sockfd, (struct sockaddr *) &peeraddr, &peerlen); if (conn == -1) { perror("accept() err"); return -1; } printf("accept by :%s \n", inet_ntoa(peeraddr.sin_addr));*/ //char recvbuf[1024] = { 0 }; int nready; int maxfd=sockfd; fd_set rset; int maxi=0; fd_set allset; FD_ZERO(&rset); FD_ZERO(&allset); FD_SET(sockfd,&allset); while(1){ rset=allset; nready=select(maxfd+1,&rset,NULL,NULL,NULL); if(FD_ISSET(sockfd,&rset)){ socklen_t peerlen = sizeof(peeraddr); conn=accept(sockfd,(struct sockaddr *) &peeraddr, &peerlen); printf("ip=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port)); for(int i=0;i<FD_SETSIZE;i++){ if(client[i]<0){ client[i]=conn; if(i>maxi) maxi=i; break; } } FD_SET(conn,&allset); if(conn>maxfd) maxfd=conn; if(--nready<=0) continue; } for(int i=0;i<FD_SETSIZE;i++){ conn=client[i]; if(conn==-1) continue; if(FD_ISSET(conn,&rset)){ int rc = read(conn, recvbuf, sizeof(recvbuf)); if (rc == 0) { cout<<"client has closed"<<endl; FD_CLR(conn,&allset); client[i]=-1; close(conn); } else{ sleep(5);\\在这里设置5秒后才对客户端进行回应,这样就可以模拟他们之间的管道。 printf("recv message:%s\n", recvbuf); write(conn, recvbuf, rc); memset(recvbuf, 0, sizeof(recvbuf)); if(--nready<=0) break; } } } } close(conn); close(sockfd); return 0; }
接着启动客户端和服务端,我们连续快速发送两条信息给服务端,最终的结果如下:
客户端出现了bad file descriptor的错误,这是因为,我们将套接字关闭后,又在rset中设置了其监听位导致的。然后接着看到,服务器端也崩溃了,自动退出。这是因为,当服务器准备回射第一条信息时,客户端的套接字已经关闭,此时,服务器端会接收到一个RST,如果服务器再次尝试回射,那么服务器会产生一个SIGPIPE信号,如果我们不会SIGPIPE信号处理,那么,根据信号的默认处理规则SIGPIPE信号的默认执行动作是terminate(终止、退出),所以server会退出。我们来证明一下,我们客户端只发送一条信息,那么服务端只会受到一个RST,而不会退出。
可以看到,服务器并没有退出。
那么接下来,我们对SIGPIPE信号进行处理,这样也不会导致服务端的退出。
server.cpp
#include<stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include<iostream> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include<sys/select.h> #include<errno.h> #include<sys/signal.h> using namespace std; //信号处理 void handler(int sig){ cout<<sig<<endl; } int main() { //signal(SIGPIPE,SIG_IGN) //我们默认自动处理也行 signal(SIGPIPE,handler); //用handler处理 int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -1) { perror("socket() err"); return -1; } int on = 1; if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { perror("setsockopt() err"); return -1; } char recvbuf[1024]; int conn; int client[FD_SETSIZE]; for(int i=0;i<FD_SETSIZE;i++){ client[i]=-1; } struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(8888); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); if (bind(sockfd, (struct sockaddr *) &addr, sizeof(addr)) == -1) { perror("bind() err"); return -1; } if (listen(sockfd, SOMAXCONN) == -1) { perror("bind() err"); return -1; } struct sockaddr_in peeraddr; // socklen_t peerlen = sizeof(peeraddr); /* conn = accept(sockfd, (struct sockaddr *) &peeraddr, &peerlen); if (conn == -1) { perror("accept() err"); return -1; } printf("accept by :%s \n", inet_ntoa(peeraddr.sin_addr));*/ //char recvbuf[1024] = { 0 }; int nready; int maxfd=sockfd; fd_set rset; int maxi=0; fd_set allset; FD_ZERO(&rset); FD_ZERO(&allset); FD_SET(sockfd,&allset); while(1){ rset=allset; nready=select(maxfd+1,&rset,NULL,NULL,NULL); if(FD_ISSET(sockfd,&rset)){ socklen_t peerlen = sizeof(peeraddr); conn=accept(sockfd,(struct sockaddr *) &peeraddr, &peerlen); printf("ip=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port)); for(int i=0;i<FD_SETSIZE;i++){ if(client[i]<0){ client[i]=conn; if(i>maxi) maxi=i; break; } } FD_SET(conn,&allset); if(conn>maxfd) maxfd=conn; if(--nready<=0) continue; } for(int i=0;i<FD_SETSIZE;i++){ conn=client[i]; if(conn==-1) continue; if(FD_ISSET(conn,&rset)){ int rc = read(conn, recvbuf, sizeof(recvbuf)); if (rc == 0) { cout<<"client has closed"<<endl; FD_CLR(conn,&allset); client[i]=-1; close(conn); } else{ sleep(5); printf("recv message:%s\n", recvbuf); write(conn, recvbuf, rc); memset(recvbuf, 0, sizeof(recvbuf)); if(--nready<=0) break; } } } } close(conn); close(sockfd); return 0; }
结果如下:
服务器将不会自动结束
接下来我们使用shutdown对客户端进行关闭。详细的shutdown参数可以自己man 一下
client.cpp
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include<sys/select.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include<iostream> using namespace std; int main(){ int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -1) { perror("socket() err"); return -1; } struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(8888); addr.sin_addr.s_addr = inet_addr("127.0.0.1"); if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1) { perror("socket() err"); return -1; } char sendbuf[1024]={0}; char recvbuf[1024]={0}; fd_set rset; FD_ZERO(&rset); int nready; int fd_stdin=fileno(stdin); while(1){ FD_SET(fd_stdin,&rset); FD_SET(sockfd,&rset); int maxfd; if(sockfd>fd_stdin) maxfd=sockfd; else maxfd=fd_stdin; nready=select(maxfd+1,&rset,NULL,NULL,NULL); if(nready==-1) perror("nready"); if(nready==0) continue; if(FD_ISSET(sockfd,&rset)){ int rc=read(sockfd,recvbuf,sizeof(recvbuf)); if(rc<0){ perror("read() error"); break; }else if(rc==0){ printf("connect is cloesd !\n"); break; } } printf("recv message:%s\n",recvbuf); memset(sendbuf,0,sizeof(sendbuf)); memset(recvbuf,0,sizeof(recvbuf)); if(FD_ISSET(fd_stdin,&rset)){ if(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL) write(sockfd,sendbuf,sizeof(sendbuf)); else { shutdown(sockfd,SHUT_WR);\\shutdown套接字的写入功能,但是不关闭读取功能 }; } } return 0; }
结果就是,当我们在客户端按下CTRL+D时,客户端会等待,等待信息回射完毕之后,再退出