[英]Why app can accept and read/write TCP connection in CLOSE_WAIT state (Linux)?
我的假設是當 TCP 連接處於 ESTABLISHED 狀態時,服務器可以從/向套接字讀/寫。 但是我看到當 TCP 連接處於 CLOSE_WAIT 狀態時,服務器實際上可以讀取和寫入套接字。 當客戶端關閉了它的連接,但服務器還沒有檢測/處理流結束的情況時,就會發生這種情況。
例如。 同步的、阻塞的單線程服務器:
#include <unistd.h>
#include <netdb.h>
#include <signal.h>
int main (void)
{
signal(SIGPIPE, SIG_IGN); // ignore SIGPIPE from last send()
int listen_sock = socket(PF_INET, SOCK_STREAM, 0);
setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int));
struct sockaddr_in addr = {.sin_family = AF_INET, .sin_port = htons(8000), .sin_addr.s_addr = htonl(INADDR_ANY)};
bind(listen_sock, (struct sockaddr *) &addr, sizeof(addr));
listen(listen_sock, 10);
while (1) {
int sock = accept(listen_sock, 0, 0);
char buffer[1024];
recv(sock, buffer, sizeof(buffer), 0);
sleep(1);
send(sock, buffer, 10, 0);
send(sock, buffer, 10, 0);
sleep(1);
send(sock, buffer, 10, 0);
close(sock);
}
}
編譯並運行:
gcc server_short.c -o server_short && strace ./server_short
觀看同一主機上的連接:
watch -n0.1 'sudo ss -lnt | grep 8000; sudo netstat -tpvn | grep 8000'
在另一台主機上運行短期客戶端:
seq 20 | xargs -P100 -n1 bash -c 'echo -n "0123456789ABCDEF" | telnet $SERVER_IP 8000'
然后我看到連接處於 CLOSE_WAIT 和 SYN_RECV 狀態,SYN_RECV 將變成 CLOSE_WAIT,因為排隊的連接將由服務器處理:
LISTEN 11 10 0.0.0.0:8000 0.0.0.0:*
tcp 17 0 server_IP:8000 client_IP:33016 CLOSE_WAIT -
tcp 17 0 server_IP:8000 client_IP:33030 CLOSE_WAIT -
tcp 17 0 server_IP:8000 client_IP:33020 CLOSE_WAIT -
tcp 17 0 server_IP:8000 client_IP:33028 CLOSE_WAIT -
tcp 0 0 server_IP:8000 client_IP:33012 CLOSE_WAIT 21282/./server
tcp 0 0 server_IP:8000 client_IP:33046 SYN_RECV -
tcp 0 0 server_IP:8000 client_IP:33040 SYN_RECV -
tcp 0 0 server_IP:8000 client_IP:33044 SYN_RECV -
tcp 0 0 server_IP:8000 client_IP:33036 SYN_RECV -
tcp 17 0 server_IP:8000 client_IP:33018 CLOSE_WAIT -
tcp 0 0 server_IP:8000 client_IP:33048 SYN_RECV -
tcp 17 0 server_IP:8000 client_IP:33034 CLOSE_WAIT -
tcp 17 0 server_IP:8000 client_IP:33022 CLOSE_WAIT -
tcp 0 0 server_IP:8000 client_IP:33042 SYN_RECV -
tcp 0 0 server_IP:8000 client_IP:33038 SYN_RECV -
tcp 17 0 server_IP:8000 client_IP:33032 CLOSE_WAIT -
tcp 17 0 server_IP:8000 client_IP:33026 CLOSE_WAIT -
tcp 17 0 server_IP:8000 client_IP:33014 CLOSE_WAIT -
tcp 17 0 server_IP:8000 client_IP:33024 CLOSE_WAIT -
然后服務器一一處理這些 CLOSE_WAIT 連接:
socket(AF_INET, SOCK_STREAM, IPPROTO_IP) = 3
setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
bind(3, {sa_family=AF_INET, sin_port=htons(8000), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
listen(3, 10) = 0
accept(3, NULL, NULL) = 4
recvfrom(4, "0123456789ABCDEF", 1024, 0, NULL, NULL) = 16
nanosleep({tv_sec=1, tv_nsec=0}, 0x7ffdb9a2d0e0) = 0
sendto(4, "0123456789", 10, 0, NULL, 0) = 10
sendto(4, "0123456789", 10, 0, NULL, 0) = 10
nanosleep({tv_sec=1, tv_nsec=0}, 0x7ffdb9a2d0e0) = 0
sendto(4, "0123456789", 10, 0, NULL, 0) = -1 EPIPE (Broken pipe)
--- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=22658, si_uid=1001} ---
close(4) = 0
accept(3, NULL, NULL) = 4
recvfrom(4, "0123456789ABCDEF", 1024, 0, NULL, NULL) = 16
nanosleep({tv_sec=1, tv_nsec=0}, 0x7ffdb9a2d0e0) = 0
sendto(4, "0123456789", 10, 0, NULL, 0) = 10
sendto(4, "0123456789", 10, 0, NULL, 0) = 10
nanosleep({tv_sec=1, tv_nsec=0}, 0x7ffdb9a2d0e0) = 0
sendto(4, "0123456789", 10, 0, NULL, 0) = -1 EPIPE (Broken pipe)
--- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=22658, si_uid=1001} ---
close(4) = 0
... AND SO ON
因此,服務器可以通過調用 read 或通過調用 write 檢測管道損壞,在檢測流結束之前成功接受、讀取請求、處理它並發送響應。
我知道這個服務器代碼並不理想,但即使你讓它異步(使用 select、epoll、boost::asio 等)你也將接受 CLOSE_WAIT 連接,在檢測到連接之前讀取傳入請求和啟動請求處理沒活着。 雖然服務器端知道連接已被客戶端關閉,然后才被服務器接受。
所以問題:
TCP 堆棧獨立於您的用戶空間應用程序。 當您將套接字設置為LISTEN
,TCP 堆棧已經接受傳入的連接(傳入的SYN
用SYN|ACK
響應)並且還將傳入的數據包存儲到接收緩沖區中。
當您程序調用accept()
它要么阻塞並等待某人連接到套接字,要么返回從 TCP 堆棧(或 TCP 狀態機)的角度來看已經存在的已建立的套接字。
有完全合法的用例,客戶端打開連接,傳遞一些數據並立即關閉連接。 然后,服務器可以花時間從網絡緩沖區中檢索該數據並處理請求(例如,如果該請求不需要對客戶端的回答)。
CLOSE_WAIT
本質上是“TCP 堆棧也在等待本地應用程序關閉套接字”的狀態,因此您假設只有在狀態為ESTABLISHED
時才能read()
是錯誤的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.