RST
表示鏈接重置,用於關閉那些已經沒有必要繼續存在的鏈接。通常狀況下表示異常關閉鏈接,區別與四次分手正常關閉鏈接。node
產生RST
的三個條件是:後端
SYN
到達,然而在該端口上並無正在監聽的服務器;下面的幾種場景,都會產生RST
,以此來講明重置報文段的用途。緩存
客戶端向服務端某端口發起鏈接請求SYN
,可是目的服務端主機不存在該端口,此時向客戶端迴應RST
,中斷鏈接請求。服務器
下面經過程序和抓包進行分析。程序源碼以下:微信
use std::io::prelude::*; use std::net::TcpStream; use std::thread; fn main() { let mut stream = TcpStream::connect("192.168.2.229:33333").unwrap(); let n = stream.write(&[1,2,3,4,5,6,7,8,9]).unwrap(); println!("send {} bytes to remote node, waiting for end.", n); loop{ thread::sleep_ms(1000); } }
上面程序向目的主機192.168.2.229
發起TCP鏈接,而目的主機並無啓動端口爲33333
的監聽服務。因此當本地主機向目的主機發起TCP鏈接後,會收到來自目的主機的RST
,並斷開鏈接。(固然也不是全部的都會回覆RST
,有的主機可能不會進行回覆)。抓包以下:網絡
本地主機向目的主機發送TCP鏈接SYN
:
併發
目的主機向本地主機回覆ACK、RST
:
socket
終止一條鏈接的正常方法是由通訊一方發送一個FIN
。這種方法也被稱爲有序釋放。由於FIN
是在以前全部排隊數據都已發送後才被髮送出去,一般不會出現丟失數據的狀況。然而在任什麼時候刻,咱們均可以經過發送一個重置報文段RST
替代FIN
來終止一條鏈接。這種方式也被稱爲終止釋放。tcp
終止一條鏈接能夠爲應用程序提供兩大特性:分佈式
套接字API可經過套接字選項SO_LINGER
的數值設置爲0
來實現上述功能。
/* Structure used to manipulate the SO_LINGER option. */ struct linger { int l_onoff; /* Nonzero to linger on close. */ int l_linger; /* Time to linger. */ };
SO_LINGER
的不一樣值的含義以下:
l_onoff
爲0
,l_linger
的值被忽略,內核缺省狀況,close()
調用會當即返回給調用者,TCP模塊負責嘗試發送殘留的緩存區數據。l_onoff
爲非零值,l_linger
爲0
,則鏈接當即終止,TCP將丟棄殘留在發送緩衝區中的數據併發送RST
給對方,而不是發送FIN
,這樣避免了TIME_WAIT
狀態,對方read()
時將收到Connection reset by peer
的錯誤。l_onoff
爲非零值,l_linger
大於零:若是l_linger
時間範圍,TCP模塊成功發送完殘留的緩衝區數據,則正常關閉,若是超時,則向對方發送RST
,丟棄殘留在發送緩衝區的數據。客戶端代碼間附錄代碼1,服務端代碼以下:
/* echo server with poll */ #include <poll.h> #include<stdio.h> #include<stdlib.h> #include<arpa/inet.h> #include<netinet/in.h> #include<sys/socket.h> #include<sys/types.h> #include<unistd.h> #include<string.h> #include<fcntl.h> #include<errno.h> #include<pthread.h> #define OPEN_MAX 1024 #define LISTEN_PORT 33333 #define MAX_BUF 1024 int set_linger(int sock, int l_onoff, int l_linger); int handle_conn(struct pollfd *nfds, char* buf); void run(); int main(int _argc, char* _argv[]) { run(); return 0; } void run() { // bind socket char str[INET_ADDRSTRLEN]; struct sockaddr_in seraddr, cliaddr; socklen_t cliaddr_len = sizeof(cliaddr); int listen_sock = socket(AF_INET, SOCK_STREAM, 0); bzero(&seraddr, sizeof(seraddr)); seraddr.sin_family = AF_INET; seraddr.sin_addr.s_addr = htonl(INADDR_ANY); seraddr.sin_port = htons(LISTEN_PORT); int opt = 1; setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); if (-1 == bind(listen_sock, (struct sockaddr*)&seraddr, sizeof(seraddr))) { perror("bind server addr failure."); exit(EXIT_FAILURE); } listen(listen_sock, 5); int ret, i; struct pollfd nfds[OPEN_MAX]; for (i=0;i<OPEN_MAX;++i){ nfds[i].fd = -1; } nfds[0].fd = listen_sock; nfds[0].events = POLLIN; char* buf = (char*)malloc(MAX_BUF); while (1) { ret = poll(nfds, OPEN_MAX, NULL); if (-1 == ret) { perror("poll failure."); exit(EXIT_FAILURE); } /* An event on one of the fds has occurred. */ if (nfds[0].revents & POLLIN) { int conn_sock = accept(listen_sock, (struct sockaddr *)&cliaddr, &cliaddr_len); if (-1 == conn_sock) { perror("accept failure."); exit(EXIT_FAILURE); } printf("accept from %s:%d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port)); set_linger(conn_sock, 1, 0); //設置SO_LINGER option值爲0 for (int k=0;k<OPEN_MAX;++k){ if (nfds[k].fd < 0){ nfds[k].fd = conn_sock; nfds[k].events = POLLIN; break; } if (k == OPEN_MAX-1){ perror("too many clients, nfds size is not enough."); exit(EXIT_FAILURE); } } } handle_conn(nfds, buf); } close(listen_sock); } int handle_conn(struct pollfd *nfds, char* buf) { int n = 0; for (int i=1;i<OPEN_MAX;++i) { if (nfds[i].fd<0) { continue; } if (nfds[i].revents & POLLIN) { bzero(buf, MAX_BUF); n = read(nfds[i].fd, buf, MAX_BUF); if (0 == n) { close(nfds[i].fd); nfds[i].fd = -1; continue; } if (n>0){ printf("recv from client: %s\n", buf); nfds[i].events = POLLIN; close(nfds[i].fd); //接收數據後就主動關閉鏈接,用於RST測試 } else { perror("read failure."); exit(EXIT_FAILURE); } } else if (nfds[i].revents & POLLOUT) { printf("write data to client: %s\n", buf); write(nfds[i].fd, buf, sizeof(buf)); bzero(buf, MAX_BUF); nfds[i].events = POLLIN; } } return 0; } int set_linger(int sock, int l_onoff, int l_linger) { struct linger so_linger; so_linger.l_onoff = l_onoff; so_linger.l_linger = l_linger; int r = setsockopt(sock, SOL_SOCKET, SO_LINGER, &so_linger, sizeof(so_linger)); return r; }
抓包結果以下:
先3次握手,後客戶端向服務度發送了5個字節數據,服務端在接收完5字節數據向客戶端ACK
後,表示想中斷鏈接,此時因設置了SO_LINGER
選項值爲0
,close()
時,直接向對方發送RST
而不是正常的發送FIN
,鏈接當即終止,而且不會有TIME_WAIT
狀態,TCP將丟棄殘留在發送緩衝區中的數據,對方read()
時將收到Connection reset by peer
的錯誤。
若是在未告知另外一端的狀況下通訊的一端關閉或終止鏈接,那麼就認爲該條TCP鏈接處於半開狀態。
舉個例子,服務器主機被切斷電源後重啓(切斷電源前可將網線斷開,重啓後再接上),此時的客戶端是一個半開的鏈接。當客戶端再次向服務端發送數據時,服務端對此鏈接一無所知,會回覆一個重置報文段RST
後,中斷鏈接。
再或者若是程序開啓了TCP保活機制,則當監測到對方主機不可達時,發送RST
中斷鏈接。詳細可參考個人另外一篇博文TCP保活機制。
TCP鏈接若是長時間沒有數據收發,會使TCP發送保活探測報文,以維持鏈接或者探測鏈接是否存在。
能夠看到若是認爲鏈接不存在了,就會發送RST
中斷鏈接。
TCP應用程序接收數據是從操做系統中接收的TCP數據,若是數據到達了操做系統可是我應用數據不想繼續接收數據了,此時RST
中斷鏈接。
服務端代碼:
/* echo server with poll */ #include <poll.h> #include<stdio.h> #include<stdlib.h> #include<arpa/inet.h> #include<netinet/in.h> #include<sys/socket.h> #include<sys/types.h> #include<unistd.h> #include<string.h> #include<fcntl.h> #include<errno.h> #include<pthread.h> #define OPEN_MAX 1024 #define LISTEN_PORT 33333 #define MAX_BUF 1024 #define RST_TEST 1 int handle_conn(struct pollfd *nfds, char* buf); void run(); int main(int _argc, char* _argv[]) { run(); return 0; } void run() { // bind socket char str[INET_ADDRSTRLEN]; struct sockaddr_in seraddr, cliaddr; socklen_t cliaddr_len = sizeof(cliaddr); int listen_sock = socket(AF_INET, SOCK_STREAM, 0); bzero(&seraddr, sizeof(seraddr)); seraddr.sin_family = AF_INET; seraddr.sin_addr.s_addr = htonl(INADDR_ANY); seraddr.sin_port = htons(LISTEN_PORT); int opt = 1; setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); if (-1 == bind(listen_sock, (struct sockaddr*)&seraddr, sizeof(seraddr))) { perror("bind server addr failure."); exit(EXIT_FAILURE); } listen(listen_sock, 5); int ret, i; struct pollfd nfds[OPEN_MAX]; for (i=0;i<OPEN_MAX;++i){ nfds[i].fd = -1; } nfds[0].fd = listen_sock; nfds[0].events = POLLIN; char* buf = (char*)malloc(MAX_BUF); while (1) { ret = poll(nfds, OPEN_MAX, NULL); if (-1 == ret) { perror("poll failure."); exit(EXIT_FAILURE); } /* An event on one of the fds has occurred. */ if (nfds[0].revents & POLLIN) { int conn_sock = accept(listen_sock, (struct sockaddr *)&cliaddr, &cliaddr_len); if (-1 == conn_sock) { perror("accept failure."); exit(EXIT_FAILURE); } printf("accept from %s:%d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port)); for (int k=0;k<OPEN_MAX;++k){ if (nfds[k].fd < 0){ nfds[k].fd = conn_sock; nfds[k].events = POLLIN; break; } if (k == OPEN_MAX-1){ perror("too many clients, nfds size is not enough."); exit(EXIT_FAILURE); } } } handle_conn(nfds, buf); } close(listen_sock); } int handle_conn(struct pollfd *nfds, char* buf) { int n = 0; for (int i=1;i<OPEN_MAX;++i) { if (nfds[i].fd<0) { continue; } if (nfds[i].revents & POLLIN) { bzero(buf, MAX_BUF); #if RST_TEST == 0 n = read(nfds[i].fd, buf, MAX_BUF); #else n = read(nfds[i].fd, buf, 5); //只接收部分數據就主動關閉鏈接,用於RST測試 #endif if (0 == n) { close(nfds[i].fd); nfds[i].fd = -1; continue; } if (n>0){ printf("recv from client: %s\n", buf); nfds[i].events = POLLOUT; #if RST_TEST != 0 close(nfds[i].fd); //只接收部分數據就主動關閉鏈接,用於RST測試 #endif } else { perror("read failure."); exit(EXIT_FAILURE); } } else if (nfds[i].revents & POLLOUT) { printf("write data to client: %s\n", buf); write(nfds[i].fd, buf, sizeof(buf)); bzero(buf, MAX_BUF); nfds[i].events = POLLIN; } } return 0; }
客戶端發起鏈接後發送超過5字節的數據後,由於服務端只接收5個字節數據後再也不接收數據(此時服務端操做系統已經收到10個字節數據,但上層read
系統調用只接收5個字節),服務端向客戶端發送RST
中斷鏈接。抓包結果以下:
先3次握手,握手後客戶端發送了10字節長度的數據,服務端在迴應客戶端ACK
接收到數據後,發送RST
中斷鏈接。
若是一個已關閉的TCP鏈接又收到數據,顯然是異常的,此時應RST
中斷鏈接。
服務端其餘代碼與上個代碼相同,下面函數替換一下
int handle_conn(struct pollfd *nfds, char* buf) { int n = 0; for (int i=1;i<OPEN_MAX;++i) { if (nfds[i].fd<0) { continue; } if (nfds[i].revents & POLLIN) { bzero(buf, MAX_BUF); n = read(nfds[i].fd, buf, MAX_BUF); if (0 == n) { close(nfds[i].fd); nfds[i].fd = -1; continue; } if (n>0){ printf("recv from client: %s\n", buf); nfds[i].events = POLLOUT; close(nfds[i].fd); //接收數據後就主動關閉鏈接,用於RST測試 } else { perror("read failure."); exit(EXIT_FAILURE); } } else if (nfds[i].revents & POLLOUT) { printf("write data to client: %s\n", buf); write(nfds[i].fd, buf, sizeof(buf)); bzero(buf, MAX_BUF); nfds[i].events = POLLIN; } } return 0; }
客戶端代碼與上個相同,只有下面函數不一樣,替換一下便可:
void client_handle(int sock) { char sendbuf[MAXLEN], recvbuf[MAXLEN]; bzero(sendbuf, MAXLEN); bzero(recvbuf, MAXLEN); int n = 0; while (1) { if (NULL == fgets(sendbuf, MAXLEN, stdin)) { break; } // 按`#`號退出 if ('#' == sendbuf[0]) { break; } struct timeval start, end; gettimeofday(&start, NULL); write(sock, sendbuf, strlen(sendbuf)); sleep(2); write(sock, sendbuf, strlen(sendbuf)); //這裏是測試RST用的代碼 sleep(60); n = read(sock, recvbuf, MAXLEN); if (0 == n) { break; } write(STDOUT_FILENO, recvbuf, n); gettimeofday(&end, NULL); printf("time diff=%ld microseconds\n", ((end.tv_sec * 1000000 + end.tv_usec)- (start.tv_sec * 1000000 + start.tv_usec))); } close(sock); }
抓包以下:
先3次握手;後客戶端向服務端發送了5字節數據,服務端接收到5字節數據回覆ACK
;以後向客戶端發送FIN
,關閉鏈接,但此時客戶端還有數據要發送,沒有向服務端發起FIN
,此時只進行了2次揮手;以後客戶端又向服務端發送了5個字節數據,但此時服務端該鏈接已經調用close()
關閉,此時再次收到該鏈接的數據屬於異常,回覆RST
中斷鏈接。
測試用的客戶端代碼
#include<unistd.h> #include<sys/types.h> #include<sys/socket.h> #include<netinet/in.h> #include<arpa/inet.h> #include<netdb.h> #include <time.h> #include <sys/time.h> #include<stdlib.h> #include <errno.h> #include <stdio.h> #include <string.h> #define SERVER_PORT 33333 #define MAXLEN 65535 void client_handle(int sock); int main(int argc, char* argv[]) { for (int i = 1; i < argc; ++i) { printf("input args %d: %s\n", i, argv[i]); } struct sockaddr_in seraddr; int server_port = SERVER_PORT; if (2 == argc) { server_port = atoi(argv[1]); } int sock = socket(AF_INET, SOCK_STREAM, 0); bzero(&seraddr, sizeof(seraddr)); seraddr.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr); seraddr.sin_port = htons(server_port); if (-1 == connect(sock, (struct sockaddr *)&seraddr, sizeof(seraddr))) { perror("connect failure"); exit(EXIT_FAILURE); } client_handle(sock); return 0; } void client_handle(int sock) { char sendbuf[MAXLEN], recvbuf[MAXLEN]; bzero(sendbuf, MAXLEN); bzero(recvbuf, MAXLEN); int n = 0; while (1) { if (NULL == fgets(sendbuf, MAXLEN, stdin)) { break; } // 按`#`號退出 if ('#' == sendbuf[0]) { break; } struct timeval start, end; gettimeofday(&start, NULL); write(sock, sendbuf, strlen(sendbuf)); n = read(sock, recvbuf, MAXLEN); if (n < 0) { perror("read failure."); exit(EXIT_FAILURE); } if (0 == n) { break; } write(STDOUT_FILENO, recvbuf, n); gettimeofday(&end, NULL); printf("time diff=%ld microseconds\n", ((end.tv_sec * 1000000 + end.tv_usec)- (start.tv_sec * 1000000 + start.tv_usec))); } close(sock); }
歡迎關注微信公衆號,推送計算機網絡、後端開發、區塊鏈、分佈式、Rust、Linux等技術文章!