为何设置listen最大2个TCP连接却能接入更多客户端?
listen设置最大连接数为2却能接受更多客户端的原因
我在Linux Ubuntu 22.04上用C++写了支持多客户端的TCP服务端和客户端,逻辑是客户端发"Hello"服务端回"World!",发"disconnect"断开连接,其他内容原样返回。但服务端里用listen(socket_fd, 2)设置了最大同时连接数为2,实际运行时却能启动超过2个客户端且服务端无报错,请问这是为什么?
main.cpp
#include "server.hpp" #include "client.hpp" int main(int argc, char** argv) { try { if (argc < 2) { std::cout << "Specify the mode: server OR client" << std::endl; exit(EXIT_FAILURE); } if (strcmp(argv[1], "server") == 0) { server(); } if (strcmp(argv[1], "client") == 0) { client(); } } catch(const std::exception &ex) { std::cerr << "Error occured: " << ex.what(); } catch(...) { std::cerr << "Undefined error occured"; } return 0; }
server.hpp
#pragma once #include <sys/wait.h> #include <arpa/inet.h> #include <unistd.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdlib.h> #include <string.h> #include <iostream> void server() { const int BUFFER_SIZE = 1024; const int PORT = 8888; std::cout << "Launched server\n"; pid_t childPid; int socket_fd; if ((socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { std::cout << "Unable to create a socket!\n"; exit(EXIT_FAILURE); } const int enable = 1; if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int))<0) { std::cout << "setsockopt(SO_REUSEADDR) failed!\n"; } if (setsockopt(socket_fd, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(int))<0) { std::cout << "setsockopt(SO_REUSEPORT) failed!\n"; } struct sockaddr_in serverAddress; serverAddress.sin_family = AF_INET; serverAddress.sin_port = htons(PORT); serverAddress.sin_addr.s_addr = INADDR_ANY; if (bind(socket_fd, (sockaddr*)&serverAddress, sizeof(serverAddress)) < 0) { std::cout << "Unable to bind a socket!\n"; exit(EXIT_FAILURE); } if (listen(socket_fd, 2) < 0) { std::cout << "Unable to listen\n"; exit(EXIT_FAILURE); } struct sockaddr_in cliAddress; int cliAddress_size = sizeof(cliAddress); int client_fd; int cnt = 0; while(1) { std::cout << "Waiting for clients to connect...\n"; std::cout.flush(); if ((client_fd = accept(socket_fd, (sockaddr*)&cliAddress, (socklen_t*)&cliAddress_size)) == -1) { std::cout << "Unable to accept!\n"; exit(EXIT_FAILURE); } std::cout << "Connection accepted from: " << inet_ntoa(cliAddress.sin_addr) << ":" << ntohs(cliAddress.sin_port) << '\n'; std::cout << "Number of connected clients: " << ++cnt << "\n"; if ((childPid = fork()) == 0) { close(socket_fd); while(1) { char buffer[BUFFER_SIZE] = {0}; int bytes_received = read(client_fd, buffer, BUFFER_SIZE); std::cout << "Received " << bytes_received << " bytes from the client wtih port " << ntohs(cliAddress.sin_port) << " " << buffer << "\n"; if (strcmp(buffer, "Hello") == 0) { auto msg = "World!"; std::cout << "Sending a response message...\n"; send(client_fd, msg, strlen(msg), 0); } else if (strcmp(buffer, "disconnect") == 0) { auto msg = "Disconnecting client\n"; send(client_fd, msg, strlen(msg), 0); break; } else { std::cout << "Sending a response message...\n"; send(client_fd, buffer, strlen(buffer), 0); } } close(client_fd); exit(EXIT_SUCCESS); } else { close(client_fd); } } }
client.hpp
#pragma once #include <arpa/inet.h> #include <unistd.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdlib.h> #include <string.h> #include <iostream> void client() { const int BUFFER_SIZE = 1024; const int PORT = 8888; std::cout << "Launched client\n"; int client_fd; if ((client_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { std::cout << "Unable to create a socket!\n"; exit(EXIT_FAILURE); } struct sockaddr_in cliAddress; cliAddress.sin_family = AF_INET; cliAddress.sin_port = htons(PORT); cliAddress.sin_addr.s_addr = INADDR_ANY; if ((connect(client_fd, (sockaddr*)&cliAddress, sizeof(cliAddress))) < 0) { std::cout << "Unable to connect!\n"; exit(EXIT_FAILURE); } while (1) { std::cout << "What message do you want to send to the server? Or do you want to 'disconnect'?\n"; std::string msg_temp; std::cin >> msg_temp; const char * msg = msg_temp.c_str(); send(client_fd, msg, strlen(msg), 0); char buffer[BUFFER_SIZE] = {0}; int bytes_received = read(client_fd, buffer, BUFFER_SIZE); std::cout << "Received " << bytes_received << " bytes from the server: " << buffer << "\n"; if (strcmp(msg, "disconnect") == 0) { break; } } close(client_fd); }
Makefile
output: main.o g++ -std=c++20 main.o -o output main.o: main.cpp client.hpp server.hpp g++ -std=c++20 -c main.cpp clean: rm *.o output
原因分析
listen函数的第二个参数不是限制同时连接的客户端总数,而是限制未完成连接队列(SYN队列)的最大长度——也就是处于TCP三次握手过程中,已经收到客户端SYN但还没完成三次握手的连接数量。
你的服务端逻辑是每收到一个新连接(accept成功)就立刻fork子进程处理该连接,父进程随即close(client_fd)并回到accept等待新连接。这意味着:
- 每当客户端发起连接,三次握手完成后,
accept会立刻把连接从已完成队列(ACCEPT队列)取出,交给子进程处理 - 已完成队列始终处于空或接近空的状态,未完成队列也不会被占满
- 只要系统资源足够,服务端就能不断接受新的客户端连接,不受
listen参数的限制
如果想要限制同时在线的客户端数量,你需要自己维护一个计数器:
- 每当
accept成功时,计数器加1 - 当计数器达到上限时,拒绝后续的
accept(或者关闭新连接) - 当客户端断开连接时,计数器减1(注意需要处理子进程退出的信号,比如
SIGCHLD,在信号处理函数中更新计数器)
另外,你的服务端没有处理子进程退出的僵尸进程问题,长时间运行会积累大量僵尸进程,建议添加SIGCHLD信号处理函数,调用waitpid回收子进程资源。
内容的提问来源于stack exchange,提问作者daryldxn
相关产品推荐
相关产品推荐

