【问题标题】:.Net Socket (UDP) Sending, Receiving and Scheduling.Net Socket (UDP) 发送、接收和调度
【发布时间】:2016-01-02 19:53:13
【问题描述】:

我目前正在进行一个个人项目以用于学习目的。我想通过 UDP 建立连接,用于游戏等应用程序。发送的每个数据报都有一个特定的标头,指示它属于哪个“逻辑”通道 - 例如,通道 0 就像 UDP 具有额外的标头开销,而通道 1 使用更多标头来带来一些额外的可靠性。通道的目标是“自动”将消息分成逻辑组,最多达到特定数量。

在我当前的代码中,在处理发送和接收的单独线程中有一个简单的循环:

// This is pseudo code
public void Tick() {
    if(Socket.Poll) {
        do {
            ReadMessage();
        } while(Socket.Available > 0)
    }

    SendQueuedOutgoingMessages();
}

虽然这适用于理想世界,但我感觉当传入或传出消息过多时,此逻辑会失败。是否可以使用同一个套接字同时发送和接收消息(即发送和接收是异步的或在不同的线程中)?即使有可能,如果我只是使用两个或多个 UDP 套接字(或混合 TCP 和 UDP 套接字,如果我需要可靠性),并特别考虑可维护性会更好吗?

我能想到的最直接的替代方案是使用调度算法通过队列大小或其他因素来控制要读取和发送的消息数量,但在这种情况下感觉很差且不灵活。

编辑:添加有关代码的更多信息。

如果 Tick() 方法立即返回,则设置为每秒调用特定次数。例如,如果不存在新的输入或输出消息,则每秒 30 次,如果需要一些时间来发送或接收数据,则更少。我使用了阻塞 ReceiveFrom 和 SendTo 方法来避免忙等待或 Sleep(0) 等调用。

虽然我会立即处理传入的消息,但我使用传出消息队列来帮助实现通道的想法 - 每个通道都有自己的优先级,直到“无优先级”,随着时间的推移在平稳和忙碌的时刻影响其带宽份额。

【问题讨论】:

    标签: c# sockets udp


    【解决方案1】:

    是使用 2 个 socket 分别接收和发送,还是只使用一个取决于具体情况。如果您要发送大量消息,并且即使您在专用线程上,如果套接字使用的传出队列已满,套接字也可能会阻塞。

    这个问题有几种解决方案。使用 2 个套接字和 2 个不同的线程是其中之一,将 select 与异步套接字结合使用是另一个。关键是您不想仅仅因为发送可能会阻塞而停止接收。

    每种可能的解决方案都有其自身的复杂性。

    select api 用于检查某个套接字是否有要接收的内容,但您也可以使用它来检测套接字是否再次变为可写。您需要一个套接字选项来将套接字置于非阻塞状态,并且您需要在每次发送时检查 E_WOULDBLOCK 返回代码。如果是这样,发送失败,您必须自己排队。

    您并不会真正同时发送和接收,它是按顺序进行的。通过使用 fd_set api 操作的 2 个位掩码,您可以使用 select 来检查套接字是否可写和可读。您甚至可以一次在多个套接字上使用 select。然后,如果 select(阻塞调用)返回,您可以检查每个单独的位以检查需要执行哪些操作。

    如果一个套接字没有进入非阻塞状态,一个发送可以阻塞的原因是该套接字的输出队列可能是满的。如果套接字阻塞,它只会等待队列再次准备好。但是在等待期间,您无法在同一个套接字上收到任何内容。这就是为什么您需要非阻塞套接字和 select api,并结合自己的某种排队机制的原因。

    【讨论】:

    • “选择”是指检测何时有东西要发送/接收,然后尽快执行相关的异步操作?既然如此,假设一个Socket可以同时异步执行一个Send和一个Receive操作是否正确?
    • 不是同时,但是您在发送之前检查是否可以实际发送某些东西,并且您还检查是否有任何东西要接收。因此,您永远不会被阻止。假设您进行发送并且队列已满,您将阻塞发送,并且只要队列已满,您将不会收到,即使可能有一些东西要接收。网络编程真是一门高深的话题,代码很容易变得复杂。
    • 我已经接受了这个答案。如果您可以将您的评论添加到答案中,那就太好了,因为它与问题有关。
    【解决方案2】:

    为什么不简单的标准读取循环设置?

    while (true)
        ReadMessage();
    

    没有必要的调度或限制。不需要知道数据包是否准备好。

    您可以在同一个 UDP 套接字上同时读取和写入。

    也不需要传出队列。发送即可。

    【讨论】:

    • 你的意思是使用两个不同的线程,一个只用于发送,另一个只用于接收?
    • 这将是一个很好的方法。您也可以使用异步 IO,但我没有看到任何证据表明您需要它。另请注意,select 和 poll 是过时的编程模型,至少在 .NET 世界中是这样。
    • 不知道(而且似乎 Poll 为 GC 提供了数据)。实际上,这很有趣。
    猜你喜欢
    • 1970-01-01
    • 2013-11-01
    • 1970-01-01
    • 2013-01-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多