sendto频率过快导致发送丢包

在一次工作中,需要测试服务器推UDP流的极限,通过在本机创建CPU核个数的线程,一共创建30万条UDP消息,每条消息1024字节,通过注册UDP套接字的写事件,触发后将消息全部推到黑洞。在实际测试过程中,发现并发量大到一定程度,会出现以下现象。

  • 通过dstat监控发现,只有10几MB的流量,后续CPU全部消耗在soft(软中断处理)上
  • 通过strace -p <线程ID>发现,一直在进行sendto函数调用,且返回值为EAGAIN

通过上网查询资料,有如下结论,原文请 点击这里。

发送频率过高导致丢包
很多人会不理解发送速度过快为什么会产生丢包,原因就是UDP的sendto不会造成线程阻塞,也就是说,UDP的sendto不会像TCP中的send那样,直到数据完全发送才会return回调用函数,它不保证当执行下一条语句时数据是否被发送。(sendto方法是异步的)这样,如果要发送的数据过多或者过大,那么在缓冲区满的那个瞬间要发送的报文就很有可能被丢失。至于对“过快”的解释,作者这样说:“A few packets a second are not an issue; hundreds or thousands may be an issue.”(一秒钟几个数据包不算什么,但是一秒钟成百上千的数据包就不好办了)。
发送方丢包:内部缓冲区(internal buffers)已满,并且发送速度过快(即发送两个报文之间的间隔过短)

通过命令watch netstat -s,可以明确的看出 ip 项下的 outgoing packets dropped 持续增长,也就意味着确实是发送丢包。

然后就通过outgoing packets dropped ,sendto频率过快等等关键词开始查资料,查到可以通过ioctls来设置传输队列长度,原文请 点击这里。

SIOCGIFTXQLEN, SIOCSIFTXQLEN
使用 ifr_qlen 读取或设置设备的传输队列长度。设置传输队列长度是特权操作。

可以通过以下代码来获取eth0网卡的队列长度。

struct ifreq ifr;

memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, "eth0", sizeof(ifr.ifr_name));

if (-1 == ioctl(sock_, SIOCGIFTXQLEN, &ifr))
    PLOG(ERROR) << "failed to get dev eth0 queue length";
LOG(KEY) << "Dev eth0 queue length " << ifr.ifr_qlen;

可以通过以下代码来设置队列长度。

struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, "eth0", sizeof(ifr.ifr_name));
ifr.ifr_qlen = 10000;
if (-1 == ioctl(sock_, SIOCSIFTXQLEN, &ifr))
  PLOG(ERROR) << "failed to set dev eth0 queue length";
if (-1 == ioctl(sock_, SIOCGIFTXQLEN, &ifr))
  PLOG(ERROR) << "failed to get dev eth0 queue length";
LOG(KEY) << "Dev eth0 queue length " << ifr.ifr_qlen;

至此,问题得到解决。
小结:

  • sendto过快导致发送丢包,是因为发送队列满了,如果说缓冲区,估计大部分人都将误解。
  • 至于接收方因为突发率导致接收丢包的问题,那么就要在发送方进行发送平滑进行解决。

你可能感兴趣的:(sendto频率过快导致发送丢包)