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

.NET Socket Listen backlog未正常工作:10k并发连接受限问题

无法实现10000个TCP并发连接的问题

我正在测试一个用C#编写的简单TCP服务器,目标是支持10000个客户端并发连接。已将Socket.Listen的backlog参数设为int.MaxValue,但测试时仅约2000个客户端能成功连接,其余均抛出ConnectionRefused错误,调整backlog值也无改善。


服务器代码

public class HighPerfTcpServer
{
    private Socket? _listeningSocket;

    private ILogger<HighPerfTcpServer> _logger;

    public HighPerfTcpServer(ILogger<HighPerfTcpServer> logger)
    {
        _logger = logger;
    }

    public async Task<int> Start(TcpServerOptions options, ITcpServerSessionsHandler sessionHandler)
    {
        var address = options.Address ?? "127.0.0.1";
        var port = options.Port ?? 0;
        var ipAddress = IPAddress.Parse(address);
        var localEndPoint = new IPEndPoint(ipAddress, port);

        _listeningSocket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

        _listeningSocket.Bind(localEndPoint);
        _listeningSocket.Listen(int.MaxValue);

        var acceptArgs = new SocketAsyncEventArgs();

        acceptArgs.Completed += AcceptArgs_Completed;

        StartAccept(acceptArgs);

        var currentLocalEndPoint = _listeningSocket.LocalEndPoint as IPEndPoint;

        ArgumentNullException.ThrowIfNull(currentLocalEndPoint);

        return currentLocalEndPoint.Port;
    }

    private void StartAccept(SocketAsyncEventArgs acceptArgs)
    {
        if (_listeningSocket == null)
        {
            return;
        }

        var willRaiseEvent = false;

        while (!willRaiseEvent)
        {
            acceptArgs.AcceptSocket = null;

            try
            {
                willRaiseEvent = _listeningSocket.AcceptAsync(acceptArgs);

                if (!willRaiseEvent)
                {
                    ProcessAccept(acceptArgs);
                }
            }
            catch
            {
                break;
            }
        }
    }

    private void ProcessAccept(SocketAsyncEventArgs acceptArgs)
    {
        _logger.LogDebug($"client connected");
    }

    private void AcceptArgs_Completed(object? sender, SocketAsyncEventArgs e)
    {
        if (e.LastOperation == SocketAsyncOperation.Accept)
        {
            ProcessAccept(e);

            StartAccept(e);
        }
    }

    public async Task Stop()
    {
        _listeningSocket?.Dispose();
    }
}

客户端代码

public class HighPerfTcpClient
{
    private TaskCompletionSource? _connectAwaiter;
    private TaskCompletionSource? _disconnectAwaiter;
    private Socket? _clientSocket;

    public async Task<ITcpClientConnection> Connect(TcpClientOptions options)
    {
        ArgumentNullException.ThrowIfNull(options.Address);
        ArgumentNullException.ThrowIfNull(options.Port);

        var ipAddress = IPAddress.Parse(options.Address);
        var remoteEndPoint = new IPEndPoint(ipAddress, options.Port.Value);

        _clientSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);

        var connectEventArgs = new SocketAsyncEventArgs();

        connectEventArgs.Completed += ConnectEventArgs_Completed;
        connectEventArgs.RemoteEndPoint = remoteEndPoint;

        _connectAwaiter = new TaskCompletionSource();

        var willRaiseEvent = _clientSocket.ConnectAsync(connectEventArgs);

        if (willRaiseEvent)
        {
            await _connectAwaiter.Task.ConfigureAwait(false);
        }

        if (connectEventArgs.SocketError != SocketError.Success)
        {
            throw new Exception($"socket connect error: {connectEventArgs.SocketError}");
        }

        return new TcpClientConnection(DataSender);
    }

    private async Task DataSender(ArraySegment<byte> segment)
    {
    
    }

    private void ConnectEventArgs_Completed(object? sender, SocketAsyncEventArgs e)
    {
        if (e.LastOperation == SocketAsyncOperation.Connect)
        {
            _connectAwaiter?.SetResult();
        }
    }

    public async Task Disconnect()
    {
        if (_clientSocket != null)
        {
            var disconnectArgs = new SocketAsyncEventArgs();

            disconnectArgs.Completed += DisconnectArgs_Completed;

            _disconnectAwaiter = new TaskCompletionSource();

            var willRaiseEvent = _clientSocket.DisconnectAsync(disconnectArgs);

            if (willRaiseEvent)
            {
                await _disconnectAwaiter.Task.ConfigureAwait(false);
            }
        }
    }

    private void DisconnectArgs_Completed(object? sender, SocketAsyncEventArgs e)
    {
        if (e.LastOperation == SocketAsyncOperation.Disconnect)
        {
            _disconnectAwaiter?.SetResult();
        }
    }
}

测试用例代码

public class TcpClientServerIntegrationTests
{
    private static ILoggerFactory _factory;

    [Fact]
    public async Task Should_be_able_to_up_server_and_clients_connect_and_disconnect()
    {
        var testLogger = GetLogger<TcpClientServerIntegrationTests>();

        var sessionHandlerMock = new Mock<ITcpServerSessionsHandler>();
        var server = new HighPerfTcpServer(GetLogger<HighPerfTcpServer>());

        var connections = 10 * 1000;

        var port = 10000;

        var tcpServerOptions = new TcpServerOptions
        {
            Address = "127.0.0.1",
            ConnectionBufferSize = 128 * 1024,
            MaxConnections = connections,
            Port = port
        };

        var startedPort = await server.Start(tcpServerOptions, sessionHandlerMock.Object);

        var tasks = Enumerable.Range(0, connections).Select(e =>
        {
            return Task.Run(async () =>
            {
                var client = new HighPerfTcpClient();
                var clientOptions = new TcpClientOptions
                {
                    Address = "127.0.0.1",
                    Port = startedPort,
                    BufferSize = 128 * 1024,
                    OnData = OnClientData,
                    OnDisconnect = OnDisconnect
                };

                await client.Connect(clientOptions);

                testLogger.LogDebug($"{e} client connected");

                await client.Disconnect();

                testLogger.LogDebug($"{e} client disconnected");
            });
        });

        await Task.WhenAll(tasks);
    }

    private async Task OnDisconnect()
    {

    }

    private async Task OnClientData(ArraySegment<byte> segment)
    {

    }

    private ILogger<T> GetLogger<T>()
    {
        return _factory.CreateLogger<T>();
    }

    static TcpClientServerIntegrationTests()
    {
        var configuration = new LoggingConfiguration();
        var target = new FileTarget();

        target.FileName = "./logs/log.${logger}.txt";
        target.KeepFileOpen = false;

        configuration.AddRule(NLog.LogLevel.Debug, NLog.LogLevel.Error, target);

        _factory = LoggerFactory.Create(builder =>
        {
            builder.AddNLog(configuration);
        });
    }
}

问题原因及解决方案

1. backlog参数的实际限制

设置的int.MaxValue不会被系统完全采纳,操作系统对backlog有内置上限:

  • Windows系统默认将backlog截断为200(部分新版本提升至16384),超出部分会被忽略。
  • backlog仅控制**未完成连接队列(SYN_RCVD状态)**的大小,**已完成连接队列(ESTABLISHED状态)**的容量由系统独立参数控制,若服务器不及时处理已完成连接,队列满后会直接拒绝新连接。

2. 服务器未处理已接受的Socket

ProcessAccept方法仅打印日志,未对acceptArgs.AcceptSocket做任何处理:

  • 每个已接受的Socket会占用系统文件句柄,Windows进程默认句柄上限约为10000,短时间内大量未释放的Socket会快速耗尽资源,导致后续无法Accept新连接。
  • 未关闭的Socket会维持ESTABLISHED状态,持续占用TCP连接资源,进一步压缩并发空间。

3. 客户端临时端口耗尽

客户端连接时使用系统分配的临时端口(Windows默认范围49152-65535,共16384个):

  • 测试中短时间创建10000个连接,即使客户端调用Disconnect,Socket仍会进入TIME_WAIT状态(默认持续240秒),临时端口无法立即复用,导致端口耗尽,后续连接失败。

4. 系统TCP参数限制

Windows默认TCP参数对并发连接有诸多限制,例如:

  • MaxUserPort:临时端口范围上限,默认值偏小。
  • TcpTimedWaitDelay:TIME_WAIT状态持续时间,默认240秒过长。
  • TcpNumConnections:系统允许的最大TCP连接数,默认值可能不足。

解决方案

  1. 正确处理服务器的已接受Socket
    在ProcessAccept中关闭或管理Socket(若无需保持连接):

    private void ProcessAccept(SocketAsyncEventArgs acceptArgs)
    {
        _logger.LogDebug($"client connected");
        // 释放已接受的Socket资源
        acceptArgs.AcceptSocket?.Close();
        acceptArgs.AcceptSocket?.Dispose();
    }
    
  2. 调整系统TCP参数(Windows)
    通过注册表修改以下参数(需管理员权限):

    • HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\MaxUserPort:设为65535
    • HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\TcpTimedWaitDelay:设为30(秒)
    • HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\TcpNumConnections:设为65535
  3. 优化客户端连接逻辑
    避免短时间内并发创建大量连接,可采用连接池复用,或增加连接间隔。

  4. 调整进程句柄限制
    在Windows任务管理器中,找到测试进程的“详细信息”,右键选择“设置优先级”->“详细信息”,调整“句柄数”上限(或通过组策略修改)。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 10:54:51