1. 程式人生 > >跨平臺C++伺服器程式開發 (4)tcp socket狀態圖(server端)

跨平臺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函式關閉,或者程式結束後自動套接字,流程圖如下:

Created with Raphaël 2.1.0socket建立bind繫結listen監聽進入LISTEN狀態close釋放

(2)已連線套接字
客戶端發起連線請求後,accept返回一個新的套接字用於和客戶端通訊,狀態為ESTABLISHED就緒狀態。 主動呼叫close關閉該套接字後,進入TIME_WAIT狀態,流程圖如下:

Created with Raphaël 2.1.0accept返回進入ESTABLISHED狀態close主動釋放進入TIME_WAIT狀態持續一段時間後自動釋放

本文簡單的對伺服器端的socket套接字狀態變化進行了分析,可以看到在程式執行過程中,套接字狀態經常會發生變化。通過分析狀態變化,有助於我們更深入的瞭解tcp協議。