accept() 在重复连接尝试时无限期地阻塞
accept() block indefinitely on repeated connection attempts
我正在编写一个 TCP 服务器,我希望一次接受一个连接,并通过重复使用它用于侦听的地址和端口。与服务器启动实例的第一次连接(例如通过 netcat)总是成功,但随后的连接尝试停止在 accept() 不返回套接字描述符。我已经尝试过不同的队列长度,并且在前一个连接处于 TIME_WAIT 状态时进行连接,并且在它被清除之后进行连接,但结果是一样的。 netcat和netstat都报告新的连接尝试成功,并报告连接已建立(不管之前的连接是在TIME_WAIT还是过期),但是我的服务器卡在了accept()调用,从而它不注册新连接。此行为并不总是在 第一次 后续连接尝试时立即发生,但几乎总是在前三次尝试期间发生。
代码:
main() {
Socket socket(10669);
while (true) {
socket.establish_connection();
socket.receive(callback);
socket.close_connection();
}
}
void Socket::establish_connection() {
// Creating socket file descriptor
int server_fd = 0;
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
throw ...;
}
// Setting socket options
int socket_options = 1;
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEPORT, &socket_options, sizeof(socket_options))) {
throw ...;
}
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(port);
if (bind(server_fd, (sockaddr *) &address, sizeof(address)) < 0) {
throw ...;
}
if (listen(server_fd, 1) < 0) {
throw ...;
}
spdlog::info("Listening for clients on port {}", port);
// this is where it blocks at repeated connection attempts
struct sockaddr_in client_address;
int addrlen = sizeof(client_address);
if ((socket = accept(server_fd, (sockaddr *) &client_address, (socklen_t*) &addrlen)) < 0) {
throw ...;
}
spdlog::info("Client connected\n");
}
void Socket::receive(SocketCallback callback) {
while (true) {
fd_set read_socket_fd;
FD_ZERO(&read_socket_fd);
FD_SET(socket, &read_socket_fd);
int sel = select(socket+1, &read_socket_fd, NULL, NULL, NULL);
if (sel > 0) {
// receiving data, no problems here
}
}
}
void Socket::close_connection() {
close(socket);
}
来自服务器的一些打印输出,以及 netstat:
启动时(服务器):
[2020-07-07 13:33:53.387] [info] Socket initialised to use port 10669
[2020-07-07 13:33:53.387] [info] Listening for clients on port 10669
启动时 (netstat):
tcp 0 0 0.0.0.0:10669 0.0.0.0:* LISTEN
首次连接(服务器):
[2020-07-07 13:34:35.481] [info] Client connected
首次连接时 (netstat):
tcp 0 0 0.0.0.0:10669 0.0.0.0:* LISTEN
tcp 0 0 localhost:54860 localhost:10669 ESTABLISHED
tcp 0 0 localhost:10669 localhost:54860 ESTABLISHED
第一次与客户端(服务器)断开连接时:
[2020-07-07 13:35:47.903] [warning] Client disconnected
[2020-07-07 13:35:47.903] [info] Listening for clients on port 10669
第一次与客户端断开连接时 (netstat):
tcp 0 0 0.0.0.0:10669 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:10669 0.0.0.0:* LISTEN
tcp 0 0 localhost:54860 localhost:10669 TIME_WAIT
在第二次连接尝试时,服务器没有报告任何内容,因为它停留在“正在侦听客户端...”行,表明在 accept() 处被阻止。这是 netstat 报告的内容(这是我在第一次断开连接后立即连接时的情况,因此之前的连接处于 TIME_WAIT 状态):
tcp 0 0 0.0.0.0:10669 0.0.0.0:* LISTEN
tcp 1 0 0.0.0.0:10669 0.0.0.0:* LISTEN
tcp 0 0 localhost:54968 localhost:10669 TIME_WAIT
tcp 0 0 localhost:54970 localhost:10669 ESTABLISHED
tcp 0 0 localhost:10669 localhost:54970 ESTABLISHED
同样的情况发生在我等待 TIME_WAIT 过期之后才尝试连接:
tcp 0 0 0.0.0.0:10669 0.0.0.0:* LISTEN
tcp 1 0 0.0.0.0:10669 0.0.0.0:* LISTEN
tcp 0 0 localhost:10669 localhost:55134 ESTABLISHED
tcp 0 0 localhost:55134 localhost:10669 ESTABLISHED
在这两种情况下,netcat 中的连接都是活动的,我可以随意输入,但当然什么也没有收到;没有其他进程可以拦截连接。
我知道我可能会尝试非阻塞的 accept(),但是 accept() 的这种阻塞行为非常适合我的用法,当它按预期运行时,所以问题是 - 为什么它会在重新连接时阻塞,我在这里错过了什么?
您应该创建一个 服务器套接字,然后在同一个套接字上重复调用accept
。每次调用 accept
时,您似乎都在创建一个新的服务器套接字,并让旧的套接字保持打开状态。
通常情况下,这是无效的,但是您使用 SO_REUSEPORT
告诉操作系统您确实需要它。与SO_REUSEPORT
、incoming connections are balanced across all the server sockets on the same port。显然,操作系统选择将您的新连接发送到第一个套接字,然后您尝试从没有新连接等待的第二个套接字 accept
发送它。
要修复它,请创建一个服务器套接字一次,然后总是从同一个套接字accept
。
我正在编写一个 TCP 服务器,我希望一次接受一个连接,并通过重复使用它用于侦听的地址和端口。与服务器启动实例的第一次连接(例如通过 netcat)总是成功,但随后的连接尝试停止在 accept() 不返回套接字描述符。我已经尝试过不同的队列长度,并且在前一个连接处于 TIME_WAIT 状态时进行连接,并且在它被清除之后进行连接,但结果是一样的。 netcat和netstat都报告新的连接尝试成功,并报告连接已建立(不管之前的连接是在TIME_WAIT还是过期),但是我的服务器卡在了accept()调用,从而它不注册新连接。此行为并不总是在 第一次 后续连接尝试时立即发生,但几乎总是在前三次尝试期间发生。
代码:
main() {
Socket socket(10669);
while (true) {
socket.establish_connection();
socket.receive(callback);
socket.close_connection();
}
}
void Socket::establish_connection() {
// Creating socket file descriptor
int server_fd = 0;
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
throw ...;
}
// Setting socket options
int socket_options = 1;
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEPORT, &socket_options, sizeof(socket_options))) {
throw ...;
}
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(port);
if (bind(server_fd, (sockaddr *) &address, sizeof(address)) < 0) {
throw ...;
}
if (listen(server_fd, 1) < 0) {
throw ...;
}
spdlog::info("Listening for clients on port {}", port);
// this is where it blocks at repeated connection attempts
struct sockaddr_in client_address;
int addrlen = sizeof(client_address);
if ((socket = accept(server_fd, (sockaddr *) &client_address, (socklen_t*) &addrlen)) < 0) {
throw ...;
}
spdlog::info("Client connected\n");
}
void Socket::receive(SocketCallback callback) {
while (true) {
fd_set read_socket_fd;
FD_ZERO(&read_socket_fd);
FD_SET(socket, &read_socket_fd);
int sel = select(socket+1, &read_socket_fd, NULL, NULL, NULL);
if (sel > 0) {
// receiving data, no problems here
}
}
}
void Socket::close_connection() {
close(socket);
}
来自服务器的一些打印输出,以及 netstat:
启动时(服务器):
[2020-07-07 13:33:53.387] [info] Socket initialised to use port 10669
[2020-07-07 13:33:53.387] [info] Listening for clients on port 10669
启动时 (netstat):
tcp 0 0 0.0.0.0:10669 0.0.0.0:* LISTEN
首次连接(服务器):
[2020-07-07 13:34:35.481] [info] Client connected
首次连接时 (netstat):
tcp 0 0 0.0.0.0:10669 0.0.0.0:* LISTEN
tcp 0 0 localhost:54860 localhost:10669 ESTABLISHED
tcp 0 0 localhost:10669 localhost:54860 ESTABLISHED
第一次与客户端(服务器)断开连接时:
[2020-07-07 13:35:47.903] [warning] Client disconnected
[2020-07-07 13:35:47.903] [info] Listening for clients on port 10669
第一次与客户端断开连接时 (netstat):
tcp 0 0 0.0.0.0:10669 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:10669 0.0.0.0:* LISTEN
tcp 0 0 localhost:54860 localhost:10669 TIME_WAIT
在第二次连接尝试时,服务器没有报告任何内容,因为它停留在“正在侦听客户端...”行,表明在 accept() 处被阻止。这是 netstat 报告的内容(这是我在第一次断开连接后立即连接时的情况,因此之前的连接处于 TIME_WAIT 状态):
tcp 0 0 0.0.0.0:10669 0.0.0.0:* LISTEN
tcp 1 0 0.0.0.0:10669 0.0.0.0:* LISTEN
tcp 0 0 localhost:54968 localhost:10669 TIME_WAIT
tcp 0 0 localhost:54970 localhost:10669 ESTABLISHED
tcp 0 0 localhost:10669 localhost:54970 ESTABLISHED
同样的情况发生在我等待 TIME_WAIT 过期之后才尝试连接:
tcp 0 0 0.0.0.0:10669 0.0.0.0:* LISTEN
tcp 1 0 0.0.0.0:10669 0.0.0.0:* LISTEN
tcp 0 0 localhost:10669 localhost:55134 ESTABLISHED
tcp 0 0 localhost:55134 localhost:10669 ESTABLISHED
在这两种情况下,netcat 中的连接都是活动的,我可以随意输入,但当然什么也没有收到;没有其他进程可以拦截连接。
我知道我可能会尝试非阻塞的 accept(),但是 accept() 的这种阻塞行为非常适合我的用法,当它按预期运行时,所以问题是 - 为什么它会在重新连接时阻塞,我在这里错过了什么?
您应该创建一个 服务器套接字,然后在同一个套接字上重复调用accept
。每次调用 accept
时,您似乎都在创建一个新的服务器套接字,并让旧的套接字保持打开状态。
通常情况下,这是无效的,但是您使用 SO_REUSEPORT
告诉操作系统您确实需要它。与SO_REUSEPORT
、incoming connections are balanced across all the server sockets on the same port。显然,操作系统选择将您的新连接发送到第一个套接字,然后您尝试从没有新连接等待的第二个套接字 accept
发送它。
要修复它,请创建一个服务器套接字一次,然后总是从同一个套接字accept
。