C# TcpListener积压概念的误解

C# TcpListener积压概念的误解,c#,asynchronous,client-server,C#,Asynchronous,Client Server,我试图理解TcpListener类的backlog参数,但我很难同时实现最大数量的挂起连接,以便对其进行测试 我有一个示例异步服务器和客户端代码。表示backlog是挂起连接队列的最大长度。我让服务器一直监听连接,而客户端连接了30次。我所期望的是在第20个请求之后在客户端抛出一个SocketException,因为backlog被设置为20。为什么它不阻止它 我的第二个误解是,假设有一个大约需要10秒的缓慢操作,例如通过TCP发送文件,我真的需要将已接受连接的逻辑放在一个新线程中吗?目前,我将

我试图理解
TcpListener
类的backlog参数,但我很难同时实现最大数量的挂起连接,以便对其进行测试

我有一个示例异步服务器和客户端代码。表示backlog是挂起连接队列的最大长度。我让服务器一直监听连接,而客户端连接了30次。我所期望的是在第20个请求之后在客户端抛出一个
SocketException
,因为backlog被设置为20。为什么它不阻止它

我的第二个误解是,假设有一个大约需要10秒的缓慢操作,例如通过TCP发送文件,我真的需要将已接受连接的逻辑放在一个新线程中吗?目前,我将我的逻辑放在一个
新线程中
,我知道这不是最好的解决方案,相反,我应该使用
线程池
,但问题是主要的。我通过将客户端的循环更改为1000次迭代来测试它,如果我的逻辑不在新线程中,则连接在第200次连接后会被阻塞,这可能是因为thread.Sleep每次将主线程的速度减慢10秒,并且主线程负责所有accept回调。因此,基本上,我自己解释如下:如果我想使用相同的概念,我必须像我那样将我的AcceptCallback逻辑放入一个新线程中,或者我必须像这里接受的答案那样做:。我说得对吗

服务器代码:

using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace Server
{
    class Program
    {
        private static readonly ManualResetEvent _mre = new ManualResetEvent(false);

        static void Main(string[] args)
        {
            TcpListener listener = new TcpListener(IPAddress.Any, 80);

            try
            {
                listener.Start(20); 

                while (true)
                {
                    _mre.Reset();

                    Console.WriteLine("Waiting for a connection...");
                    listener.BeginAcceptTcpClient(new AsyncCallback(AcceptCallback), listener);

                    _mre.WaitOne();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        private static void AcceptCallback(IAsyncResult ar)
        {
            _mre.Set();

            TcpListener listener = (TcpListener)ar.AsyncState;
            TcpClient client = listener.EndAcceptTcpClient(ar);

            IPAddress ip = ((IPEndPoint)client.Client.RemoteEndPoint).Address;
            Console.WriteLine($"{ip} has connected!");

            // Actually I changed it to ThreadPool
            //new Thread(() =>
            //{
            //  Console.WriteLine("Sleeping 10 seconds...");
            //  Thread.Sleep(10000);
            //  Console.WriteLine("Done");
            //}).Start();

            ThreadPool.QueueUserWorkItem(new WaitCallback((obj) =>
            {
                Console.WriteLine("Sleeping 10 seconds...");
                Thread.Sleep(10000);
                Console.WriteLine("Done");
            }));

            // Close connection
            client.Close();
        }
    }
}
客户端代码:

using System;
using System.Net.Sockets;

namespace Client
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 30; i++)
            {
                Console.WriteLine($"Connecting {i}");

                using (TcpClient client = new TcpClient()) // because once we are done, we have to close the connection with close.Close() and in this way it will be executed automatically by the using statement
                {
                    try
                    {
                        client.Connect("localhost", 80);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                    }
                }
            }

            Console.ReadKey();
        }
    }
}
客户:

using System;
using System.Net.Sockets;
using System.Text;

namespace Client
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 33; i++)
            {
                Console.WriteLine($"Connecting {i}");

                using (TcpClient client = new TcpClient()) // once we are done, the using statement will do client.Close()
                {
                    try
                    {
                        client.Connect("localhost", 80);

                        using (NetworkStream ns = client.GetStream())
                        {
                            byte[] bytes = new byte[100];
                            int readBytes = ns.Read(bytes, 0, bytes.Length);
                            string result = Encoding.Unicode.GetString(bytes, 0, readBytes);
                            Console.WriteLine(result);
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                    }
                }
            }

            Console.ReadKey();
        }
    }
}
使用系统;
使用System.Net.Sockets;
使用系统文本;
命名空间客户端
{
班级计划
{
静态void Main(字符串[]参数)
{
对于(int i=0;i<33;i++)
{
Console.WriteLine($“连接{i}”);
using(TcpClient client=new TcpClient())//完成后,using语句将执行client.Close()
{
尝试
{
client.Connect(“localhost”,80);
使用(NetworkStream ns=client.GetStream())
{
字节[]字节=新字节[100];
int readBytes=ns.Read(字节,0,字节.长度);
字符串结果=Encoding.Unicode.GetString(字节,0,readBytes);
控制台写入线(结果);
}
}
捕获(例外情况除外)
{
控制台写入线(例如消息);
}
}
}
Console.ReadKey();
}
}
}
并告诉操作系统在
接受队列中允许的最大套接字数

传入连接由TCP/IP堆栈放置在此队列中,并在服务器调用
Accept
处理新连接时删除

在您的问题中,两个版本的服务器代码都从主线程在一个循环中调用
Accept
,并等待
AcceptCallback
启动,然后再进行另一个Accept调用。这会导致队列的快速排空

要演示侦听队列溢出,最简单的方法是降低服务器的接受率-例如,将其降至零:

    var serverEp = new IPEndPoint(IPAddress.Loopback, 34567);
    var serverSocket = new TcpListener(serverEp);        
    serverSocket.Start(3);
    for (int i = 1; i <= 10; i++)
    {
        var clientSocket = new TcpClient();
        clientSocket.Connect(serverEp);
        Console.WriteLine($"Connected socket {i}");
    }   
和告诉操作系统
接受队列中允许的最大套接字数

传入连接由TCP/IP堆栈放置在此队列中,并在服务器调用
Accept
处理新连接时删除

在您的问题中,两个版本的服务器代码都从主线程在一个循环中调用
Accept
,并等待
AcceptCallback
启动,然后再进行另一个Accept调用。这会导致队列的快速排空

要演示侦听队列溢出,最简单的方法是降低服务器的接受率-例如,将其降至零:

    var serverEp = new IPEndPoint(IPAddress.Loopback, 34567);
    var serverSocket = new TcpListener(serverEp);        
    serverSocket.Start(3);
    for (int i = 1; i <= 10; i++)
    {
        var clientSocket = new TcpClient();
        clientSocket.Connect(serverEp);
        Console.WriteLine($"Connected socket {i}");
    }   

您的客户端在连接后立即关闭连接,并且连接是串行的。为了测试积压工作,您需要同时启动几个连接,并保持连接打开一段时间。一旦我在主线程中放置了一个线程。睡眠,这种情况就会发生<代码>连接202无法建立连接,因为目标计算机主动拒绝127.0.0.1:80
。它不能处理超过200个连接。这200个连接可能是(cosumer)操作系统的限制。服务器操作系统应该允许更多。哦,你似乎是对的。我将backlog改为20,它在早些时候(20日)阻止了它,但有趣的是,如果我将backlog改为1000,它将继续阻止它在200。所以Win 10 Pro 1809上的最大积压价值似乎是200。一个解决方案将是来自中国的答案。第二个问题呢?据我所知,AcceptCallback已经在线程池线程中运行(与侦听器不同)。您可以打印出线程id进行确认。但是,对于高性能、高容量的服务器,您可能需要查看。您的客户端在连接后立即关闭连接,并且连接是串行的。为了测试积压工作,您需要同时启动几个连接,并保持连接打开一段时间。一旦我在主线程中放置了一个线程。睡眠,这种情况就会发生<代码>连接202无法建立连接,因为目标计算机主动拒绝127.0.0.1:80
。它不能处理超过200个连接。这200个连接可能是(cosumer)操作系统的限制。服务器操作系统应该允许更多。哦,你似乎是对的。我将backlog改为20,它在早些时候(20日)阻止了它,但有趣的是,如果我将backlog改为1000,它将继续阻止它在200。所以Win 10 Pro 1809上的最大积压价值似乎是200。一个解决方案将是来自中国的答案。第二个问题呢?AcceptCallback已经在threadpoo中运行了
    static async Task Main(string[] args)
    {
        var server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        server.Bind(new IPEndPoint(IPAddress.Any, 80));
        server.Listen(5);            
        while (true)
        {
            var client = await server.AcceptAsync();
            var backTask = ProcessClient(client); 
        }  
    }

    private static async Task ProcessClient(Socket socket)
    {
        using (socket)
        {
            var ip = ((IPEndPoint)(socket.RemoteEndPoint)).Address;
            Console.WriteLine($"{ip} has connected!");

            var buffer = Encoding.Unicode.GetBytes("test");
            await socket.SendAsync(buffer, SocketFlags.None);
        }
        Console.WriteLine("Connection closed.");            
    }