如何设置 TcpListener 始终监听并接受多个连接?

Posted

技术标签:

【中文标题】如何设置 TcpListener 始终监听并接受多个连接?【英文标题】:How to set up TcpListener to always listen and accept multiple connections? 【发布时间】:2013-10-23 14:29:06 【问题描述】:

这是我的服务器应用程序:

public static void Main()

    try
    
        IPAddress ipAddress = IPAddress.Parse("127.0.0.1");

        Console.WriteLine("Starting TCP listener...");

        TcpListener listener = new TcpListener(ipAddress, 500);

        listener.Start();

        while (true)
        
            Console.WriteLine("Server is listening on " + listener.LocalEndpoint);

            Console.WriteLine("Waiting for a connection...");

            Socket client = listener.AcceptSocket();

            Console.WriteLine("Connection accepted.");

            Console.WriteLine("Reading data...");

            byte[] data = new byte[100];
            int size = client.Receive(data);
            Console.WriteLine("Recieved data: ");
            for (int i = 0; i < size; i++)
                Console.Write(Convert.ToChar(data[i]));

            Console.WriteLine();

            client.Close();
        

        listener.Stop();
    
    catch (Exception e)
    
        Console.WriteLine("Error: " + e.StackTrace);
        Console.ReadLine();
    

如您所见,它总是在工作时收听,但我想指定我希望应用能够同时收听并支持多个连接。

如何修改它以在接受多个连接的同时不断收听?

【问题讨论】:

是否指定127.0.0.1 限制监听套接字只能在本地机器上工作?公共网络上的设备是否能够访问此侦听套接字? 为什么要用new byte[100];数组?这是从客户端发送的数据大小吗? @barteloma 这是很久以前的事了,我不记得了,但它可能只是一个神奇的数字。 【参考方案1】:

    您要侦听传入连接的套接字通常称为侦听套接字

    监听套接字确认传入连接时,通常称为子套接字的套接字创建有效地表示远程端点。

    为了同时处理多个客户端连接,您需要为服务器接收和处理数据的每个子套接字生成一个新线程。 这样做将允许 侦听套接字 接受和处理多个连接,因为您正在侦听的线程在您等待传入数据时将不再阻塞或等待.

while (true)

   Socket client = listener.AcceptSocket();
   Console.WriteLine("Connection accepted.");
    
   var childSocketThread = new Thread(() =>
   
       byte[] data = new byte[100];
       int size = client.Receive(data);
       Console.WriteLine("Recieved data: ");
       
       for (int i = 0; i < size; i++)
       
           Console.Write(Convert.ToChar(data[i]));
       

       Console.WriteLine();
    
       client.Close();
    );

    childSocketThread.Start();

【讨论】:

如何异步设置(不为每个客户端启动一个新线程?) @MrDysprosium 可扩展性明显。 你说的很明显,所以我对这个话题的理解程度可能很低......但是为什么异步比线程更具可扩展性? @MrDysprosium 因为为操作系统创建和管理线程的成本很高,因此为每个客户端启动一个线程很昂贵。除此之外,您为每个客户端创建的线程大部分都不会做任何事情,而是等待从客户端获取一些东西。 @SHM 不是 2 年后回复的那个人,但是为了让异步进程继续进行,在某些时候必须建立一个线程,因此,必须有人假脱机.即使通过来自另一台机器的线路,也会打开一个线程来等待响应。我不明白您将如何建立异步进程(例如,任务仍然是线程)。【参考方案2】:

我今天遇到了类似的问题,这样解决了:

while (listen) // <--- boolean flag to exit loop

   if (listener.Pending())
   
      Thread tmp_thread = new Thread(new ThreadStart(() =>
      
         string msg = null;

         TcpClient clt = listener.AcceptTcpClient();

         using (NetworkStream ns = clt.GetStream())
         using (StreamReader sr = new StreamReader(ns))
         
            msg = sr.ReadToEnd();
         

         Console.WriteLine("Received new message (" + msg.Length + " bytes):\n" + msg);
      
      tmp_thread.Start();
   
   else
   
       Thread.Sleep(100); //<--- timeout
   

我的循环在等待连接时没有卡住,它确实接受了多个连接。


编辑: 以下代码 sn-p 是使用任务而不是线程的 async 等效项。请注意,代码包含 C#-8 结构。

private static TcpListener listener = .....;
private static bool listen = true; // <--- boolean flag to exit loop


private static async Task HandleClient(TcpClient clt)

    using NetworkStream ns = clt.GetStream();
    using StreamReader sr = new StreamReader(ns);
    string msg = await sr.ReadToEndAsync();

    Console.WriteLine($"Received new message (msg.Length bytes):\nmsg");


public static async void Main()

    while (listen)
        if (listener.Pending())
            await HandleClient(await listener.AcceptTcpClientAsync());
        else
            await Task.Delay(100); //<--- timeout

【讨论】:

一般来说,当我想管理来自外部的监听时,我也会使用这个解决方案,并有可能正确地停止它。我面临的唯一问题是,如果设置为高,使用 sleep 命令会产生过多的延迟,如果设置为较低,则会消耗 cpu。有没有办法解决这个问题? 我相信最好的解决方案是结合使用 async/await 和“Task.Delay(...)”。当我可以访问我的计算机时,我会尝试进一步详细说明。 太好了,让我们看看它是如何工作的。我的另一个想法是继续使用 AcceptTcpClient 而不嵌套到睡眠等待循环中,并在另一个单独的线程中启动它,然后在想要完成收听时强行终止它。然后它抛出一个必须处理的异常。丑陋,但在速度方面似乎是最有效的,只是想知道是否有任何类似但更好的解决方案。 我使用“Task.Delay(...)”更新了我的答案以包含异步代码。使用任务的异步编程被认为是基于线程的代码结构的“继承者”。 最好的方法就是在一个while循环中等待listener.AcceptTcpClientAsync。如果您希望能够从外部关闭侦听套接字,则只需使用取消令牌关闭套接字并捕获 AcceptTcpClientAsync() 抛出的套接字异常【参考方案3】:

基本思想是,监听器套接字总是监听给定的 IP 和端口号。当有连接请求时,侦听器接受连接并使用 tcpclient 对象获取远程端点,直到连接关闭或丢失。

【讨论】:

以上是关于如何设置 TcpListener 始终监听并接受多个连接?的主要内容,如果未能解决你的问题,请参考以下文章

Go语言中Socket通信TCP服务端

使用 async/await 的异步 TcpListener 的正确方法

如何获取 TcpListener 的公共端口号?

如何在 .NET 中的线程上传播 tcplistener 传入连接?

C# 在客户端和服务器上使用 TcpListener

tcplistener 没有启动