close和SO_LINGER
? ? ? ? ? ? ? close函數的作用是關閉套接字,并終止TCP連接。unix網絡編程這本書上是這樣說的,我覺得這個解釋有人會讓人產生誤解。close了某個socket,該socket就真的必須關閉嗎?其實不是,close是將該套接字的引用計數減1,當某個套接字的引用計數為0時,該套接字就被關閉了;不為0,就不會被關閉。多進程并發服務器中會出現這種情況,我開始就誤解了。
? ? ? ? ? ? ? SO_LINGER套接字選項是用來設置close操作的。直接看代碼吧。
?
[mapan@localhost test]$ ls client.cpp makefile server.cpp [mapan@localhost test]$ cat server.cpp #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <errno.h> #include <malloc.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/ioctl.h> #include <stdarg.h> #include <fcntl.h> #include <sys/types.h> #include <sys/wait.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h> #define MAXLINE 4096int main() {int listenfd,acceptfd;socklen_t clilen;struct sockaddr_in cliaddr,servaddr;listenfd=socket(AF_INET,SOCK_STREAM,0);servaddr.sin_family=AF_INET;servaddr.sin_port=htons(8888);servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); bind(listenfd,(struct sockaddr *)&servaddr,sizeof(struct sockaddr_in));listen(listenfd,5);acceptfd=accept(listenfd,(struct sockaddr *)NULL,NULL);char recvbuf[200000];while(1){getchar();read(acceptfd,recvbuf,sizeof(recvbuf)); }getchar();close(listenfd);return 0; } [mapan@localhost test]$ cat client.cpp #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <errno.h> #include <malloc.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/ioctl.h> #include <stdarg.h> #include <fcntl.h> #include <sys/types.h> #include <sys/wait.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h> #define MAXLINE 4096int main() {int sockfd;struct sockaddr_in servaddr;sockfd=socket(AF_INET,SOCK_STREAM,0);bzero(&servaddr,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_port=htons(8888);servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");struct linger so_linger;so_linger.l_onoff=0;//so_linger.l_linger=20;setsockopt(sockfd,SOL_SOCKET,SO_LINGER,&so_linger,sizeof(so_linger)); int ret=connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr));char sendbuf[200000];write(sockfd,sendbuf,sizeof(sendbuf)); close(sockfd);return 0; }[mapan@localhost test]$ cat makefile all:server clientserver.o:server.cppg++ -c server.cpp client.o:client.cppg++ -c client.cpp server:server.og++ -o server server.o client:client.og++ -o client client.oclean:rm -f server client *.o [mapan@localhost test]$?
編譯并運行,客戶端需要打開另一個窗口執行。
?
[mapan@localhost test]$ make g++ -c server.cpp g++ -o server server.o g++ -c client.cpp g++ -o client client.o [mapan@localhost test]$ ./server?
運行客戶端,并查看網絡狀態。
?
[mapan@localhost test]$ ./client [mapan@localhost test]$ [mapan@localhost ~]$ netstat -na | grep 8888 tcp 0 0 127.0.0.1:8888 0.0.0.0:* LISTEN tcp 0 61101 127.0.0.1:35260 127.0.0.1:8888 FIN_WAIT1 tcp 138900 0 127.0.0.1:8888 127.0.0.1:35260 ESTABLISHED [mapan@localhost ~]$?
?
可以看到的是close立即返回了,套接字關閉了,但客戶端發送緩沖區中仍然還有數據。當服務端接收緩沖區有地方后,這些數據將會由系統自動發送給服務端,但是此時客戶端講不會管服務端是否已接收到數據。l_onoff=0,就是關閉這個套接字選項,默認close操作。注意觀察,此時客戶端的狀態時FIN_WAIT1,就是客戶單還沒有把數據發送完畢,所以沒有接到服務端協議棧返回的ACK,所以客戶端為這個狀態。
?
再改變客戶端代碼:
?
#include <netinet/in.h> #include <arpa/inet.h> #include <sys/ioctl.h> #include <stdarg.h> #include <fcntl.h> #include <sys/types.h> #include <sys/wait.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h> #define MAXLINE 4096int main() {int sockfd;struct sockaddr_in servaddr;sockfd=socket(AF_INET,SOCK_STREAM,0);bzero(&servaddr,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_port=htons(8888);servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");struct linger so_linger;so_linger.l_onoff=1;so_linger.l_linger=0;setsockopt(sockfd,SOL_SOCKET,SO_LINGER,&so_linger,sizeof(so_linger)); int ret=connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr));char sendbuf[200000];write(sockfd,sendbuf,sizeof(sendbuf)); getchar();close(sockfd);return 0; }
還是按照上述操作,編譯后啟動服務端和客戶端。此時會發現客戶端卡在getchar()處,查看此時的網絡狀態。
?
?
[mapan@localhost ~]$ netstat -na | grep 8888 tcp 0 0 127.0.0.1:8888 0.0.0.0:* LISTEN tcp 138900 0 127.0.0.1:8888 127.0.0.1:35262 ESTABLISHED tcp 0 61100 127.0.0.1:35262 127.0.0.1:8888 ESTABLISHED [mapan@localhost ~]$?
?
?
客戶端的發送緩沖區中還沒有數據發送出去,如果是我們說的第一種情況,在客戶端按下回車鍵之后,客戶端應該FIN_WAIT1狀態。好,我們在客戶端按下回車鍵后看網絡狀態。
?
[mapan@localhost ~]$ netstat -na | grep 8888 tcp 0 0 127.0.0.1:8888 0.0.0.0:* LISTEN [mapan@localhost ~]$?
?
看,連接直接斷開了。說明close調用后,會丟棄發送緩沖區的內存,并發送一個RST給服務端,從而斷開連接,這也避免了time_wait的狀態。這也是將?so_linger.l_onoff=1,so_linger.l_linger=0的close效果。
?
在看客戶端代碼:
?
[mapan@localhost test]$ cat client.cpp #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <errno.h> #include <malloc.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/ioctl.h> #include <stdarg.h> #include <fcntl.h> #include <sys/types.h> #include <sys/wait.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h> #define MAXLINE 4096int main() {int sockfd;struct sockaddr_in servaddr;sockfd=socket(AF_INET,SOCK_STREAM,0);bzero(&servaddr,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_port=htons(8888);servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");struct linger so_linger;so_linger.l_onoff=1;so_linger.l_linger=10;setsockopt(sockfd,SOL_SOCKET,SO_LINGER,&so_linger,sizeof(so_linger)); int ret=connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr));char sendbuf[200000];write(sockfd,sendbuf,sizeof(sendbuf)); //getchar();close(sockfd);return 0; }
還是重復上述操作,編譯運行查看網絡狀態,此時我將getchar()注釋掉了。你會看到客戶端會卡在那里,其原因是調用了close函數,但是它不會馬上返回,close等待的時間是我們設置的超時時間。看此時的網絡狀態。
?
?
[mapan@localhost ~]$ netstat -na | grep 8888 tcp 0 0 127.0.0.1:8888 0.0.0.0:* LISTEN tcp 0 61101 127.0.0.1:35266 127.0.0.1:8888 FIN_WAIT1 tcp 138900 0 127.0.0.1:8888 127.0.0.1:35266 ESTABLISHED [mapan@localhost ~]$?
?
?
當客戶端發送緩沖區中的數據全部發送到服務端的協議棧,并且接收到了服務端的ACK,那么此時close就會返回,前提是在我們設置的超時時間之內。過了超時時間,close也會返回,那就和我們說的第一種情況一樣了。
網絡問題本應該用tcpdump抓包來看效果的,但是很遺憾,我正在測試的linux上沒有root權限。
?
?
參考資料:unix網絡編程卷一
?
?
?
?
?
?
?
?
?
?
總結
以上是生活随笔為你收集整理的close和SO_LINGER的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: SO_SNDTIMEO和SO_RCVTI
- 下一篇: TCP_DEFER_ACCEPT