好的UDP中继服务器设计

时间:2012-05-29 22:15:43

标签: c# sockets udp rtp

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

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

我用:

开始监听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.Send还是UdpClient.BeginSend?由于这是针对实时协议的,因此对于任何给定的客户端,有几个发送暂挂/缓冲是没有意义的。

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

4 个答案:

答案 0 :(得分:3)

我没有看到任何理由不能使用一个套接字,这是一种简单明了的方法。在这种情况下,异步I / O似乎不会给我带来任何惊人的好处。只需使用非阻塞模式。收到一个包,发送它。如果send()导致EAGAIN或EWOULDBLOCK或其他任何C#表现形式,请将其删除。

在您的计划中,第一次调用send()时,发送套接字将自动绑定。客户将回复该端口。所以它需要成为您正在阅读的端口。你已经有了其中一个。

答案 1 :(得分:1)

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

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

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

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

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

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

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

C#被证明是使用C或C ++的典型选择。

麦克

答案 2 :(得分:0)

EJP说的是什么。

大多数关于使用异步IO扩展套接字服务器的文献都是在考虑TCP场景的情况下编写的。去年我做了很多关于如何扩展UDP套接字服务器的研究,发现IOCP / epoll不适合UDP,也不适合TCP。请参阅此答案here

因此,使用多个线程扩展UDP套接字服务器实际上非常简单,而不是尝试执行任何类型的异步I / O操作。通常情况下,一个单独的线程泵出recvfrom / sendto调用就足够了。

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

还要考虑使用UdpClient的低级套接字类intead。

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

答案 3 :(得分:0)

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

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

如果您的信令协议不是SIP,那么也有其他协议的解决方案。