使UDP在.NET / Mono中丢失更少的消息

时间:2010-10-30 00:53:11

标签: c# mono udp

我们目前正在为开源学术项目Logbus-ng执行一些基准测试。它基本上通过UDP(RFC 5426)和TLS(RFC 5425)实现Syslog协议。 我们知道TLS的优势在于可靠性(即我们不会丢失消息)但具有性能的缺点。

我们有一个基准测试客户端,还有一个特殊的伪造Apache安装,可以高速发送消息。 我们的目标是将UDP数据包的丢失降至最低。 Apache 1.3.41已经过检测,以便通过UDP发送特殊的日志消息(不是Syslog格式,而是我们在服务器端解析的特殊短语法),这样的检测使它在httpd启动时发送超过2000条消息,并且我们希望它发生:)

更多,我可以告诉你,在Apache的加速阶段,这个数量的消息(与我们提交给日志服务器的其他工作负载相比)是在发送的。高速率,可能泛洪UDP。

现在,日志服务器位于与HTTP服务器不同的机器上,并且两者都具有相当不错的硬件(甚至不是双核CPU,而是具有HyperThread的Pentium 4)。日志服务器代码在C#中。以下方法由AboveNormal priority

中的4个线程运行
    UdpClient _client;
    IQueue<T>[] _byteQueues; //not really IQueue, but a special FIFO queue class that reduces overhead to the minimum

    private void ListenerLoop()
    {
        IPEndPoint remoteEndpoint = new IPEndPoint(IPAddress.Any, 0);
        while (_listen)
        {
            try
            {
                byte[] payload = _client.Receive(ref remoteEndpoint);

                _byteQueues[
                    (((Interlocked.Increment(ref _currentQueue))%WORKER_THREADS) + WORKER_THREADS)%WORKER_THREADS].
                    Enqueue(payload);
            }
            catch (SocketException)
            {
            }
            catch (Exception)
            {
            } //Really do nothing? Shouldn't we stop the service?
        }
    }

为了减少线程在Receive方法之外花费的时间,我们不会在收到消息后解析消息,而是将其存储在其他工作线程将读取的4个特殊队列之一中。据我所知,.NET调度程序是贪心的,所以无论线程等待多长时间,优先级较高的线程都会提前调度并可能导致饥饿,所以这就是为什么我们现在不关心线程数量的增加应用程序(全球约20个)。

我们不仅增加了线程优先级,还尝试将UDP缓冲区大小增加到1MB。这是初始化代码的片段

try
{
    Socket clientSock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)
                            {
#if !MONO
                                //Related to Mono bug 643475
                                ExclusiveAddressUse = true,
#endif
                            };

    if (ReceiveBufferSize >= 0) clientSock.ReceiveBufferSize = ReceiveBufferSize;
    clientSock.Bind(localEp);
    _client = new UdpClient {Client = clientSock};
}
catch (SocketException ex)
{
    throw new LogbusException("Cannot start UDP listener", ex);
}

在运行时配置ReceiveBufferSize ...

Apache发送的每条日志消息都很短,我认为不超过50个字节。我们在实验室中运行千兆以太网。

在上一次使用此类配置的实验中,日志服务器仅收到超过2900个生成的700多个。 Wireshark在UDP套接字上报告了超过2900条消息,但Logbus的日志跟踪(将所有收到的消息存储到文件中)仅报告这些700/800。执行cat /proc/net/udp并使用lsof进行操作以查找正确的行报告大量丢弃的数据包。原木肯定以非常高的速度发送。如果我们在每次调用日志后修改Apache内核以暂时休眠(少于一毫秒),我们会将损耗降低到零,但性能也会降低到几乎为零。我们将进行这样的测试,但我们必须证明Logbus-ng在现实场景中的有效性:(

我的直接问题是

  1. UdpClient.ReceiveBufferSize是否有助于防止数据包丢失?我还能在C#中做些什么?
  2. 显然假设也在Mono中工作,但是你知道有关该属性的可能错误吗?我的意思是,有没有人报告过一个错误? (Mono 2.8)
  3. 您知道首先向localhost发送数据包是否可以减少数据包丢失? (我会在Web服务器上运行一个特殊的日志服务器实例,然后通过TLS将日志转发给真正的日志服务器)
  4. 您建议我减少损失率
  5. 我们当前必须使用Apache执行特殊测试,我们使用UDP来传递消息。我们不能选择TLS,因为我们只有C#API。

    提前感谢您的帮助。我希望已经清楚了。如果有帮助,你可以在SVN上找到UDP接收器的源代码

3 个答案:

答案 0 :(得分:1)

ReceiveBufferSize肯定会影响UDP套接字(即UdpClient),如果数据包丢失是由于缓冲区溢出造成的,那么增加ReceiveBufferSize会有所帮助。

请记住,如果数据速率如此之高以至于您无法快速读取缓冲区足够长的时间,那么即使是最大的缓冲区也不可避免地会溢出。

我在Ubuntu上运行的Mono 2.6.7上有效地使用了UdpClient.Client.ReceiveBufferSize,所以我相信Mono的实现很好,当然我还没有将它用于Mono 2.8。

根据我的经验,以极高的速率向本地主机发送UDP数据包,可能会丢失一些数据包,但我从未在真实世界的应用程序中遇到过这种丢包现象。所以你可以用这种方法取得一些成功。

您还需要查看数据包丢失是否正在发生,可能是数据包丢失是由于网络基础设施,数据包冲突,交换机可能因为交换机的某些限制而丢弃数据包。

简而言之,您需要准备好处理并期望在使用UDP时丢包。

答案 1 :(得分:0)

如果您使用的是udp,则必定会丢失数据包。丢包有很多原因。对你来说,很可能是因为频道上任何地方的数据包溢流。可能是你的开关或可能是你的接收器。过度泛滥的原因是因为UDP没有任何类型的拥塞控制。 Tcp确实具有拥塞控制(慢启动),这就是为什么使用tcp,它从不(理论上在完美的环境下)过度驱动。

防止udp传输过度溢出的有效方法是手动采用tcp慢启动拥塞控制策略。

回答你的直截了当的问题 1.否。请参阅tcp慢启动算法。 2.最有可能的不是虫子。 Udp就是这样设计的。这是一个原因,并且需要这样做。 3.号码代理不起作用。 4.最简单的实现,以防止由于过度编码导致的数据包丢失,是在发送更多数据包之前等待接收方的确认(注意接收方已成功接收到数据包)。当然,由于其他原因,这对于防止数据包丢失没有帮助。

答案 2 :(得分:0)

  

显然也应该在Mono中工作,但你知道该属性可能存在的错误吗?我的意思是,有没有人报告过一个错误? (单声道2.8)

显然,设置套接字接收缓冲区大小在单声道版本3.2.7中严重受损:而不是指定的大小,它将被设置为随机值,因此尝试增加缓冲区大小实际上可能会使性能变差: - (

https://github.com/mono/mono/commit/bbd4ee4181787189fbb1f8ba6364afdd982ae706