目前昨天查一个线上问题:“”dns服务器在我们的设备, 有大量的终端到设备上请求解析域名,但是一直是单线程,dns报文处理不过来”, 然而设备是多核,dns服务器一直不能利用多核资源,所以能不能使用多线程进行处理呢?
udp不像tcp那样,udp没有连接的概念,也就是没有通过建立多个连接来提高对dns服务器并发访问,然而在多核环境下那就只能通过多线程来访问一个共享的udp socket,但是还是一个socket , 会涉及到多线程抢占资源问题。
来看一下内核协议栈udp收到包代码:根据以前分析tcpip协议栈文章可以知道,报文在内核协议栈流程大约如下:
sk = __udp4_lib_lookup; ////根据报文的端口号和目的端口号查询udptable,寻找应该接收该数据包的传输控制块
udp_queue_rcv_skb//涉及到内核态和 用户态抢占socket的问题, 所以有收包队列 以及 sk_add_backlog队列
sk->sk_data_ready(sk, skb_len) wake up 唤醒等待队列,
也就是 报文送到那个socket是由__udp4_lib_lookup这个函数做出选择, 选择的依据是 ip 端口号 接口来进行处理选择对应的socket,对于
dhcp dns服务器来说一般不会绑定接口,所以一般就是 设置ip udp.port, 所以内核选择socket的时候一般也是通过对比ip port来查找。通过找出匹配层度最高的socket作为收包sk。
如果要是允许有多个socket呢??
那么不就是可以通过轮询选择或者hash选择出对应的socket 吗!!!!!!!! 所以在linux 3.9内核版本后面增加reuseport,允许多个socket绑定同一个ip port, 通过hash散列在桶里面,
后面就允许多线程/多进程服务器的每个线程都bind 同一个端口,并且最终每个线程拥有一个独立的socket,而不是所有线程都访问一个socket;没有reuseport这个patch的话,这么做的后果就是服务器会报出一个类似“地址/端口被占用的”错误信息。
socket bind ip port时 会调用get_port 计算是否存在ip port存在冲突, linux 3.9patch中对hash 以及计算方式加入reuseport,可以允许多个socket bind同样的ip port。
同一个客户端的数据总是分配给同一个 udp_sock。so!! 在写 UDP server 的时候,为了提高处理能力,可以起多个线程,每个线程读写自己的 UDP socket
顺便看一看udp 怎样查找port:
int udp_v4_get_port(struct sock *sk, unsigned short snum) { unsigned int hash2_nulladdr = udp4_portaddr_hash(sock_net(sk), htonl(INADDR_ANY), snum); unsigned int hash2_partial = udp4_portaddr_hash(sock_net(sk), inet_sk(sk)->inet_rcv_saddr, 0); /* precompute partial secondary hash */ udp_sk(sk)->udp_portaddr_hash = hash2_partial; return udp_lib_get_port(sk, snum, ipv4_rcv_saddr_equal, hash2_nulladdr); }
UDP 对于porttable维护一个是一port 进行hash 一个是以sip +port(port=0) 进行hash。
struct udp_sock {
/* inet_sock has to be the first member */
struct inet_sock inet;
#define udp_port_hash inet.sk.__sk_common.skc_u16hashes[0]
#define udp_portaddr_hash inet.sk.__sk_common.skc_u16hashes[1]
}
UDP 协议的主要数据结构是两张 hash 表,指向 UDP 协议控制块 struct udp_sock。其中 hash 以 port 为 key,hash2 以 IP+port (port=0)为 key
1 int udp_lib_get_port(struct sock *sk, unsigned short snum, 2 int (*saddr_comp)(const struct sock *sk1, 3 const struct sock *sk2, 4 bool match_wildcard), 5 unsigned int hash2_nulladdr) 6 { 7 struct udp_hslot *hslot, *hslot2; 8 struct udp_table *udptable = sk->sk_prot->h.udp_table; 9 int error = 1; 10 struct net *net = sock_net(sk); 11 12 if (!snum) { 13 int low, high, remaining; 14 unsigned int rand; 15 unsigned short first, last; 16 DECLARE_BITMAP(bitmap, PORTS_PER_CHAIN); 17 18 inet_get_local_port_range(net, &low, &high); 19 remaining = (high - low) + 1; 20 21 rand = prandom_u32(); 22 first = reciprocal_scale(rand, remaining) + low; 23 /* 24 * force rand to be an odd multiple of UDP_HTABLE_SIZE 25 */ 26 rand = (rand | 1) * (udptable->mask + 1); 27 last = first + udptable->mask + 1; 28 do { 29 hslot = udp_hashslot(udptable, net, first); 30 bitmap_zero(bitmap, PORTS_PER_CHAIN); 31 spin_lock_bh(&hslot->lock); 32 udp_lib_lport_inuse(net, snum, hslot, bitmap, sk, 33 saddr_comp, udptable->log); 34 35 snum = first; 36 /* 37 * Iterate on all possible values of snum for this hash. 38 * Using steps of an odd multiple of UDP_HTABLE_SIZE 39 * give us randomization and full range coverage. 40 */ 41 do { 42 if (low <= snum && snum <= high && 43 !test_bit(snum >> udptable->log, bitmap) && 44 !inet_is_local_reserved_port(net, snum)) 45 goto found; 46 snum += rand; 47 } while (snum != first); 48 spin_unlock_bh(&hslot->lock); 49 } while (++first != last); 50 goto fail; 51 } else { 52 hslot = udp_hashslot(udptable, net, snum); 53 spin_lock_bh(&hslot->lock); 54 if (hslot->count > 10) { 55 int exist; 56 unsigned int slot2 = udp_sk(sk)->udp_portaddr_hash ^ snum; 57 58 slot2 &= udptable->mask; 59 hash2_nulladdr &= udptable->mask; 60 61 hslot2 = udp_hashslot2(udptable, slot2); 62 if (hslot->count < hslot2->count) 63 goto scan_primary_hash; 64 65 exist = udp_lib_lport_inuse2(net, snum, hslot2, 66 sk, saddr_comp); 67 if (!exist && (hash2_nulladdr != slot2)) { 68 hslot2 = udp_hashslot2(udptable, hash2_nulladdr); 69 exist = udp_lib_lport_inuse2(net, snum, hslot2, 70 sk, saddr_comp); 71 } 72 if (exist) 73 goto fail_unlock; 74 else 75 goto found; 76 } 77 scan_primary_hash: 78 if (udp_lib_lport_inuse(net, snum, hslot, NULL, sk, 79 saddr_comp, 0)) 80 goto fail_unlock; 81 } 82 found: 83 inet_sk(sk)->inet_num = snum; 84 udp_sk(sk)->udp_port_hash = snum; 85 udp_sk(sk)->udp_portaddr_hash ^= snum; 86 if (sk_unhashed(sk)) { 87 if (sk->sk_reuseport && 88 udp_reuseport_add_sock(sk, hslot, saddr_comp)) { 89 inet_sk(sk)->inet_num = 0; 90 udp_sk(sk)->udp_port_hash = 0; 91 udp_sk(sk)->udp_portaddr_hash ^= snum; 92 goto fail_unlock; 93 } 94 95 sk_add_node_rcu(sk, &hslot->head); 96 hslot->count++; 97 sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1); 98 99 hslot2 = udp_hashslot2(udptable, udp_sk(sk)->udp_portaddr_hash); 100 spin_lock(&hslot2->lock); 101 if (IS_ENABLED(CONFIG_IPV6) && sk->sk_reuseport && 102 sk->sk_family == AF_INET6) 103 hlist_add_tail_rcu(&udp_sk(sk)->udp_portaddr_node, 104 &hslot2->head); 105 else 106 hlist_add_head_rcu(&udp_sk(sk)->udp_portaddr_node, 107 &hslot2->head); 108 hslot2->count++; 109 spin_unlock(&hslot2->lock); 110 } 111 sock_set_flag(sk, SOCK_RCU_FREE); 112 error = 0; 113 fail_unlock: 114 spin_unlock_bh(&hslot->lock); 115 fail: 116 return error; 117 }
以bind时:port不为0即port已经选定进行分析;
原文:https://www.cnblogs.com/codestack/p/12774946.html