跨平臺C++伺服器程式開發 (4)tcp socket狀態圖(server端)
套接字狀態
在上一節中,介紹了檔案描述符的概念,我們可以看到socket套接字與磁碟檔案的讀寫方法很相似,但套接字比普通的檔案描述符多了一種狀態,每個開啟的套接字都對應一種狀態,Windows和Linux都可以使用netstat
命令檢視。
通過觀察套接字狀態,可以檢查伺服器程式是否正常工作(監聽埠是否開啟),排查網路故障(比如為什麼連不上伺服器),優化程式碼(伺服器卡頓,佔用大量資源)等。
測試程式
通過一小段測試程式(socket_server.cpp)來例項分析套接字的狀態變化。
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
printf("step1: socket()\n");
int servsock = socket(AF_INET, SOCK_STREAM, 0);
getchar();
printf("step2: bind()\n");
struct sockaddr_in servaddr;
memset(&servaddr, 0 , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(8828);
bind(servsock, (struct sockaddr*)&servaddr, sizeof(servaddr));
getchar();
printf("step3: listen()\n");
listen(servsock, 5);
getchar();
printf ("step4: accept()\n");
struct sockaddr_in clientaddr;
socklen_t addr_len = sizeof(clientaddr);
int clientsock = accept(servsock, (struct sockaddr*)&clientaddr, &addr_len);
getchar();
printf("step5: close client socket()\n");
close(clientsock);
getchar();
printf("step6: close server socket()\n");
close(servsock);
return 0;
}
(1)編譯:g++ socket_server.cpp
,生成一個名為a.out
的程式
(2)執行: ./a.out
(3)呼叫socket
函式建立一個套接字,輸出如下:
step1: socket()
這裡我們使用getchar
函式獲取輸入來暫停程式,從而分析一些資料。
使用命令ps -ef | grep a.out
查詢程序id,本次測試a.out程式的程序id為6789。
進入/proc/6789/fd
目錄,然後執行ll
命令,檢視該程序已開啟的檔案描述符,如下圖:
可以看到建立了一個fd為3的socket套接字。
(4)在a.out
程式暫停介面按一下回車鍵,開始執行step 2,這裡呼叫了bind
函式將剛才建立的套接字與本機的8828埠繫結。輸出如下:
step1: socket()
step2: bind()
(5)再按一下回車鍵,執行step3,這裡呼叫了listen
函式將該套接字作為監聽套接字,監聽埠為繫結的8828埠。輸出如下
step1: socket()
step2: bind()
step3: listen()
執行命令:netstat -ant | grep 'State\ |8828'
該命令前半段用於檢視tcp套接字狀態,後半段grep命令用於顯示包含State
或者8828
的資料行,方便觀察,命令結果如下:
可以看到套接字狀態為LISTEN
,表明該套接字處於監聽狀態。
(6)繼續按一下回車鍵,執行step4,呼叫accept
函式來等待客戶端的連線請求,若沒有客戶端發起連線請求,該函式將一直阻塞下去。
此時我們通過使用telnet
命令來作為客戶端連線。
執行命令:telnet 127.0.0.1 8828
該命令表示遠端連線ip為127.0.0.1,埠為8828的服務,執行結果如圖:
成功建立連線,然後再次檢視netstat
命令輸出,如圖:
對比上次netstat結果,可以發現多了兩個套接字,並且狀態為ESTABLISHED
就緒狀態,這表示兩個套接字都處於就緒狀態,可以正常的讀寫網路資料。
(7)繼續我們的回車鍵,按一下,進入step5,關閉剛建立成功的已連線套接字,該套接字用於與對應的客戶端套接字通訊,而監聽套接字繼續監聽。
關閉已連線套接字後,telnet
程式也自動退出,提示
Connection closed by foreign host.
再次檢視netstat命令結果,如圖:
可以看到3個套接字變為2個,並且有一個變為TIME_WAIT
狀態。
(8)最後一下回車鍵,關閉監聽套接字,程式執行結束。
檢視netstat結果如圖:
只剩下一個TIME_WAIT
狀態套接字。
結果分析
本測試程式執行過程中共建立了兩個套接字,一個監聽套接字,一個是accept
返回的已連線套接字。
(1)監聽套接字
呼叫listen
函式後,套接字進入LISTEN
監聽狀態,手動呼叫close
函式關閉,或者程式結束後自動套接字,流程圖如下:
(2)已連線套接字
客戶端發起連線請求後,accept
返回一個新的套接字用於和客戶端通訊,狀態為ESTABLISHED
就緒狀態。 主動呼叫close
關閉該套接字後,進入TIME_WAIT
狀態,流程圖如下:
本文簡單的對伺服器端的socket套接字狀態變化進行了分析,可以看到在程式執行過程中,套接字狀態經常會發生變化。通過分析狀態變化,有助於我們更深入的瞭解tcp協議。