Unix網路-shutdown和close
四次揮手狀態:
close終止了套接字傳送資料的方向。假如我們的客戶端和伺服器端進行通訊,我們在客戶端將socket套接字close,那麼我們無法再利用這個套接字向伺服器端傳送資訊,也無法再利用這個套接字從伺服器中接受資訊。但是shutdown不同,我們可以自己選擇shutdown之後套接字的功能。
在多程序伺服器中,如果父程序close conn套接字,那麼伺服器不會像客戶端傳送FIN訊號,因為套接字有一個引用計數。每建立一個子程序,這個引用計數就加1。shutdown不管在哪個程序中,只要shutdown就一定能傳送FIN訊號。
客戶端cpp
客戶端 client.cpp —————————————————————————————————— #includeView Code<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; }View Code
接著啟動客戶端和服務端,我們連續快速傳送兩條資訊給服務端,最終的結果如下:
客戶端出現了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; }View Code
結果如下:
伺服器將不會自動結束
接下來我們使用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; }View Code
結果就是,當我們在客戶端按下CTRL+D時,客戶端會等待,等待資訊回射完畢之後,再退出