前言

现有两台Ubuntu服务器,一台名叫TcpServer,一台名叫TcpClient。
TcpServer用于监听Tcp连接,TcpClient用于发起Tcp连接。
现在想测试TcpServer是否能承受住10w+ TCP连接。

编程语言:C#
使用框架:TouchSocket

image.png

准备工作

安装DotNet6环境

请查看:Ubuntu 安装DotNet6步骤

服务端代码

using System.Text;
using TouchSocket.Core.Config;
using TouchSocket.Core.Dependency;
using TouchSocket.Core.Log;
using TouchSocket.Sockets;

namespace TcpServer
{
    internal class Program
    {
        //用于记录客户端数量
        private static int _count = 0;
        //阻塞事件,防止客户端退出
        private static readonly AutoResetEvent _closingEvent = new AutoResetEvent(false);
        static void Main(string[] args)
        {
            TcpService service = new TcpService();
            service.Connected += (client, e) => 
            {
                Interlocked.Increment(ref _count);
                Console.WriteLine($"有客户端连接:{((SocketClient)client).GetIPPort()} ---- 客户端数量:{_count}");
            
            };
            service.Disconnected += (client, e) => 
            {
                Interlocked.Decrement(ref _count);
                Console.WriteLine($"有客户断开:{((SocketClient)client).GetIPPort()} ---- 客户端数量:{_count}");
            };
            service.Setup( new TouchSocketConfig()
                .SetListenIPHosts(new IPHost[] {new IPHost(7790) })
                .SetMaxCount(999999)
                //这里很重要,否则一分钟后服务端会删除无活动的连接
                .SetClearInterval(-1))
                .Start();

            _closingEvent.WaitOne();
        }
    }
}

image.png

客户端代码

using System.Net.Sockets;
using System.Text;
using TouchSocket.Core.Config;
using TouchSocket.Sockets;

namespace TestTcpClient
{
    internal class Program
    {
        //用于记录连接数量
        private static int _count = 0;
        private static readonly AutoResetEvent _closingEvent = new AutoResetEvent(false);
        static void Main(string[] args)
        {
            for (int i = 0; i < 100000; i++)
            {
                try
                {
                    TouchSocket.Sockets.TcpClient tcpClient = new TouchSocket.Sockets.TcpClient();
                    tcpClient.Connected += (client, e) => 
                    {
                        Interlocked.Increment(ref _count);
                        Console.WriteLine($"当前连接数:{_count}");
                    };
                    tcpClient.Disconnected += (client, e) => 
                    {
                        Interlocked.Decrement(ref _count);
                        Console.WriteLine($"当前连接数:{_count}");
                    };
                    tcpClient.Setup("1.14.107.247:7790");
                    tcpClient.Connect();
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"{i}:{ex.Message}");
                    if(ex.InnerException != null)
                    {
                        Console.WriteLine($"{i}:{ex.InnerException.Message}");
                    }
                }
            }
            _closingEvent.WaitOne();  
        }
    }
}

image.png

编译

将TcpServer和TcpClient编译后分别放入TcpServer服务器和TcpClient服务器

image.png

image.png

测试记录

失败尝试1(Linux可用端口范围限制)

运行TcpServer:dotnet TcpServer.dll
运行TcpClient:dotnet TestTcpClient.dll

从服务端记录可以看到,客户端数量在28232时候不再增加了
image.png

客户端疯狂抛出异常 The Operation has timed out
image.png

上述原因是因为TcpClient服务器限制了端口范围

解决Linux端口范围限制

查看端口范围

#查看端口范围
sysctl net.ipv4.ip_local_port_range

image.png

可以看到端口范围在32768到60999之间
60999-32768=28231 正好印证了我们的猜想

修改端口范围

vim /etc/sysctl.conf

追加记录
net.ipv4.ip_local_port_range = 1024 65535

需要注意的是1024之前是系统保留端口,开始值不能小于1024,结束值不能大于65535否则会报错

image.png

保存后使用sysctl -p使修改立即生效

sysctl -p

再次使用命令sysctl net.ipv4.ip_local_port_range查看端口范围

sysctl net.ipv4.ip_local_port_range

可以看到刚才的设置成功了
image.png

失败尝试2(可用端口耗尽)

再次运行TcpServer:dotnet TcpServer.dll
再次运行TcpClient:dotnet TestTcpClient.dll

这次明显有了改善,从服务端记录可以看到,客户端数量在64511时候不再增加了

image.png

客户端依旧疯狂报错:the operation has timed out
image.png

这次的问题依旧在客户端,不过和上一次失败不太一样,上次是被Linux可用端口范围限制了,这次是真的把客户端端口耗尽了。

而我们的目标是单机10w连接,得想办法突破客户端65535端口数限制。

我们知道TCP协议由四元组组成,任何一个改变都视为一个新的连接:


<Server 端口>

<Client 端口>

所以我们有两种方案实现单机10W连接

方案一、改变 Server 端口
方案二、改变 Client IP

方案一、改变Server端口

当服务端监听7791端口时,客户端允许分配65535个端口建立连接
当服务端监听7792端口时,客户端将再次允许分配65535个端口建立连接
所以我们只需要对TcpServer代码进行改造增加监听端口即可.

修改TcpServer代码

TcpServer代码如下,监听7791 7792 7793三个端口

using System.Text;
using TouchSocket.Core.Config;
using TouchSocket.Core.Dependency;
using TouchSocket.Core.Log;
using TouchSocket.Sockets;

namespace TcpServer
{
    internal class Program
    {
        //用于记录客户端数量
        private static int _count = 0;
        //阻塞事件,防止客户端退出
        private static readonly AutoResetEvent _closingEvent = new AutoResetEvent(false);
        static void Main(string[] args)
        {
            TcpService service = new TcpService();
            service.Connected += (client, e) => 
            {
                Interlocked.Increment(ref _count);
                Console.WriteLine($"有客户端连接:{client.GetIPPort()} 目标端口:{client.ServicePort} ---- 客户端数量:{_count}");
            
            };
            service.Disconnected += (client, e) => 
            {
                Interlocked.Decrement(ref _count);
                Console.WriteLine($"有客户断开:{client.GetIPPort()} ---- 客户端数量:{_count}");
            };
            service.Setup( new TouchSocketConfig()
                .SetListenIPHosts(new IPHost[] {new IPHost(7791), new IPHost(7792), new IPHost(7793) })
                .SetMaxCount(999999)
                //这里很重要,否则一分钟后服务端会删除无活动的连接
                .SetClearInterval(-1))
                .Start();

            _closingEvent.WaitOne();
        }
    }
}

image.png

修改TcpClient代码

using System.Net.Sockets;
using System.Text;
using TouchSocket.Core.Config;
using TouchSocket.Sockets;

namespace TestTcpClient
{
    internal class Program
    {
        //用于记录连接数量
        private static int _count = 0;
        private static readonly AutoResetEvent _closingEvent = new AutoResetEvent(false);
        static void Main(string[] args)
        {
            for (int i = 1; i <= 120000; i++)
            {
                try
                {
                    TouchSocket.Sockets.TcpClient tcpClient = new TouchSocket.Sockets.TcpClient();
                    tcpClient.Connected += (client, e) =>
                    {
                        Interlocked.Increment(ref _count);
                        Console.WriteLine($"当前连接数:{_count}");
                    };
                    tcpClient.Disconnected += (client, e) =>
                    {
                        Interlocked.Decrement(ref _count);
                        Console.WriteLine($"当前连接数:{_count}");
                    };
                    if (i <= 40000)
                    {
                        tcpClient.Setup("1.14.107.247:7791");
                    }
                    if (i > 40000)
                    {
                        tcpClient.Setup("1.14.107.247:7792");
                    }
                    if (i > 80000)
                    {
                        tcpClient.Setup("1.14.107.247:7793");
                    }
                    tcpClient.Connect();
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"{i}:{ex.Message}");
                    if (ex.InnerException != null)
                    {
                        Console.WriteLine($"{i}:{ex.InnerException.Message}");
                    }
                }
            }
            _closingEvent.WaitOne();
        }
    }
}

image.png

再次测试(完成目标)

注意:如果您测试发现创建不了10w+连接,大概率是服务器内存满了,可以尝试加大服务器内存或者降低每个连接缓冲区大小

image.png

image.png

进阶测试(测试TCP连接上限)

服务端代码

using System.Text;
using TouchSocket.Core.Config;
using TouchSocket.Core.Dependency;
using TouchSocket.Core.Log;
using TouchSocket.Sockets;

namespace TcpServer
{
    internal class Program
    {
        //用于记录客户端数量
        private static int _count = 0;
        //阻塞事件,防止客户端退出
        private static readonly AutoResetEvent _closingEvent = new AutoResetEvent(false);
        static void Main(string[] args)
        {
            TcpService service = new TcpService();
            service.Connected += (client, e) => 
            {
                Interlocked.Increment(ref _count);
                Console.WriteLine($"有客户端连接:{client.GetIPPort()} 目标端口:{client.ServicePort} ---- 客户端数量:{_count}");
            
            };
            service.Disconnected += (client, e) => 
            {
                Interlocked.Decrement(ref _count);
                Console.WriteLine($"有客户断开:{client.GetIPPort()} ---- 客户端数量:{_count}");
            };
            service.Setup( new TouchSocketConfig()
                .SetListenIPHosts(new IPHost[] {
                    new IPHost(7790),
                    new IPHost(7791), 
                    new IPHost(7792), 
                    new IPHost(7793),
                    new IPHost(7794),
                    new IPHost(7795),
                    new IPHost(7796),
                    new IPHost(7797),
                    new IPHost(7798),
                    new IPHost(7799),
                    new IPHost(7780),
                    new IPHost(7781),
                    new IPHost(7782),
                    new IPHost(7783),
                    new IPHost(7784),
                    new IPHost(7785),
                    new IPHost(7786),
                    new IPHost(7787),
                    new IPHost(7788),
                    new IPHost(7789),
                })
                .SetMaxCount(999999)
                //这里很重要,减少缓冲区大小防止把内存跑满了
                .SetBufferLength(64)
                //这里很重要,否则一分钟后服务端会删除无活动的连接
                .SetClearInterval(-1))
                .Start();

            _closingEvent.WaitOne();
        }
    }
}

客户端代码

using System.Net.Sockets;
using System.Text;
using TouchSocket.Core.Config;
using TouchSocket.Sockets;

namespace TestTcpClient
{
    internal class Program
    {
        //用于记录连接数量
        private static int _count = 0;
        private static readonly AutoResetEvent _closingEvent = new AutoResetEvent(false);
        static void Main(string[] args)
        {
            for (int i = 1; i <= 1000000; i++)
            {
                try
                {
                    TouchSocket.Sockets.TcpClient tcpClient = new TouchSocket.Sockets.TcpClient();
                    tcpClient.Connected += (client, e) =>
                    {
                        Interlocked.Increment(ref _count);
                        Console.WriteLine($"当前连接数:{_count}");
                    };
                    tcpClient.Disconnected += (client, e) =>
                    {
                        Interlocked.Decrement(ref _count);
                        Console.WriteLine($"当前连接数:{_count}");
                    };
                    //声明配置
                    TouchSocketConfig config = new TouchSocketConfig();
                    if (i <= 50000)
                    {
                        config.SetRemoteIPHost(new IPHost("1.14.107.247:7790")).SetBufferLength(64);
                        tcpClient.Setup(config);
                    }
                    if (i > 100000)
                    {
                        config.SetRemoteIPHost(new IPHost("1.14.107.247:7791")).SetBufferLength(64);
                        tcpClient.Setup(config);
                    }
                    if (i > 150000)
                    {
                        config.SetRemoteIPHost(new IPHost("1.14.107.247:7792")).SetBufferLength(64);
                        tcpClient.Setup(config);
                    }
                    if (i > 200000)
                    {
                        config.SetRemoteIPHost(new IPHost("1.14.107.247:7793")).SetBufferLength(64);
                        tcpClient.Setup(config);
                    }
                    if (i > 250000)
                    {
                        config.SetRemoteIPHost(new IPHost("1.14.107.247:7797")).SetBufferLength(64);
                        tcpClient.Setup(config);
                    }
                    if (i > 300000)
                    {
                        config.SetRemoteIPHost(new IPHost("1.14.107.247:7795")).SetBufferLength(64);
                        tcpClient.Setup(config);
                    }
                    if (i > 350000)
                    {
                        config.SetRemoteIPHost(new IPHost("1.14.107.247:7796")).SetBufferLength(64);
                        tcpClient.Setup(config);
                    }
                    if (i > 400000)
                    {
                        config.SetRemoteIPHost(new IPHost("1.14.107.247:7797")).SetBufferLength(64);
                        tcpClient.Setup(config);
                    }
                    if (i > 450000)
                    {
                        config.SetRemoteIPHost(new IPHost("1.14.107.247:7798")).SetBufferLength(64);
                        tcpClient.Setup(config);
                    }
                    if (i > 500000)
                    {
                        config.SetRemoteIPHost(new IPHost("1.14.107.247:7799")).SetBufferLength(64);
                        tcpClient.Setup(config);
                    }
                    if (i > 550000)
                    {
                        config.SetRemoteIPHost(new IPHost("1.14.107.247:7780")).SetBufferLength(64);
                        tcpClient.Setup(config);
                    }
                    if (i > 600000)
                    {
                        config.SetRemoteIPHost(new IPHost("1.14.107.247:7781")).SetBufferLength(64);
                        tcpClient.Setup(config);
                    }
                    if (i > 650000)
                    {
                        config.SetRemoteIPHost(new IPHost("1.14.107.247:7782")).SetBufferLength(64);
                        tcpClient.Setup(config);
                    }
                    if (i > 700000)
                    {
                        config.SetRemoteIPHost(new IPHost("1.14.107.247:7783")).SetBufferLength(64);
                        tcpClient.Setup(config);
                    }
                    if (i > 750000)
                    {
                        config.SetRemoteIPHost(new IPHost("1.14.107.247:7784")).SetBufferLength(64);
                        tcpClient.Setup(config);
                    }
                    if (i > 800000)
                    {
                        config.SetRemoteIPHost(new IPHost("1.14.107.247:7785")).SetBufferLength(64);
                        tcpClient.Setup(config);
                    }
                    if (i > 850000)
                    {
                        config.SetRemoteIPHost(new IPHost("1.14.107.247:7786")).SetBufferLength(64);
                        tcpClient.Setup(config);
                    }
                    if (i > 900000)
                    {
                        config.SetRemoteIPHost(new IPHost("1.14.107.247:7787")).SetBufferLength(64);
                        tcpClient.Setup(config);
                    }
                    if (i > 950000)
                    {
                        config.SetRemoteIPHost(new IPHost("1.14.107.247:7788")).SetBufferLength(64);
                        tcpClient.Setup(config);
                    }
                    if (i > 1000000)
                    {
                        config.SetRemoteIPHost(new IPHost("1.14.107.247:7789")).SetBufferLength(64);
                        tcpClient.Setup(config);
                    }
                    tcpClient.Connect();
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"{i}:{ex.Message}");
                    if (ex.InnerException != null)
                    {
                        Console.WriteLine($"{i}:{ex.InnerException.Message}");
                    }
                }
            }
            _closingEvent.WaitOne();
        }
    }
}

测试结果,当前配置服务器极限大概能跑364510个TCP连接

image.png

查看内核对象发现使用率基本满了

slabtop

image.png

方案二、改变ClientIP

有时服务端受限于只能监听某一个端口,不能使用方案一。
此时我们可以给TcpCient服务器绑定多个外网IP解决65535端口数限制。
比如给TcpClient服务器绑定两个外网IP,并在TcpClient服务器创建两个网卡,不同网卡走不同外网IP。
此时TcpClient只需要前60000个请求走网卡1,后60000个请求走网卡二即可实现绕过65535端口数限制。接下来实操下。

腾讯云申请弹性网卡

参考链接:https://cloud.tencent.com/document/product/576/59353#ubuntu

image.png

腾讯云申请外网ip并绑定弹性网卡

image.png

修改TcpClient代码

在服务器查看配置好的IP和网卡
局域网IP分别是172.27.0.5和172.27.0.14
image.png

接下来修改TcpClient代码

using System.Net.Sockets;
using System.Text;
using TouchSocket.Core.Config;
using TouchSocket.Sockets;

namespace TestTcpClient
{
    internal class Program
    {
        //用于记录连接数量
        private static int _count = 0;
        private static readonly AutoResetEvent _closingEvent = new AutoResetEvent(false);
        static void Main(string[] args)
        {
            for (int i = 1; i <= 1000000; i++)
            {
                try
                {
                    TouchSocket.Sockets.TcpClient tcpClient = new TouchSocket.Sockets.TcpClient();
                    tcpClient.Connected += (client, e) =>
                    {
                        Interlocked.Increment(ref _count);
                        Console.WriteLine($"当前连接数:{_count}");
                    };
                    tcpClient.Disconnected += (client, e) =>
                    {
                        Interlocked.Decrement(ref _count);
                        Console.WriteLine($"当前连接数:{_count}");
                    };
                    TouchSocketConfig config = new TouchSocketConfig();
                    if (i <= 60000)
                    {
                        config.SetRemoteIPHost(new IPHost("1.14.107.247:7790"))
                            .SetBufferLength(64)
                            //通过网卡1建立连接
                            .SetBindIPHost("172.27.0.5:0");
                        tcpClient.Setup(config);
                    }
                    if (i > 60000)
                    {
                        config.SetRemoteIPHost(new IPHost("1.14.107.247:7790"))
                            .SetBufferLength(64)
                            //通过网卡2建立连接
                             .SetBindIPHost("172.27.0.14:0");
                        tcpClient.Setup(config);
                    }
                    tcpClient.Connect();
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"{i}:{ex.Message}");
                    if (ex.InnerException != null)
                    {
                        Console.WriteLine($"{i}:{ex.InnerException.Message}");
                    }
                }
            }
            _closingEvent.WaitOne();
        }
    }
}

image.png

再次测试(完成目标)

image.png

结语

其他设置

查阅其他文章还提到修改下面的参数,
不过本文并没用到,先记录下。

vim /etc/sysctl.conf

#系统最大句柄数
fs.file-max=1100000
#最大进程数
fs.nr_open=1100000

#使设置生效
sysctl -p

vim /etc/security/limits.conf

root soft nproc 1010000
root hard nproc 1010000
root soft nofile 1010000
root hard nofile 1010000

Windows为什么不行

假如将TcpClient放到本地windows下去连接服务端。
我们会发现建立3000个左右连接就无法继续创建了,打开网页也非常卡。
初步推测是路由器防局域网病毒之类的策略导致的,后续再研究。

源码下载:

链接:https://pan.baidu.com/s/1TdmFGxv_zCDp05PQMvGSrQ?pwd=8ajf
提取码:8ajf

参考资料:
https://blog.csdn.net/zhangyanfei01/article/details/114257045
https://mp.weixin.qq.com/s/fxy4S78Xw54y0iU_HsOosA
https://blog.51cto.com/u_15060546/2641200
https://blog.csdn.net/s_zhchluo/article/details/118415011

原文地址:http://www.cnblogs.com/fuhua/p/16904864.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性