TCP建立一個(gè)連接需要三次握手,但是終止一個(gè)連接需要四次揮手: 1. 當(dāng)某個(gè)應(yīng)用進(jìn)程主動(dòng)調(diào)用close時(shí),它向?qū)Χ税l(fā)送一個(gè)FIN分節(jié),表示這端需要關(guān)閉連接 2. 當(dāng)對(duì)端接收到FIN分節(jié)時(shí),read函數(shù)返回0,它的TCP發(fā)送一個(gè)ACK,表示接收到了主動(dòng)close端的FIN分節(jié)。主動(dòng)關(guān)閉端的TCP在接受到ACK后處于FIN_WAIT狀態(tài),表示需要等待對(duì)端的FIN分節(jié)到達(dá)。 3. 被動(dòng)關(guān)閉端發(fā)送FIN分節(jié)給主動(dòng)關(guān)閉端,主動(dòng)關(guān)閉端收到FIN后,發(fā)送ACK給對(duì)端,處于TIME_WAIT狀態(tài),表示等待被動(dòng)關(guān)閉端的ACK確認(rèn) 4. 被動(dòng)關(guān)閉端接收到ACK后四次揮手完成,兩端套接字關(guān)閉完成 
close在TCP中的默認(rèn)行為是把該套接字標(biāo)記為已關(guān)閉,然后立即返回到調(diào)用進(jìn)程。該套接字描述符不能再由調(diào)用進(jìn)程使用,也就是不能再作為read,write的第一個(gè)參數(shù)。然而TCP將嘗試發(fā)送已排隊(duì)等待發(fā)送到對(duì)端的任何數(shù)據(jù),發(fā)送完畢后再發(fā)送TCP連接終止序列。
close的錯(cuò)誤返回三種情況: - EBADF:表示參數(shù)fd為非法描述符 - EINTR:close調(diào)用被信號(hào)中斷 - EIO:I/O時(shí)出現(xiàn)錯(cuò)誤
上述代碼中close被調(diào)用2次,因?yàn)樽舆M(jìn)程和父進(jìn)程共享了client_fd,其引用計(jì)數(shù)為2,如果子進(jìn)程只調(diào)用一次,則會(huì)導(dǎo)致client_fd的套接字永遠(yuǎn)不能關(guān)閉,導(dǎo)致進(jìn)程描述符耗盡而無法為其他請(qǐng)求提供服務(wù)。并且當(dāng)close調(diào)用時(shí),TCP的讀寫2端都被關(guān)閉。
close在網(wǎng)絡(luò)編程中的局限: - 當(dāng)一個(gè)描述符被共享使用時(shí),調(diào)用close函數(shù)只是將其引用計(jì)數(shù)減一,僅僅在引用計(jì)數(shù)為0的時(shí)候,該套接字才被關(guān)閉。 - close終止讀和寫2個(gè)方向的數(shù)據(jù)傳輸。TCP為全雙工協(xié)議,有時(shí)候當(dāng)我們完成數(shù)據(jù)發(fā)送后,可能需要等待對(duì)端發(fā)送數(shù)據(jù),此時(shí)可以調(diào)用shutdown來實(shí)現(xiàn)此功能。
howto的可選項(xiàng): SHUT_RD:關(guān)閉連接的讀——套接字中不再讀取數(shù)據(jù),而且套接字接受緩沖區(qū)中的數(shù)據(jù)也會(huì)被丟棄。進(jìn)程不能再對(duì)這個(gè)套接字調(diào)用任何讀取函數(shù)。 SHUT_WR:關(guān)閉連接的寫——對(duì)于TCP套接字,這成為半關(guān)閉。當(dāng)前留在套接字發(fā)送緩沖區(qū)中的數(shù)據(jù)將被發(fā)送掉,然后TCP的正常連接終止序列。無論這個(gè)套接字描述符的引用計(jì)數(shù)是否為0,shutdown都會(huì)激發(fā)TCP終止序列,以后進(jìn)程不能再對(duì)這個(gè)套接字調(diào)用任何寫函數(shù)。 SHUT_RDWR:關(guān)閉讀、寫——相當(dāng)于分別調(diào)取了shutdown兩次并傳遞參數(shù)SHUT_RD和SHUT_WR。shutdown使用此選項(xiàng)與close的區(qū)別是,shutdown立馬關(guān)閉套接字的讀寫通道,但是close只會(huì)在引用計(jì)數(shù)為0的情況才關(guān)閉讀寫通道。
主程序
#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讀取數(shù)據(jù)發(fā)送到sockfd并回寫 return 0;}使用close的實(shí)現(xiàn)
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; // 由于中斷引發(fā)的失敗,重試 else { // 其他錯(cuò)誤,退出 perror("select"); return; } } if(FD_ISSET(sockfd,&read_set)){ if( (nread = read(sockfd,recvbuf,sizelf(recvbuf))) == 0){ 在上面的程序中,當(dāng)客戶端從終端讀取到CTRL+D的時(shí)候?qū)⑹筬gets函數(shù)返回0,此時(shí)引發(fā)客戶端主動(dòng)關(guān)閉close,退出客戶端的連接處理函數(shù)后,將立即退出程序(main函數(shù)結(jié)束)。此時(shí)服務(wù)端如果有數(shù)據(jù)正在發(fā)送,則將會(huì)丟失。處理這種問題的方式是,當(dāng)客戶端不再write時(shí),只關(guān)閉TCP的write,但是任然保留其read通道??梢酝ㄟ^shutdown來實(shí)現(xiàn)。使用shutdown的實(shí)現(xiàn)
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; // 由于中斷引發(fā)的失敗,重試 else { // 其他錯(cuò)誤,退出 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中,當(dāng)從終端讀取到EOF時(shí),將調(diào)用shutdown關(guān)閉套接字的寫通道,但是此套接字任然可以從服務(wù)端讀取數(shù)據(jù),保證了數(shù)據(jù)不會(huì)丟失。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注