【问题标题】:TcpListener server loop to idle until there is a pending connectionTcpListener 服务器循环空闲,直到有挂起的连接
【发布时间】:2014-07-27 02:45:47
【问题描述】:

为了这个问题,我做了一个超级简化的例子:

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

namespace Loop
{
    class Program
    {
        public static void Main (string[] args)
        {
            TcpListener server = new TcpListener(IPAddress.Any, 1337);

            server.Start();

            Console.WriteLine("Starting listener on {0}:{1}", IPAddress.Any, 1337);

            while (true)
            {
                if (server.Pending())
                {
                    Console.WriteLine("Activity...");

                    Socket client = server.AcceptSocket();

                    IPEndPoint clientAddress = (IPEndPoint) client.RemoteEndPoint;

                    Console.WriteLine("Accepted client: {0}:{1}", clientAddress.Address, clientAddress.Port);

                    client.Close();

                    Console.WriteLine("Closed connection to: {0}:{1}", clientAddress.Address, clientAddress.Port);
                }
                else
                {
                    // Currently takes 100% of my CPU (well, actually Core - 25%, but you get the idea).
                    // How do I idle (CPU @ 0%) the loop until pending connection?
                }
            }
        }
    }
}

评论已经包含了问题,但是是的,我如何让循环空闲直到有一个实际的挂起连接,这样我的 CPU 才不会被融化?

当给定的套接字上有挂起的连接时,有没有办法只在操作系统事件上监听和唤醒? (像libuv (node.js) 这样的东西,这就是我添加 的原因)

我的实际实现是针对一个相当基本的 Reactor 事件循环,但是是的,我不知道如何使用 C# 监听 OS 事件(以及是否有可能)。

我知道 BeginAccept 和其他 Async 家族,但由于他们的多线程性质,这些人是不可接受的。

另外,我知道我可以在循环中简单地 Thread.Sleep,但我正在寻找基于事件的行为。

附:我正在使用 Mono,目标是 Linux 可执行文件。

【问题讨论】:

  • 如果要使用阻塞样式,可以先调用AcceptSocket而不用等待Pending;在接受连接之前,呼叫将阻塞(不旋转)。或者,您可以在轮询循环中添加睡眠以减少 CPU 开销,但在编写网络 I/O 代码以避免轮询并使用适当的同步工具仅在 I/O 可用时唤醒时会更好。跨度>
  • 我只想指出BeginAccept 不一定是多线程的(至少就您的代码而言)。如果你提供一个同步上下文,如果你可以访问await/asyncAcceptSocketAsync 非常容易使用,它会在使用与同步非常相似的代码时异步处理事情,并完全解决多线程问题(因为您实际上在代码中一直在处理一个线程)。 Mono 现在应该支持await/async
  • @Luaan,使用多线程,我认为每个请求都会产生额外的线程。
  • @jolt 好吧,BeginAccept 没有,不是真的。它从ThreadPool 借用一个线程,只是在极短的时间内(将实际回调放入调用队列)。如果您调用BeginAccept 一百次,它不会产生 100 个线程。它只是注册了一百个不同的异步套接字。这就是 IOCP 的美妙之处——您无需使用 CPU 线程来处理 I/O 密集型工作。要处理与一千个客户端的通信,您不再需要一千个线程(与“同步”模型不同)。它实际上很酷:)
  • 我很好奇:您从哪里得到使用 Pending 的想法?我经常看到这个错误。

标签: single-threaded c# events mono tcplistener single-threaded


【解决方案1】:

我建议你去掉对挂起连接的检查,让主线程阻塞(AcceptSocket 是一种阻塞方法),直到连接被接受:

while (true)
{
    Console.WriteLine("Waiting for client to connect...");

    Socket client = server.AcceptSocket(); // This is a blocking call...

    Console.WriteLine("Client connected...");

    IPEndPoint clientAddress = (IPEndPoint) client.RemoteEndPoint;

    Console.WriteLine("Accepted client: {0}:{1}", clientAddress.Address, clientAddress.Port);

    client.Close();

    Console.WriteLine("Closed connection to: {0}:{1}", clientAddress.Address, clientAddress.Port);
}

【讨论】:

    【解决方案2】:

    如果您想避免等待,您正在使用可用的机制 (Pending),然后因为您想等待而遇到问题:

            while (true)
            {
                    Socket client = server.AcceptSocket();
    
                    Console.WriteLine("Activity...");
    
                    IPEndPoint clientAddress = (IPEndPoint) client.RemoteEndPoint;
    
                    Console.WriteLine("Accepted client: {0}:{1}", clientAddress.Address, clientAddress.Port);
    
                    client.Close();
    
                    Console.WriteLine("Closed connection to: {0}:{1}", clientAddress.Address, clientAddress.Port);
            }
    

    AcceptSocket 阻塞,直到它获得连接。

    通常,在获得连接后,您会将打开的套接字交给其他东西(专用线程1 或线程池或其他东西),然后再回到再次调用AcceptSocket,这样就可以一次连接多个客户端。

    1每个套接字一个线程并不能很好地扩展,但是当你只是在玩的时候它会工作得很好。

    【讨论】:

    • 比每个线程一个套接字更合适的技术是什么?每个线程是否有合理的套接字数量?该数字是否会根据您的活动连接数而变化?
    • @TheMonarch - 这些天我通常建议使用更高级别的抽象,例如异步机制和/或任务,让其他东西担心管理线程。
    【解决方案3】:

    循环将尽可能快地进行——这就是为什么它会占用你这么多的 CPU 周期。

    尝试添加

    System.Threading.Thread.Sleep(10);
    

    在你的 else 体内。

    以上将解决高 CPU 负载,但它不会解决调用 Sleep() 方法时发生在主线程上的阻塞。我个人建议在新的 (IsBackground = true) 线程下运行您的服务器循环。这将导致 CPU 周期使用率大大降低并防止您的主线程阻塞。

    希望我回答了你的问题。

    【讨论】:

    • 这是治疗症状(高 CPU 负载)。更好地处理原因:待定呼叫。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-12-06
    • 2019-01-14
    • 2017-12-14
    • 2023-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多