首页 文章

好的UDP中继服务器设计

提问于
浏览
2

我正在创建一个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) . 我不确定的主要部分是在上面的"Send to each client"部分做什么 . 我应该使用 UdpClient.Send 还是 UdpClient.BeginSend ?由于这是针对实时协议的,因此对于任何给定的客户端来说,有几个发送暂挂/缓冲是没有意义的 .

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

4 回答

  • 0

    我没有使用一个插座,这是一种直截了当的方式 . 在这种情况下,异步I / O似乎不会给我带来任何惊人的好处 . 只需使用非阻塞模式 . 收到一个包,发送它 . 如果send()导致EAGAIN或EWOULDBLOCK或其他任何C#表现形式,请将其删除 .

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

  • 0

    它在很大程度上取决于规模 - 即您想要中继多少并发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通常接近的一个很好的选择 .

    麦克风

  • 3

    EJP说的是什么 .

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

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

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

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

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

  • 1

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

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

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

相关问题