После закрытия направления записи соединения сокета с использованием shutdown()
все последующие полученные данные вызывают отправку пакета RST, а read()
возвращает размер 0, указывающий EOF для направления чтения. Почему это так, хотя направление чтения не было закрыто?
Используя wireshark, я проверил отправленные пакеты:
No. Time Source Destination Protocol Length Info
1 0.000000 192.168.0.175 3.85.154.144 TCP 78 55318 → 80 [SYN] Seq=0 Win=65535 Len=0 MSS=1460 WS=32
2 0.114608 3.85.154.144 192.168.0.175 TCP 74 80 → 55318 [SYN, ACK] Seq=0 Ack=1 Win=26847 Len=0 MSS=1460 WS=256
3 0.114706 192.168.0.175 3.85.154.144 TCP 66 55318 → 80 [ACK] Seq=1 Ack=1 Win=131744 Len=0
4 0.115371 192.168.0.175 3.85.154.144 HTTP 112 GET /bytes/512 HTTP/1.1
5 0.115401 192.168.0.175 3.85.154.144 TCP 66 55318 → 80 [FIN, ACK] Seq=47 Ack=1 Win=131744 Len=0
6 0.222652 3.85.154.144 192.168.0.175 TCP 66 80 → 55318 [ACK] Seq=1 Ack=47 Win=26880 Len=0
7 0.224444 3.85.154.144 192.168.0.175 HTTP 801 HTTP/1.1 200 OK (application/octet-stream)
8 0.224543 192.168.0.175 3.85.154.144 TCP 54 55318 → 80 [RST] Seq=48 Win=0 Len=0
9 0.226056 3.85.154.144 192.168.0.175 TCP 66 80 → 55318 [FIN, ACK] Seq=736 Ack=48 Win=26880 Len=0
10 0.226100 192.168.0.175 3.85.154.144 TCP 54 55318 → 80 [RST] Seq=48 Win=0 Len=0
После того, как [FIN, ACK] отправлено, на все полученные данные отвечают RST. Кажется, что локальная сторона думает, что соединение полностью закрыто, даже если был отправлен FIN (с указанием конца записанных данных) вместо RST (с указанием конца соединения). Удаленная сторона ожидает, что сможет отправлять данные, но не может.
Мониторинг состояния сокета с помощью netstat показывает, что соединение сразу полностью закрывается после вызова shutdown(sockfd, SHUT_WR)
.
Вот MWE в C ++. Предполагается, что все не относящиеся к делу функции выполняются успешно и в противном случае прерывается. Коды возврата всех функций проверяются на наличие ошибок, чтобы убедиться, что они не ошибаются в результатах.
#include <arpa/inet.h>
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#define RUN_OR_ABORT(fun) {\
int RETVAL = fun;\
if (RETVAL == -1)\
{\
perror(#fun);\
abort();\
}\
}
int main() {
// Establish connection to httpbin.org:80
int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sockfd == -1) exit(EXIT_FAILURE);
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(80);
// "httpbin.org" resolves to 3.85.154.144 or 52.71.234.219
int retcode = inet_pton(addr.sin_family, "3.85.154.144", &addr.sin_addr);
if (retcode != 1) exit(EXIT_FAILURE);
RUN_OR_ABORT(connect(sockfd, (sockaddr*)&addr, sizeof(addr)));
// Request 512 random bytes
const char* msg = "GET /bytes/512 HTTP/1.1\r\nHost: httpbin.org\r\n\r\n";
ssize_t write_len = write(sockfd, msg, strlen(msg));
if (write_len != strlen(msg)) exit(EXIT_FAILURE);
RUN_OR_ABORT(shutdown(sockfd, SHUT_WR));
// Prepare buffer for recieving data
size_t buf_size = 1024, read_offset = 0;
char* buf = new char[buf_size];
// Read as long as there is data available
while (true) {
ssize_t read_len = read(sockfd, buf + read_offset, buf_size - read_offset);
if (read_len == -1 and (errno & (EAGAIN | EINTR)))
continue;
else if (read_len == -1) {
perror("recv()");
exit(EXIT_FAILURE);
}
read_offset += read_len;
if (read_len == 0) {
// EOF?
printf("%lu bytes have been read\n", read_offset);
break;
}
if (read_offset >= buf_size) {
fprintf(stderr, "Unexpectedly large response\n");
exit(EXIT_FAILURE);
}
}
}
Я бы ожидал, что вызов read()
вернет ненулевой размер и сокет останется открытым для чтения после закрытия конца записи с помощью shutdown(sockfd, SHUT_WR)
. Ожидаемый выход MWE будет:
740 bytes have been read
(или аналогичный, но количество прочитанных байтов должно быть больше 512 байтов).
Фактический результат был:
0 bytes have been read