udp loader balance

udp 的端口复用实现负载均衡

前言

偶尔看到 python 3.9 的release note 里面提到一个bug

1
2
3
asyncio¶

出于重要的安全性考量,asyncio.loop.create_datagram_endpoint() 的 reuse_address 形参不再被支持。 这是由 UDP 中的套接字选项 SO_REUSEADDR 的行为导致的。 更多细节请参阅 loop.create_datagram_endpoint() 的文档。 (由 Kyle Stanley, Antoine Pitrou 和 Yury Selivanov 在 bpo-37228 中贡献。。)

意思是tcp的socket option:SO_REUSEADDR不适用于udp:
在tcp中这个选项表示立即回收端口,减少 time_wait 的时间。而在udp中,这个选项表示多个socket可以绑定一个端口, 由内核来分发请求。

所以看到此功能,自己试了一下,确实如此, 顺便回顾一下知识

主要代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8888);
inet_pton(AF_INET,"127.0.0.1",(void*)&addr.sin_addr);
// inet_pton 支持ipv4和ipv6,是比较新的转换函数

sfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(sfd == -1)
{
perror("socket");
exit(1);
}
int val = 1;
if(0 != setsockopt(sfd, SOL_SOCKET, SO_REUSEPORT,&val, sizeof(val))){
perror("setsockopt");
exit(1);
}
/* sockaddr 和 sockaddr_in 有什么区别?
struct sockaddr {
sa_family_t sin_family;//地址族
   char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息
   };
   struct sockaddr_in {
   sa_family_t sin_family;//地址族
   uint16_t sin_port;
   struct in_addr sin_addr; // 32 位地址
   char sin_zero[8]; // reserve
   };
   struct in_addr {
   In_addr_t s_addr; //32位
   };
  
   sockaddr_in 和 sockaddr 长度相同,都 sin_family + 14 个字节,但是前者显式划分了
   */
if(bind(sfd, (struct sockaddr*) &addr, sizeof(addr)) != 0)
{
perror("bind");
exit(1);
}

效果

先启动两个服务端,再使用ncat 来模拟请求,

1
ncat -uv 0.0.0.0 8888

启动ncat时,系统会分配给一个服务端处理。 但是重启ncat时, 会切换到另一个服务端处理