解决服务器UDP数据包丢失问题

Severe UDP packet loss

While looking after a UDP based service, it came to my attention that we were losing a significant number of inbound packets. The first place to start is with  netstat(8) and you can use the  -s option to check statistics for various protocols (or add  -u for UDP only, or  -t for TCP).

Example output of  netstat -su:
$ netstat -su
Udp:
    2829651752 packets received
    27732564 packets to unknown port received.
    1629462811 packet receive errors
    179722143 packets sent
This is showing the total number of UDP packets received and sent, plus two extra metrics. The second line shows UDP packets that were sent to a port that doesn't have a listening socket, then the third line shows packets that were dropped by the kernel.

Sockets contain a couple of buffers between the kernel and the application, one for receiving and one for sending data which have a fixed size. When the application fails to read from the buffer fast enough, packets will be discarded, incrementing the receive error counter.

As no technical blog post is complete without a pretty graph, below is a graph generated using  Munin, showing the UDP traffic flowing on one particular system:

Netstat UDP graph

In the above graph, you can see the dominant line being the received packets and the turquoise line lower down is showing the packet receive errors.

On Linux, the buffer sizes are controlled by a group of sysctl parameters with rmem* being receive buffers and w* being send buffers:
  • net.core.rmem_default
  • net.core.rmem_max
  • net.core.wmem_default
  • net.core.wmem_max
Checking a Debian Etch system, the default values for the max is about 128kB and the default size is 120kB. I've shown them here using the  sysctl(8) tool.
$ sysctl net.core   grep [rw]mem
net.core.wmem_max = 131071
net.core.rmem_max = 131071
net.core.wmem_default = 122880
net.core.rmem_default = 122880
Using sysctl, you can update the values of these parameters with the -w option:
$ sudo sysctl -w net.core.rmem_max=1048576 net.core.rmem_default=1048576
net.core.rmem_max = 1048576
net.core.rmem_default = 1048576
This now causes any application to have increased buffer sizes on its sockets by default, which provided your application doesn't have other bottlenecks affecting its throughput, will give it a little more space. It's also possible to increase the maximum and then have the application alter the socket size - see  socket(7) for more info.

In our case, you can clearly see on the graph that the problem has been solved for a few days. We had to apply two changes mentioned:
  • Increasing the buffer size, which was done using the application config (and increasing the net.core.rmem_max parameter, leaving rmem_default alone)
  • by tweaking the application to increase its throughput, using more controlled buffering internally, rather than relying on the kernel socket buffering
Only one packet has been lost since the changes were made, which is an acceptable error rate for this application given its throughput.
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一个简单的C语言UDP服务器端代码的示例: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define PORT 8080 #define BUFFER_SIZE 1024 int main() { int sockfd; struct sockaddr_in serverAddr, clientAddr; char buffer[BUFFER_SIZE]; // 创建套接字 sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (sockfd == -1) { perror("socket"); exit(1); } // 设置服务器地址结构 serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(PORT); serverAddr.sin_addr.s_addr = INADDR_ANY; // 绑定套接字到本地地址 if (bind(sockfd, (struct sockaddr *)&serverAddr, sizeof(serverAddr)) == -1) { perror("bind"); exit(1); } printf("UDP Server is listening on port %d...\n", PORT); while(1) { socklen_t addrLen = sizeof(clientAddr); // 接收消息 ssize_t numBytes = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&clientAddr, &addrLen); if (numBytes == -1) { perror("recvfrom"); exit(1); } buffer[numBytes] = '\0'; // 将接收到的字符串转换成以'\0'结尾的格式 printf("Received message from %s:%hu: %s\n", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port), buffer); // 发送响应 if (sendto(sockfd, buffer, strlen(buffer), 0, (struct sockaddr *)&clientAddr, sizeof(clientAddr)) == -1) { perror("sendto"); exit(1); } } // 关闭套接字 close(sockfd); return 0; } ``` 这段代码实现了一个基本的UDP服务器端。它首先创建一个套接字,并将其绑定到指定端口。然后进入一个无限循环,在循环中不断接收来自客户端的数据,并打印出接收到的消息,然后将相同的消息发送回客户端。 需要注意的是,服务器端的IP地址是INADDR_ANY,这意味着它将会接收来自所有网络接口的数据包。另外,这段代码没有处理并发连接,如果有多个客户端同时发送数据,可能会导致数据包丢失。如果需要更复杂的并发处理,请在代码中添加适当的同步机制。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值