tft每日頭條

 > 圖文

 > tcp中常用的應用層協議有

tcp中常用的應用層協議有

圖文 更新时间:2024-06-20 03:27:40

現在是一個網絡時代了。應該不少程序員在編程中需要考慮多機、局域網、廣域網的各種問題。所以網絡知識也是避免不了學習的。而且筆者一直覺得 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 的情況會經常發生。

tcp中常用的應用層協議有(幾種TCP連接中出現)1

當然在某些操作系統的主機上,未必是這樣的表現。比如向一台 WINDOWS7 的主機發送一個連接不存在的端口的請求,這台主機就不會回應。

2 請求超時

曾經遇到過這樣一個情況:一個客戶端連接服務器,connect 返回 - 1 并且 error=EINPROGRESS。 直接 telnet 發現網絡連接沒有問題。ping 沒有出現丢包。用抓包工具查看,客戶端是在收到服務器發出的 SYN 之後就莫名其妙的發送了 RST。

比如像下面這樣:

tcp中常用的應用層協議有(幾種TCP連接中出現)2

有 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中常用的應用層協議有(幾種TCP連接中出現)3

前三行就是 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等),免費分享

tcp中常用的應用層協議有(幾種TCP連接中出現)4

4 在一個已關閉的 socket 上收到數據

如果某個 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中常用的應用層協議有(幾種TCP連接中出現)5

總結

總結,本文講了幾種 TCP 連接中出現 RST 的情況。實際上肯定還有無數種的 RST 發生,我以後會慢慢收集把更多的例子加入這篇文章。

,

更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!

查看全部

相关圖文资讯推荐

热门圖文资讯推荐

网友关注

Copyright 2023-2024 - www.tftnews.com All Rights Reserved