應該沒有人會質疑,如今是一個網絡時代了。應該很多程序員在編程中須要考慮多機、局域網、廣域網的各類問題。因此網絡知識也是避免不了學習的。並且筆者一直以爲TCP/IP網絡知識在一個程序員知識體系中必需佔有一席之地的。 程序員
在TCP協議中RST表示復位,用來異常的關閉鏈接,在TCP的設計中它是不可或缺的。發送RST包關閉鏈接時,沒必要等緩衝區的包都發出去,直接就丟棄緩存區的包發送RST包。而接收端收到RST包後,也沒必要發送ACK包來確認。 編程
其實在網絡編程過程當中,各類RST錯誤實際上是比較難排查和找到緣由的。下面我列出幾種會出現RST的狀況。 緩存
服務器程序端口未打開而客戶端來鏈接。這種狀況是最爲常見和好理解的一種了。去telnet一個未打開的TCP的端口可能會出現這種錯誤。這個和操做系統的實現有關。在某些狀況下,操做系統也會徹底不理會這些發到未打開端口請求。 服務器
好比在下面這種狀況下,主機241向主機114發送一個SYN請求,表示想要鏈接主機114的40000端口,可是主機114上根本沒有打開40000這個端口,因而就向主機241發送了一個RST。這種狀況很常見。特別是服務器程序core dump以後重啓以前連續出現RST的狀況會常常發生。 網絡
固然在某些操做系統的主機上,未必是這樣的表現。好比向一臺WINDOWS7的主機發送一個鏈接不存在的端口的請求,這臺主機就不會迴應。 併發
曾經遇到過這樣一個狀況:一個客戶端鏈接服務器,connect返回-1而且error=EINPROGRESS。 直接telnet發現網絡鏈接沒有問題。ping沒有出現丟包。用抓包工具查看,客戶端是在收到服務器發出的SYN以後就莫名其妙的發送了RST。 socket
好比像下面這樣: 工具
有8九、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拒絕進一步發送數據。 spa
關於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斷開了鏈接。和咱們的預期一致。
若是某個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發生,我之後會慢慢收集把更多的例子加入這篇文章。
1 從TCP協議的原理來談談RST攻擊 http://russelltao.iteye.com/blog/1405349
2 TCP客戶-服務器程序例子http://blog.csdn.net/youkuxiaobin/article/details/6917880