現在是一個網絡時代了。應該不少程序員在編程中需要考慮多機、局域網、廣域網的各種問題。所以網絡知識也是避免不了學習的。而且筆者一直覺得 TCP/IP 網絡知識在一個程序員知識體系中必需占有一席之地的。
在 TCP 協議中 RST 表示複位,用來異常的關閉連接,在 TCP 的設計中它是不可或缺的。發送 RST 包關閉連接時,不必等緩沖區的包都發出去,直接就丢棄緩存區的包發送 RST 包。而接收端收到 RST 包後,也不必發送 ACK 包來确認。
其實在網絡編程過程中,各種 RST 錯誤其實是比較難排查和找到原因的。下面我列出幾種會出現 RST 的情況。
1 端口未打開服務器程序端口未打開而客戶端來連接。這種情況是最為常見和好理解的一種了。去 telnet 一個未打開的 TCP 的端口可能會出現這種錯誤。這個和操作系統的實現有關。在某些情況下,操作系統也會完全不理會這些發到未打開端口請求。
比如在下面這種情況下,主機 241 向主機 114 發送一個 SYN 請求,表示想要連接主機 114 的 40000 端口,但是主機 114 上根本沒有打開 40000 這個端口,于是就向主機 241 發送了一個 RST。這種情況很常見。特别是服務器程序 core dump 之後重啟之前連續出現 RST 的情況會經常發生。
當然在某些操作系統的主機上,未必是這樣的表現。比如向一台 WINDOWS7 的主機發送一個連接不存在的端口的請求,這台主機就不會回應。
2 請求超時曾經遇到過這樣一個情況:一個客戶端連接服務器,connect 返回 - 1 并且 error=EINPROGRESS。 直接 telnet 發現網絡連接沒有問題。ping 沒有出現丢包。用抓包工具查看,客戶端是在收到服務器發出的 SYN 之後就莫名其妙的發送了 RST。
比如像下面這樣:
有 89、27 兩台主機。主機 89 向主機 27 發送了一個 SYN,表示希望連接 8888 端口,主機 27 回應了主機 89 一個 SYN 表示可以連接。但是主機 27 卻很不友好,莫名其妙的發送了一個 RST 表示我不想連接你了。
後來經過排查發現,在主機 89 上的程序在建立了 socket 之後,用 setsockopt 的 SO_RCVTIMEO 選項設置了 recv 的超時時間為 100ms。而我們看上面的抓包結果表示,從主機 89 發出 SYN 到接收 SYN 的時間多達 110ms。(從 15:01:27.799961 到 15:01:27.961886, 小數點之後的單位是微秒)。因此主機 89 上的程序認為接收超時,所以發送了 RST 拒絕進一步發送數據。
3 提前關閉關于 TCP,我想我們在教科書裡都讀到過一句話,'TCP 是一種可靠的連接 '。 而這可靠有這樣一種含義,那就是操作系統接收到的來自 TCP 連接中的每一個字節,我都會讓應用程序接收到。如果應用程序不接收怎麼辦?你猜對了,RST。
看兩段程序:
//server.c
int main(int argc, char** argv)
{
int listen_fd, real_fd;
struct sockaddr_in listen_addr, client_addr;
socklen_t len = sizeof(struct sockaddr_in);
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if(listen_fd == -1)
{
perror("socket failed ");
return -1;
}
bzero(&listen_addr,sizeof(listen_addr));
listen_addr.sin_family = AF_INET;
listen_addr.sin_addr.s_addr = htonl(INADDR_ANY);
listen_addr.sin_port = htons(SERV_PORT);
bind(listen_fd,(struct sockaddr *)&listen_addr, len);
listen(listen_fd, WAIT_COUNT);
while(1)
{
real_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &len);
if(real_fd == -1)
{
perror("accpet fail ");
return -1;
}
if(fork() == 0)
{
close(listen_fd);
char pcContent[4096];
read(real_fd,pcContent,4096);
close(real_fd);
exit(0);
}
close(real_fd);
}
return 0;
}
這一段是 server 的最簡單的代碼。邏輯很簡單,監聽一個 TCP 端口然後當有客戶端來連接的時候 fork 一個子進程來處理。注意看的是這一段 fork 裡面的處理:
char pcContent[4096];
read(real_fd,pcContent,4096);
close(real_fd);
每次隻是讀 socket 的前 4096 個字節,然後就關閉掉連接。
然後再看一下 client 的代碼:
//client.c
int main(int argc, char** argv)
{
int send_sk;
struct sockaddr_in s_addr;
socklen_t len = sizeof(s_addr);
send_sk = socket(AF_INET, SOCK_STREAM, 0);
if(send_sk == -1)
{
perror("socket failed ");
return -1;
}
bzero(&s_addr, sizeof(s_addr));
s_addr.sin_family = AF_INET;
inet_pton(AF_INET,SER_IP,&s_addr.sin_addr);
s_addr.sin_port = htons(SER_PORT);
if(connect(send_sk,(struct sockaddr*)&s_addr,len) == -1)
{
perror("connect fail ");
return -1;
}
char pcContent[5000]={0};
write(send_sk,pcContent,5000);
sleep(1);
close(send_sk);
}
這段代碼更簡單,就是打開一個 socket 然後連接一個服務器并發送 5000 個字節。剛才我們看服務器的代碼,每次隻接收 4096 個字節,那麼就是說客戶端發送的剩下的 4 個字節服務端的應用程序沒有接收到,服務器端的 socket 就被關閉掉,這種情況下會發生什麼狀況呢,還是抓包看一看。
前三行就是 TCP 的 3 次握手,從第四行開始看,客戶端的 49660 端口向服務器的 9877 端口發送了 5000 個字節的數據,然後服務器端發送了一個 ACK 進行了确認,緊接着服務器向客戶端發送了一個 RST 斷開了連接。和我們的預期一緻。
相關視頻推薦
tcp/ip accept,11個狀态,細枝末節的秘密,還有哪些你不知道?
100行代碼實現tcp/ip協議棧,自行準備好Linux系統
學習地址:C/C Linux服務器開發/後台架構師【零聲教育】-學習視頻教程-騰訊課堂
需要C/C Linux服務器架構師學習資料加qun812855908獲取(資料包括C/C ,Linux,golang技術,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK,ffmpeg等),免費分享
如果某個 socket 已經關閉,但依然收到數據也會産生 RST。
代碼如下:
客戶端:
int main(int argc, char** argv)
{
int send_sk;
struct sockaddr_in s_addr;
socklen_t len = sizeof(s_addr);
send_sk = socket(AF_INET, SOCK_STREAM, 0);
if(send_sk == -1)
{
perror("socket failed ");
return -1;
}
bzero(&s_addr, sizeof(s_addr));
s_addr.sin_family = AF_INET;
inet_pton(AF_INET,SER_IP,&s_addr.sin_addr);
s_addr.sin_port = htons(SER_PORT);
if(connect(send_sk,(struct sockaddr*)&s_addr,len) == -1)
{
perror("connect fail ");
return -1;
}
char pcContent[4096]={0};
write(send_sk,pcContent,4096);
sleep(1);
write(send_sk,pcContent,4096);
close(send_sk);
}
服務端:
int main(int argc, char** argv)
{
int listen_fd, real_fd;
struct sockaddr_in listen_addr, client_addr;
socklen_t len = sizeof(struct sockaddr_in);
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if(listen_fd == -1)
{
perror("socket failed ");
return -1;
}
bzero(&listen_addr,sizeof(listen_addr));
listen_addr.sin_family = AF_INET;
listen_addr.sin_addr.s_addr = htonl(INADDR_ANY);
listen_addr.sin_port = htons(SERV_PORT);
bind(listen_fd,(struct sockaddr *)&listen_addr, len);
listen(listen_fd, WAIT_COUNT);
while(1)
{
real_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &len);
if(real_fd == -1)
{
perror("accpet fail ");
return -1;
}
if(fork() == 0)
{
close(listen_fd);
char pcContent[4096];
read(real_fd,pcContent,4096);
close(real_fd);
exit(0);
}
close(real_fd);
}
return 0;
}
客戶端在服務端已經關閉掉 socket 之後,仍然在發送數據。這時服務端會産生 RST。
總結,本文講了幾種 TCP 連接中出現 RST 的情況。實際上肯定還有無數種的 RST 發生,我以後會慢慢收集把更多的例子加入這篇文章。
,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!