C# .NET 服务器轮询多个连接 - 有更好的方法吗?

发布于 2024-10-04 06:07:43 字数 2021 浏览 0 评论 0原文

我正在 .NET C# 中开发 TCP 服务器。它使用异步 I/O(I/O 完成)来同时处理大量客户端。现在,我将所有 TCP 连接都放在一个列表中,我不断地遍历该列表以查找任何特定连接的状态机的变化。一旦 I/O 完成设置某些标志,给定连接的状态机就会更新。

我确信有更好的方法来做到这一点 - 当前的实现是非常处理器密集型的,因为我没有阻塞等待更新,而是在没有限制的情况下进行轮询。我真的不在乎我的服务器是否浪费周期,但我猜它的设计很糟糕。我试图找到一种方法,仅当 I/O 完成信号需要处理某些内容时才处理特定连接,而当没有时则等待(即睡眠)。有人可以建议一个好方法来做到这一点吗?

我在想一些线程同步的事情可能会在循环的主线程等待任何 I/O 完成来释放它的情况下起作用。然而,I/O 完成有时是使用调用线程执行的(当数据立即可用时等),因此这会导致该解决方案出现问题。

您能提出的任何建议将不胜感激!

下面是主线程执行的(简化的)循环(rgClient 是客户端列表):

//Do communications on each client we currently have connected.
//This loops runs backwards so we can delete elements on the fly
//without have to iterate through more than once.
lock (rgClient)
{
    for (i = rgClient.Count - 1; i >= 0; i--)
    {
        if (!rgClient[i].DoComm())
        {
            rgClient[i].DoClose();
            rgClient.RemoveAt(i);
        }
    }
}

DoComm() 在连接的状态机上执行更新,其中涉及执行当前状态的活动,然后转换到如果需要的话,新的状态。下面是用于发送简单“ack”数据包的状态类:

class StateAck : State
{
    public StateAck(TextBox txtOutputExt, Form fmOwner)
        : base(txtOutputExt, fmOwner)
    {
        fWriting = false;
    }

    public override bool DoExecute(out Type tpNextState)
    {
        PktAck pkt;

        if (!base.DoExecute(out tpNextState))
        {
            return false;
        }

        //Start a write if we haven't yet
        if (!fWriting)
        {
            pkt = new PktAck();
            fWriting = true;

            return FPutPkt(pkt.rgbSerialize());
        }

        //Is the read finished / has an error occurred?
        if (fDataErrorWrite)
        {
            return false;
        }

        //Process the data
        if (fDataWritten)
        {
            tpNextState = typeof(StateIdle);
        }

        return true;
    }

    private bool fWriting;
}

每次从主线程调用 DoComm() 时,都会通过 DoExecute() 执行。 99%的时间里,实际上什么也没有发生。当写入(通过调用 FPutPkt() 启动)完成时,一个标志将发出信号,然后下一个状态设置为“空闲”。我想要做的是让主线程仅检查已完成网络活动并有需要更新的内容的客户端,以避免通过 DoExecute() 进行持续和冗余的传递。

I'm developing a TCP server in .NET C#. It uses asynchronous I/O (I/O completion) to handle a large number of clients simultaneously. Right now I have all the TCP connections in a list, which I pass through continuously looking for a change in the state machine of any particular connection. The state machine for a given connection is updated once the I/O completion sets certain flags.

I'm sure there is a better way to do this - the current implementation is very processor intensive since I am not blocking waiting for an update, but rather polling without throttling. I don't really care if my server is wasting cycles, but I'm guessing it's poor design. I'm trying to find a way to process a particular connection only when I/O completion signals there is something to handle, and wait (i.e. sleep) when not. Can anybody suggest a good way to do this?

I was thinking that some thread synchronization things might work where the main thread that is looping waits for any I/O completion to release it. However, I/O completion is sometimes executed using the calling thread (when data is immediately available, etc.) so this would cause problems with this solution.

Anything you can suggest would be much appreciated!

Here is the (simplified) loop that is executed by the main thread (rgClient is the list of clients):

//Do communications on each client we currently have connected.
//This loops runs backwards so we can delete elements on the fly
//without have to iterate through more than once.
lock (rgClient)
{
    for (i = rgClient.Count - 1; i >= 0; i--)
    {
        if (!rgClient[i].DoComm())
        {
            rgClient[i].DoClose();
            rgClient.RemoveAt(i);
        }
    }
}

DoComm() performs an update on the state machine for the connection, which involves executing the current state's activities, and then transitioning to a new state if necessary. Here is the state class for sending a simple "ack" packet:

class StateAck : State
{
    public StateAck(TextBox txtOutputExt, Form fmOwner)
        : base(txtOutputExt, fmOwner)
    {
        fWriting = false;
    }

    public override bool DoExecute(out Type tpNextState)
    {
        PktAck pkt;

        if (!base.DoExecute(out tpNextState))
        {
            return false;
        }

        //Start a write if we haven't yet
        if (!fWriting)
        {
            pkt = new PktAck();
            fWriting = true;

            return FPutPkt(pkt.rgbSerialize());
        }

        //Is the read finished / has an error occurred?
        if (fDataErrorWrite)
        {
            return false;
        }

        //Process the data
        if (fDataWritten)
        {
            tpNextState = typeof(StateIdle);
        }

        return true;
    }

    private bool fWriting;
}

Execution passes through DoExecute() every time DoComm() is called from the main thread. 99% of the time, nothing actually occurs. When the write (which is initiated by calling FPutPkt()) completes, a flag will signal this and then the next state is set to "idle". What I want to do, is have the main thread only check clients that have finished their network activity and have something that needs updating, to avoid the constant and redundant passes through DoExecute().

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

下雨或天晴 2024-10-11 06:07:43

我找到了一个似乎效果很好的解决方案。使用 EventWaitHandler (System.Threading),并在每次循环的底部自动重置为 WaitOne()。然后,任何回调或辅助线程都可以通过调用 EWH.Set() 向 EventWaitHandler 发出信号,这将允许循环进行另一次传递。无需对程序流程进行重大修改即可消除轮询循环的 CPU 使用率的好方法。希望它能帮助某人。

I found a solution that seems to work pretty well. Use a EventWaitHandler (System.Threading) with automatic reset to WaitOne() at the bottom of each pass through the loop. Then any callback or secondary thread can signal the EventWaitHandler by calling EWH.Set(), which will allow the loop to make another pass. Great way to eliminate the CPU usage of a polling loop without major modifications to program flow. Hope it helps someone.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文