RST
Указывает на сброс соединения, который используется для закрытия соединений, которые больше не нужны для продолжения существования. В общем, это означает, что соединение закрывается ненормально, что отличается от нормального закрытия соединения четыре раза.
производитьRST
Три условия:
- место назначения для порта
SYN
прибыл, но сервер не прослушивает порт; - TCP хочет отменить существующее соединение;
- TCP получил сегмент соединения, которого вообще не существует.
Следующие сценарии будут производитьRST
, чтобы проиллюстрировать назначение сегмента сброса.
1. Запросы на подключение для несуществующих портов
Клиент инициирует запрос на подключение к порту на сервере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
:
2. Разорвать соединение
Обычный способ разрыва соединения — это отправка одной сторонойFIN
. Этот метод также известен как приказное освобождение. так какFIN
Он отправляется после того, как все ранее поставленные в очередь данные были отправлены, и потери данных обычно не происходит. Однако в любой момент мы можем отправить сегмент сброса поRST
альтернативаFIN
чтобы разорвать соединение. Этот метод также называется прекращенным выпуском.
Завершение соединения предоставляет приложению две основные характеристики:
- Любые данные в очереди будут отброшены, и немедленно будет отправлен сегмент сброса;
- Получатель сегмента сброса укажет, что другой конец связи прерван, а не корректно завершен. API ДОЛЖЕН предоставлять способ реализации поведения завершения, описанного выше, вместо обычной операции завершения работы.
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;
}
Результаты захвата пакетов следующие:
Сначала три рукопожатия, затем клиент отправляет сервису 5 байт данных, а после получения 5 байт данных сервер отправляет клиенту 5 байт данных.ACK
После этого это означает, что вы хотите разорвать соединение.SO_LINGER
Значение параметра0
,close()
, отправить непосредственно другой сторонеRST
вместо обычной отправкиFIN
, соединение немедленно разрывается, и не будетTIME_WAIT
состояние, TCP отбрасывает данные, оставшиеся в буфере отправки, другая сторонаread()
получитConnection reset by peer
ошибка.
3. Полуоткрытое соединение
Соединение 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
Отключить. Результаты захвата пакетов следующие:
ACK
После получения данных отправитьRST
Отключить.
5. Получить данные по закрытому TCP-соединению
Если закрытое 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
, только дважды замахнулся в это время, после этого клиент отправил 5 байт данных на сервер, но в это время сервер вызвал соединениеclose()
Закрыто, повторное получение данных о соединении в настоящее время является ненормальным, ответьтеRST
Отключить.
6. Приложение
клиентский код для тестирования
#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);
}
Справочная документация:
Параметр SO_LINGER для сокета Linux
So_linger и первый из Socket
Добро пожаловать в общедоступную учетную запись WeChat и размещайте технические статьи о компьютерных сетях, серверной разработке, блокчейне, распределенных приложениях, Rust, Linux и т. д.!