窺探 Socket 監(jiān)聽(tīng)的秘密
本文轉(zhuǎn)載自微信公眾號(hào)「盼盼編程」,作者盼盼編程。轉(zhuǎn)載本文請(qǐng)聯(lián)系盼盼編程公眾號(hào)。
socket用listen函數(shù)監(jiān)聽(tīng),listen從英語(yǔ)上理解就是一個(gè)"聽(tīng)"函數(shù),實(shí)際上它也就是這個(gè)意思。
我們來(lái)看unix網(wǎng)絡(luò)編程這本書(shū)是怎樣對(duì)它的解釋?zhuān)簂isten函數(shù)把一個(gè)未連接的套接字轉(zhuǎn)換成一個(gè)被動(dòng)套接字,指示內(nèi)核應(yīng)該接受指向該套接字的鏈接請(qǐng)求。
該函數(shù)有2個(gè)參數(shù),第一個(gè)我就不說(shuō)了,第二參數(shù)規(guī)定了內(nèi)核為相應(yīng)套接字排隊(duì)的最大連接個(gè)數(shù)。只看這些理論搞的人稀里糊涂,我們還是來(lái)測(cè)一下。
- [mapan@localhost test]$ ls
- client.cpp makefile server.cpp
- [mapan@localhost test]$
- [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 4096
- void main()
- {
- int listenfd,connfd;
- socklen_t clilen;
- struct sockaddr_in cliaddr,servaddr;
- listenfd=socket(AF_INET,SOCK_STREAM,0);
- bzero(&servaddr,sizeof(servaddr));
- servaddr.sin_family=AF_INET;
- servaddr.sin_addr.s_addr=INADDR_ANY;
- servaddr.sin_port=htons(8888);
- bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
- listen(listenfd,1);
- getchar();
- connfd=accept(listenfd,(struct sockaddr *)&cliaddr,&clilen);
- close(connfd);
- close(listenfd);
- }
- [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 4096
- void 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");
- int ret=connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
- getchar();
- close(sockfd);
- }
- [mapan@localhost test]$ cat makefile
- all:server client
- server.o:server.cpp
- g++ -c server.cpp
- client.o:client.cpp
- g++ -c client.cpp
- server:server.o
- g++ -o server server.o
- client:client.o
- g++ -o client client.o
- clean:
- rm -f server client *.o
- [mapan@localhost test]$
請(qǐng)注意上面的服務(wù)端中,我是沒(méi)有調(diào)用accept函數(shù)的,直接調(diào)用getchar()了,跑起來(lái)。
- [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
- 服務(wù)度開(kāi)啟,然后新打開(kāi)一個(gè)窗口開(kāi)啟客戶(hù)端。
- [mapan@localhost TCP]$ cd ../test/
- [mapan@localhost test]$
- [mapan@localhost test]$ ./client 127.0.0.1
查看網(wǎng)絡(luò):
- [mapan@localhost test]$ netstat -na | grep 8888
- tcp 0 0 0.0.0.0:8888 0.0.0.0:* LISTEN
- tcp 0 0 127.0.0.1:34846 127.0.0.1:8888 ESTABLISHED
- tcp 0 0 127.0.0.1:8888 127.0.0.1:34846 ESTABLISHED
- [mapan@localhost test]$
看,已經(jīng)建立起一個(gè)連接了。但是我們沒(méi)有調(diào)用accept函數(shù),連接還是建立起來(lái)了,這說(shuō)說(shuō)明accept函數(shù)和TCP三次握手沒(méi)啥關(guān)系,這也是一個(gè)知識(shí)盲點(diǎn)。好,在開(kāi)啟一個(gè)新窗口運(yùn)行客戶(hù)端,查看網(wǎng)絡(luò)狀態(tài)。(新開(kāi)窗口運(yùn)行客戶(hù)端同上,這里就不用代碼演示了)
- [mapan@localhost test]$ netstat -na | grep 8888
- tcp 0 0 0.0.0.0:8888 0.0.0.0:* LISTEN
- tcp 0 0 127.0.0.1:34846 127.0.0.1:8888 ESTABLISHED
- tcp 0 0 127.0.0.1:34848 127.0.0.1:8888 ESTABLISHED
- tcp 0 0 127.0.0.1:8888 127.0.0.1:34846 ESTABLISHED
- tcp 0 0 127.0.0.1:8888 127.0.0.1:34848 ESTABLISHED
看,又建立起一個(gè)連接。在運(yùn)行一個(gè)客戶(hù)端,看網(wǎng)絡(luò)狀態(tài)。
- [mapan@localhost test]$ netstat -na | grep 8888
- tcp 0 0 0.0.0.0:8888 0.0.0.0:* LISTEN
- tcp 0 0 127.0.0.1:8888 127.0.0.1:34850 SYN_RECV
- tcp 0 0 127.0.0.1:34846 127.0.0.1:8888 ESTABLISHED
- tcp 0 0 127.0.0.1:34848 127.0.0.1:8888 ESTABLISHED
- tcp 0 0 127.0.0.1:8888 127.0.0.1:34846 ESTABLISHED
- tcp 0 0 127.0.0.1:8888 127.0.0.1:34848 ESTABLISHED
- tcp 0 0 127.0.0.1:34850 127.0.0.1:8888 ESTABLISHED
當(dāng)?shù)谌齻€(gè)客戶(hù)端連接進(jìn)來(lái)的時(shí)候,出現(xiàn)了一個(gè)SYN_RECV,這標(biāo)明第三個(gè)客戶(hù)端沒(méi)有與服務(wù)端建立連接。
我們listen函數(shù)設(shè)置的監(jiān)聽(tīng)隊(duì)列為1,那么監(jiān)聽(tīng)隊(duì)列塞了2個(gè)之后就沒(méi)有往里面塞了。這下大概懂了listen函數(shù)第二個(gè)參數(shù)的意義了吧,當(dāng)參數(shù)為1的時(shí)候只能監(jiān)聽(tīng)2個(gè)套接字,這應(yīng)該是從0開(kāi)始數(shù)的。
為什么是大概呢?其實(shí)unix網(wǎng)絡(luò)編程上是這樣說(shuō)的:listen函數(shù)的第二個(gè)參數(shù)是ESTABLISHED和SYN_RECV之和,只是在監(jiān)聽(tīng)隊(duì)列沒(méi)有滿(mǎn)的情況下,SYN_RECV狀態(tài)不容易重現(xiàn)。這時(shí)候在服務(wù)度輸入一個(gè)字符會(huì)有啥效果呢?
答案告訴你,就是那個(gè)SYN_RECV狀態(tài)變成ESTABLISHED了,這也是 accept函數(shù)的作用。accept函數(shù)會(huì)將已完成連接隊(duì)列中的對(duì)頭項(xiàng)返回給進(jìn)程,所以SYN_RECV變成ESTABLISHED了。這個(gè)現(xiàn)象留給大家去實(shí)踐一下吧,只有自己實(shí)踐出來(lái)的東西才是自己的。