2

我正在创建一个 UDP 服务器,它需要从各种客户端接收 UDP 数据包,然后将它们转发给其他客户端。我正在使用 C#,所以每个 UDP 套接字对我来说都是一个 UdpClient。我想我想要 2 个 UdpClient 对象,一个用于接收,一个用于发送。接收套接字将绑定到一个已知端口,而发送者根本不会被绑定。

服务器将获取每个数据包,在数据包数据中查找用户名,然后根据服务器维护的路由列表,将数据包转发给 1 个或多个其他客户端。

我开始监听 UdpClient:

   UdpClient udpListener = new UdpClient(new IPEndPoint(ListenerIP, UdpListenerPort));
   udpListener.BeginReceive(new AsyncCallback(OnUDPRead), udpListener);

我的回调看起来像:

    void OnUDPRead(IAsyncResult ar)
    {
        UdpClient udpListener = (UdpClient)ar.AsyncState;

        try
        {
            IPEndPoint remoteEndPoint = null;
            byte[] packet = udpListener.EndReceive(ar, ref remoteEndPoint);

            // Get connection based on data in packet

            // Get connections routing table (from memory)

            // ***Send to each client in routing table***
        }
        catch (Exception ex)
        {
        }

        udpListener.BeginReceive(new AsyncCallback(OnUDPRead), udpListener);
    }

服务器将需要每秒处理 100 个数据包(这是用于 VOIP)。我不确定的主要部分是在上面的“发送给每个客户”部分要做什么。我应该使用UdpClient.SendorUdpClient.BeginSend吗?由于这是针对实时协议的,因此为任何给定的客户端设置多个挂起/缓冲的发送是没有意义的。

由于我在任何给定时间只发布了一个 BeginReceive(),我假设我的 OnUDPRead 函数在已经执行时永远不会被调用。除非 UdpClient 在这方面的工作方式与 TcpClient 不同?由于我没有给它一个缓冲区(我没有给它任何缓冲区),我想它可以在只发布一个 BeginReceive 的情况下多次触发 OnUDPRead?

4

4 回答 4

3

我看不出有什么理由不能直接使用一个套接字。在我看来,异步 I/O 在这种情况下并没有提供任何惊人的好处。只需使用非阻塞模式。收到一个数据包,发送它。如果 send() 导致 EAGAIN 或 EWOULDBLOCK 或任何 C# 表现形式,只需删除它。

在您的计划中,发送套接字将在您第一次调用 send() 时自动绑定。客户端将回复该端口。所以它必须是你正在阅读的端口。而你已经拥有其中之一。

于 2012-05-29T23:49:56.460 回答
2

这在很大程度上取决于规模——即您想要中继多少并发 RTP 流。我已经开发了这个解决方案,作为我们 SIP 交换机的媒体网关的一部分。尽管最初有保留,但我使用 C# 非常有效地工作(单个服务器上 1000 个并发流,每秒 50K 数据报,使用 20ms PTime)。

通过反复试验,我确定了以下内容:

1) 使用 Socket.ReceiveFromAsync 比使用同步方法要好得多,因为您不需要为每个流使用单独的线程。收到数据包后,它们会通过线程池进行调度。

2) 创建/处理 SocketAsyncEventArgs 结构需要时间 - 最好在启动时分配结构池并从池中“借用”它们。

3) 对于处理期间所需的任何其他对象(例如,RTPPacket 类),类似。以如此高的速率动态分配这些会在任何类型的实际负载下很快导致 GC 问题。如果您避免所有动态分配,而是使用您自己管理的对象池,那么这个问题就会消失。

4) 如果您需要做任何时钟来协调输入和输出流、转码、混合或文件播放,请不要尝试使用标准的 .NET 计时器。我从 winmm.dll 包装了 Win32 多媒体计时器——这些计时器要精确得多。

我最终得到了一个架构,其中组件可以公开 IRTPSource 和 IRTPSink 接口,然后可以将它们连接起来以创建所需的图形。我创建了 RTP 输入/输出组件、混音器、播放器、录音机、(简单)转码器、播放缓冲区等 - 然后使用这两个接口将它们连接起来。然后使用 MM 计时器通过图形对 RTP 数据包进行计时。

对于通常使用 C 或 C++ 处理的问题,C# 被证明是一个不错的选择。

麦克风

于 2012-06-06T23:46:53.527 回答
0

EJP 所说的。

大多数关于使用异步 IO 扩展套接字服务器的文献都是在考虑 TCP 场景的情况下编写的。去年我对如何扩展 UDP 套接字服务器进行了大量研究,发现 IOCP/epoll 并不像 TCP 那样适合 UDP。在这里看到这个答案。

因此,仅使用多个线程来扩展 UDP 套接字服务器实际上比尝试执行任何类型的异步 I/O 事情要简单。很多时候,一个单线程抽出 recvfrom/sendto 调用就足够了。

我建议服务器每个客户端有一个同步套接字。然后让几个线程在客户端套接字的不同子集上执行 Socket.Select 调用。我想您可以为所有客户端使用一个套接字,但是如果您尝试从多个线程处理该套接字上的音频数据包,则会遇到一些乱序问题。

还可以考虑使用 UdpClient 的较低级别的套接字类。

但如果你真的想向外扩展——你可能想在 C/C++ 中完成核心工作。使用 C#,winsock 之上的更多抽象层意味着更多的缓冲区副本和更高的延迟。您可以通过一些技术使用 winsock 将数据包从一个套接字转发到另一个套接字,而不会产生缓冲区副本。

于 2012-05-30T01:15:17.587 回答
0

一个好的设计不是重新发明轮子:)

已经有很多 RTP 媒体代理解决方案:Kamailio、FreeSWITCH、Asterisk 和其他一些开源项目。我在 FreeSWITCH 方面有丰富的经验,强烈推荐它作为 SIP 呼叫的媒体代理。

如果您的信令协议不是 SIP,那么还有一些针对其他协议的解决方案。

于 2012-06-16T20:50:00.090 回答