TCP建立一個連接需要三次握手,但是終止一個連接需要四次揮手: 1. 當某個應用進程主動調用close時,它向對端發送一個FIN分節,表示這端需要關閉連接 2. 當對端接收到FIN分節時,read函數返回0,它的TCP發送一個ACK,表示接收到了主動close端的FIN分節。主動關閉端的TCP在接受到ACK后處于FIN_WAIT狀態,表示需要等待對端的FIN分節到達。 3. 被動關閉端發送FIN分節給主動關閉端,主動關閉端收到FIN后,發送ACK給對端,處于TIME_WAIT狀態,表示等待被動關閉端的ACK確認 4. 被動關閉端接收到ACK后四次揮手完成,兩端套接字關閉完成
close在TCP中的默認行為是把該套接字標記為已關閉,然后立即返回到調用進程。該套接字描述符不能再由調用進程使用,也就是不能再作為read,write的第一個參數。然而TCP將嘗試發送已排隊等待發送到對端的任何數據,發送完畢后再發送TCP連接終止序列。
close的錯誤返回三種情況: - EBADF:表示參數fd為非法描述符 - EINTR:close調用被信號中斷 - EIO:I/O時出現錯誤
上述代碼中close被調用2次,因為子進程和父進程共享了client_fd,其引用計數為2,如果子進程只調用一次,則會導致client_fd的套接字永遠不能關閉,導致進程描述符耗盡而無法為其他請求提供服務。并且當close調用時,TCP的讀寫2端都被關閉。
close在網絡編程中的局限: - 當一個描述符被共享使用時,調用close函數只是將其引用計數減一,僅僅在引用計數為0的時候,該套接字才被關閉。 - close終止讀和寫2個方向的數據傳輸。TCP為全雙工協議,有時候當我們完成數據發送后,可能需要等待對端發送數據,此時可以調用shutdown來實現此功能。
howto的可選項: SHUT_RD:關閉連接的讀——套接字中不再讀取數據,而且套接字接受緩沖區中的數據也會被丟棄。進程不能再對這個套接字調用任何讀取函數。 SHUT_WR:關閉連接的寫——對于TCP套接字,這成為半關閉。當前留在套接字發送緩沖區中的數據將被發送掉,然后TCP的正常連接終止序列。無論這個套接字描述符的引用計數是否為0,shutdown都會激發TCP終止序列,以后進程不能再對這個套接字調用任何寫函數。 SHUT_RDWR:關閉讀、寫——相當于分別調取了shutdown兩次并傳遞參數SHUT_RD和SHUT_WR。shutdown使用此選項與close的區別是,shutdown立馬關閉套接字的讀寫通道,但是close只會在引用計數為0的情況才關閉讀寫通道。
主程序
#include <stdio.h>#include <stdlib.h>#include <sys/types.h>#include <sys/socket.h>#include <sys/select.h>#include <arpa/inet.h>#include <netinet/in.h>extern void client_echo(FILE *fp,int sockfd);int main(){ int sockfd = socket(AF_INET,SOCK_STREAM,0); struct sockaddr_in serv_addr; struct sockaddr_in client_addr; int client_len; serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(LISTEN_PORT); char *strip = inet_pton(AF_INET,"127.0.0.1",&serv_addr.sin_addr); int ret = connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)); client_echo(stdin,sockfd);//從stdin讀取數據發送到sockfd并回寫 return 0;}使用close的實現
void client_echo(FILE *fp,int sockfd){ if(fp == NULL) return; int fd = fineno(fp); fd_set read_set; FD_ZERO(&read_set); int stdin_eof = 0; int nread = 0; char recvbuf[1024]; while(1){ FD_SET(fd,&read_set); FD_SET(sockfd,&read_set); int maxfd = max(fd,sockfd); int ret = select(maxfd + 1,&read_set,NULL,NULL,NULL); if(ret < 0){ if(errno == EINTR) continue; // 由于中斷引發的失敗,重試 else { // 其他錯誤,退出 perror("select"); return; } } if(FD_ISSET(sockfd,&read_set)){ if( (nread = read(sockfd,recvbuf,sizelf(recvbuf))) == 0){ 在上面的程序中,當客戶端從終端讀取到CTRL+D的時候將使fgets函數返回0,此時引發客戶端主動關閉close,退出客戶端的連接處理函數后,將立即退出程序(main函數結束)。此時服務端如果有數據正在發送,則將會丟失。處理這種問題的方式是,當客戶端不再write時,只關閉TCP的write,但是任然保留其read通道。可以通過shutdown來實現。使用shutdown的實現
void client_echo(FILE *fp,int sockfd){ if(fp == NULL) return; int fd = fineno(fp); fd_set read_set; FD_ZERO(&read_set); int stdin_eof = 0; int nread = 0; char recvbuf[1024]; int maxfd; while(1){ FD_SET(fd,&read_set); FD_SET(sockfd,&read_set); if(stdin_eof != 0){ FD_CLR(fd,&read_set); maxfd = sockfd; } else maxfd = max(fd,sockfd); int ret = select(maxfd + 1,&read_set,NULL,NULL,NULL); if(ret < 0){ if(errno == EINTR) continue; // 由于中斷引發的失敗,重試 else { // 其他錯誤,退出 perror("select"); return; } } if(FD_ISSET(sockfd,&read_set)){ if( (nread = read(sockfd,recvbuf,sizelf(recvbuf))) == 0){ if(stdin_eof == 1){ return; } else{ printf("read EOF from serv/n"); close(sockfd); exit(1); } } fputs(recvbuf,stdout); } if(FD_ISSET(fd,&read_set)){ if(fgets(recvbuf,sizeof(recvbuf),fd) == 0){ stdin_eof = 1; shutdown(sockfd,SHUT_WR); // send FIN FD_CLR(fd,&read_set); } else write(sockfd,recvbuf,strlen(recvbuf)); } }}在shutdown_client中,當從終端讀取到EOF時,將調用shutdown關閉套接字的寫通道,但是此套接字任然可以從服務端讀取數據,保證了數據不會丟失。
新聞熱點
疑難解答