正常狀況tcp四層握手關閉鏈接,rst基本都是異常狀況,整理以下:程序員
1. GFW編程
2. 對方端口未打開,發生在鏈接創建緩存
若是對方sync_backlog滿了的話,sync簡單被丟棄,表現爲超時,而不會rst服務器
3. close Socket 時recv buffer 不爲空網絡
例如,客戶端發了兩個請求,服務器只從buffer 讀取第一個請求處理完就關閉鏈接,tcp層認爲數據沒有正確提交到應用,使用rst關閉鏈接。併發
3. 移動鏈路socket
移動網絡下,國內是有5分鐘後就回收信令,也就是IM產品,若是心跳>5分鐘後服務器再給客戶端發消息,就會收到rst。也要查移動網絡下IM 保持<5min 心跳。tcp
4. 負載等設備工具
負載設備須要維護鏈接轉發策略,長時間無流量,鏈接也會被清除,並且不少都不告訴兩層機器,新的包過來時才通告rst。學習
Apple push 服務也有這個問題,並且是不可預期的偶發性鏈接被rst;rst 前第一個消息write 是成功的,而第二條寫纔會告訴你鏈接被重置,
曾經被它折騰沒轍,所以打開每2秒一次tcp keepalive,固定5分鐘tcp鏈接回收,並且發現鏈接出錯時,重發以前10s內消息。
5. SO_LINGER 應用強制使用rst 關閉
該選項會直接丟棄未發送完畢的send buffer,可能形成業務錯誤,慎用; 固然內網服務間http client 在收到應該時主動關閉,使用改選項,會節省資源。
好像曾經測試過haproxy 某種配置下,會使用rst關閉鏈接,少了網絡交互並且沒有TIME_WAIT 問題
6. 超過超時重傳次數、網絡暫時不可達
7. TIME_WAIT 狀態
tw_recycle = 1 時,sync timestamps 比上次小時,會被rst
7. 設置 connect_timeout
應用設置了鏈接超時,sync 未完成時超時了,會發送rst終止鏈接。
8. 非正常包
鏈接已經關閉,seq 不正確等
9. keepalive 超時
公網服務tcp keepalive 最好別打開;移動網絡下會增長網絡負擔,切容易掉線;非移動網絡核心ISP設備也不必定都支持keepalive,曾經也發現過廣州那邊有個核心節點就不支持。
10. 待整理
應該沒有人會質疑,如今是一個網絡時代了。應該很多程序員在編程中須要考慮多機、局域網、廣域網的各類問題。因此網絡知識也是避免不了學習的。並且筆者一直以爲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。
好比像下面這樣:
有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拒絕進一步發送數據。
關於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