監聽套接字
使用socket接口創建一個套接字,然后bind給套接字綁定地址,最后listen將套接字設置為監聽套接字。監聽套接字以前理解是三元組標識,后面看了netstat,覺得應該是五元組,只不過它這個五元組是{協議,本地ip,本地端口,0.0.0.0,*},也就是對端的地址無論是ip還是port都是任意的,我們既可以三元組也可以五元組,這個不重要
理解 listen 接口
int listen(int sockfd, int backlog);
第一個參數很easy,不就是套接字文件描述符嘛,主要看第二個backlog,這個參數是用來指定監聽套接字的全連接隊列大小的,linux中全連接隊列大小通常是backlog+1
結合代碼理解全連接隊列
#include "tcp_socket.hpp"
int main(int argc, char* argv[]) {
if (argc != 3) {
printf("Usage ./test_server [ip] [port]\n");
return 1;
}
TcpSocket sock;
bool ret = sock.Bind(argv[1], atoi(argv[2]));
if (!ret) {
return 1;
}
ret = sock.Listen(2);
if (!ret) {
return 1;
}
// 客戶端不進行 accept
while (1) {
sleep(1);
}
return 0;
}
#include "tcp_socket.hpp"
int main(int argc, char* argv[]) {
if (argc != 3) {
printf("Usage ./test_client [ip] [port]\n");
return 1;
}
TcpSocket sock;
bool ret = sock.Connect(argv[1], atoi(argv[2]));
if (ret) {
printf("connect ok\n");
} else {
printf("connect failed\n");
}
while (1) {
sleep(1);
}
return 0;
}
服務器端創建好監聽套接字,并設置backlog為2,那全連接隊列大小就是3,后面服務器端一直死循環,不進行accept。前三個客戶端過程都一樣,進行創建套接字,然后connect開始三次握手,發送SYN包,服務器端收到后在監聽套接字的半連接隊列里創建struct request_sock,然后響應SYN+ACK包,客戶端主機收到后,進程從套接字通用等待隊列上喚醒,喚醒后connect執行完畢返回,也會進入死循環,當然這要等進程重新被調度再走這些,在收到包后還要返回ACK包,服務器端收到后會將監聽套接字半連接隊列中的struct request_sock升級成全連接隊列中的struct sock,三個客戶端都是這個式,我們來看第4個客戶端,客戶端同樣connect觸發三次握手發送SYN包,服務器端收到后同樣的在監聽套接字的半連接隊列中創建對應結構,然后響應SYN+ACK包,客戶端收到后,進程被喚醒,然后響應ACK包,服務器端收到后因為全連接隊列滿了,那半連接隊列中的struct request_sock就沒法升級,所以該通信套接字會卡在SYN_RECV,客戶端的通信套接字卻因為收到了SYN+ACK所以是ESTABLISHED狀態
用netstat -anptu查看一下
Proto | Recv-Q | Send-Q | Local Address | Foreign Address | State | PID/Program |
---|---|---|---|---|---|---|
tcp | 3 | 3 | 0.0.0.0:9090 | 0.0.0.0:* | LISTEN | 9084/./test_server |
tcp | 0 | 0 | 127.0.0.1:9090 | 127.0.0.1:48178 | SYN_RECV | - |
tcp | 0 | 0 | 127.0.0.1:9090 | 127.0.0.1:48176 | ESTABLISHED | - |
tcp | 0 | 0 | 127.0.0.1:48178 | 127.0.0.1:9090 | ESTABLISHED | 9140/./test_client |
tcp | 0 | 0 | 127.0.0.1:48174 | 127.0.0.1:9090 | ESTABLISHED | 9087/./test_client |
tcp | 0 | 0 | 127.0.0.1:48176 | 127.0.0.1:9090 | ESTABLISHED | 9088/./test_client |
tcp | 0 | 0 | 127.0.0.1:48172 | 127.0.0.1:9090 | ESTABLISHED | 9086/./test_client |
tcp | 0 | 0 | 127.0.0.1:9090 | 127.0.0.1:48174 | ESTABLISHED | - |
tcp | 0 | 0 | 127.0.0.1:9090 | 127.0.0.1:48172 | ESTABLISHED | - |
客戶端的四個通信套接字全是ESTABLISHED,沒問題,服務器端一個LISTEN套接字,三個ESTABLISHED,一個SYN_RECV,和我們預期一摸一樣
netstat命令
netstat其中一個作用是查看套接字
(1) 查看所有 TCP 連接??
netstat -ant
-a
:顯示所有連接(包括監聽和通信)-n
:顯示數字-t
:僅顯示 TCP 連接
??(2) 查看所有 UDP 連接??
netstat -anu
-u
:僅顯示 UDP 連接
??(3) 查看監聽狀態的端口??
netstat -tuln
-l
:僅顯示監聽(LISTEN)狀態的套接字
??(3) 最常用的配合
?netstat -anptu
?
屬性行中Recv-Q和Send-Q是什么?
Proto | Recv-Q | Send-Q | Local Address | Foreign Address | State | PID/Program |
---|
1. 基礎概念??
字段 | 全稱 | 作用范圍 | 單位 |
---|---|---|---|
Recv-Q | Receive Queue | 接收隊列 | 字節 |
Send-Q | Send Queue | 發送隊列 | 字節 |
??2. 不同狀態下的含義??
??(1) 對于?LISTEN
?狀態的套接字(服務端)??
??Recv-Q??:
已完成三次握手,全連接隊列中等待被?accept()
?系統調用取走的連接數。
示例:Recv-Q: 5
?表示全連接隊列中有 5 個連接等待處理。??Send-Q??:
全連接隊列的最大長度,linux中為backlog+1。
示例:Send-Q: 128
?表示全連接隊列的大小為 128。
??(2) 對于 ESTABLISHED 狀態的套接字(已建立的連接)??
??Recv-Q??:
內核已接收但??應用層未讀取??的數據量(堆積在接收緩沖區)。
正常情況:應為 0 或較小值。若持續增長,可能表示應用層讀取過慢。??Send-Q??:
已發送但??未收到對方 ACK?? 應答的數據量(在發送緩沖區中等待確認)。
正常情況:在網絡無擁塞時應快速清零。若持續增長,可能表示網絡延遲或丟包。
TCP 套接字狀態解析??
netstat
?輸出的?State
?列顯示 TCP 連接狀態,常見狀態如下:
??狀態?? | ??含義?? | ??典型場景?? |
---|---|---|
??LISTEN?? | 服務器正在監聽端口,等待客戶端連接(bind() ?+?listen() ?后) | sshd ?監聽 22 端口 |
??SYN_SENT?? | 客戶端發送 SYN 后等待 SYN+ACK(主動連接中) | curl google.com ?發起連接時 |
??SYN_RECV?? | 服務器收到 SYN 并回復 SYN+ACK,等待 ACK(半連接狀態) | SYN Flood 攻擊時大量出現 |
??ESTABLISHED?? | 連接已建立,數據可正常傳輸 | 正常 HTTP 請求、SSH 會話 |
??FIN_WAIT1?? | 主動關閉方發送 FIN,等待對方 ACK | 客戶端調用?close() ?后 |
??FIN_WAIT2?? | 收到對端 ACK,等待對端 FIN | 四次揮手的中間狀態 |
??TIME_WAIT?? | 主動關閉方收到 FIN 后進入,等待 2MSL(防止舊連接數據干擾新連接) | 服務器重啟后短時間殘留 |
??CLOSE_WAIT?? | 被動關閉方收到 FIN 后進入,等待應用層調用?close() | 應用未正確關閉連接時堆積 |
??LAST_ACK?? | 被動關閉方發送 FIN 后,等待最終 ACK | 即將完全關閉連接 |
??CLOSED?? | 連接已完全關閉(通常不會在?netstat ?中顯示) | - |