1. 程式人生 > >TCP/IP詳解--接收RST迴應的幾種情況

TCP/IP詳解--接收RST迴應的幾種情況

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!

               

出現RST分節的情況可以在三種情況下發生,在連線建立時、在中間傳送資料時、在連線關閉時。

連線建立時出現RST迴應的情況有:傳送一個不存在的埠,想一個Listen的套接字或者埠上傳送資料分節(不是建立連線的分節)

傳送資料時出現RST分節的情況有:資料出現錯誤、不是按照seq要求來發送資料時

連線終止時傳送RST分節的情況有:如果在關閉的時候設定了套接字選項,l_linger,那麼在關閉的時候可能會出現RST分節

在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.cint 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.cint 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斷開了連線。和我們的預期一致。

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。

5、異常終止一個連線

終止一個連線的正常方式是一方傳送F I N。有時這也稱為有序釋放(orderly release),因為在所有排隊資料都已傳送之後才傳送F I N,正常情況下沒有任何資料丟失。但也有可能傳送一個復位報文段(RST)而不是F I N來中途釋放一個連線。有時稱這為異常釋放

異常終止一個連線對應用程式來說有兩個優點:

1)丟棄任何待發資料並立即傳送復位報文段;

2R S T的接收方會區分另一端執行的是異常關閉還是正常關閉。

應用程式使用的A P I必須提供產生異常關閉而不是正常關閉的手段

6、向處於LISTEN的埠傳送資料

如果殺掉伺服器端處理客戶端的子程序,程序退出後,關閉它開啟的所有檔案描述符,此時,當伺服器TCP接收到來自此客戶端的資料時,由於先前開啟的那個套接字介面的程序已終止,所以以RST響應。

父程序:192.168.0.80:5800*.*LISTENING

子程序:192.168.0.80:5800192.201.0.75:6500ESTALBING

殺掉子程序後,客戶端向伺服器端(192.168.0.80:5800 192.201.0.75:6500)傳送資料,則tcp_input呼叫in_pcblookup查詢對應的協議控制塊時,會找到伺服器端的父程序的協議控制塊,tcp_input繼續處理,在TCP_LISTEN狀態收到資料的處理方式如下:

n 如果報文段中帶有RST標誌,則丟棄它。

n 如果帶有ACK標誌,則丟棄並以RST作為響應。(本例正是這種情況)

n 如果沒帶有SYN,也沒有ACK標誌,則丟棄它。

7、連線沒有服務程序的埠號

在客戶端連線伺服器時,如果該伺服器主機在制定的埠上沒有程序在等待與之連線(例如伺服器程序也許沒有執行),那麼客戶端接收到RST迴應,客戶一接收到RST就馬上返回ECONNERFUSED錯誤。

           

給我老師的人工智慧教程打call!http://blog.csdn.net/jiangjunshow

這裡寫圖片描述