You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何设置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等待新连接。这意味着:

  1. 每当客户端发起连接,三次握手完成后,accept会立刻把连接从已完成队列(ACCEPT队列)取出,交给子进程处理
  2. 已完成队列始终处于空或接近空的状态,未完成队列也不会被占满
  3. 只要系统资源足够,服务端就能不断接受新的客户端连接,不受listen参数的限制

如果想要限制同时在线的客户端数量,你需要自己维护一个计数器:

  • 每当accept成功时,计数器加1
  • 当计数器达到上限时,拒绝后续的accept(或者关闭新连接)
  • 当客户端断开连接时,计数器减1(注意需要处理子进程退出的信号,比如SIGCHLD,在信号处理函数中更新计数器)

另外,你的服务端没有处理子进程退出的僵尸进程问题,长时间运行会积累大量僵尸进程,建议添加SIGCHLD信号处理函数,调用waitpid回收子进程资源。

内容的提问来源于stack exchange,提问作者daryldxn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 02:20:05