so_reuseport 选项在linux 3.9被引入内核,在这之前也有一个很像的选项so_reuseaddr。
如果你不太清楚这两者的区别和联系,建议搜索 how do so_reuseaddr and so_reuseport differ?。
如果不想读,那么下面这一节算是为懒人准备的。
so_reuseaddr 与 so_reuseport 是什么?
tcp/udp用五元组唯一标识一个连接。
任何时候,两条连接的五元组都不能完全相同,否则当收到一个报文时,协议栈没办法判断它是属于哪个连接的。
五元组{, , , , }
五元组里,protocol在创建socket时确定,和在bind()时确定,和在connect()时确定。
当然,bind()和connect()在一些时候并不需要显式使用,不过这不在本文的讨论范围里。
那么,如果对socket设置了so_reuseaddr和so_reuseport选项,它们什么时候起作用呢?
答案是bind(),也就在确定和时。
不同操作系统内核对待so_reuseaddr和so_reuseport的行为有少许差异,但它们都源自bsd。
因此,接下来就以bsd的实现为标准进行说明。
so_reuseaddr
假设我现在需要bind()将socketa绑定到a:x,将socketb绑定到b:y(不考虑x=0或者y=0,因为0表示让内核自动分配端口,一定不会冲突)。
如果x!=y,那么无论a和b的关系如何,两个bind()都会成功。但如果x==y,那么结果会是下面这样:
so_reuseaddr socketa socketb result --------------------------------------------------------------------- on/off 192.168.0.1:21 192.168.0.1:21 error (eaddrinuse) on/off 192.168.0.1:21 10.0.0.1:21 ok on/off 10.0.0.1:21 192.168.0.1:21 ok off 0.0.0.0:21 192.168.1.0:21 error (eaddrinuse) off 192.168.1.0:21 0.0.0.0:21 error (eaddrinuse) on 0.0.0.0:21 192.168.1.0:21 ok on 192.168.1.0:21 0.0.0.0:21 ok on/off 0.0.0.0:21 0.0.0.0:21 error (eaddrinuse)
第一列表示是否设置so_reuseaddr注,最后一列表示后绑定的socket是否能绑定成功。
注:这里设置的对象是指后绑定的socket(也就是说不关心前一个是否设置)
可以看出,bsd的实现中so_reuseaddr可以让一个使用通配地址(0.0.0.0),一个使用指定地址(192.168.1.0)的socket同时绑定成功。
so_reuseaddr还有一种应用情景:在tcp中存在一个time_wait状态,它是指主动关闭的一端最后停留的阶段。
假设socketa绑定到a:x,在完成tcp通信后主动使用close(),进入time_wait,此时,如果socketb也去绑定a:x,那么同样会得到 eaddrinuse 错误,但如果socketb设置了so_reuseaddr,那么就可以绑定成功。
so_reuseport
如果理解了so_reuseaddr,那么so_reuseport就很好理解了,它让两个socket可以绑定完全相同的。
so_reuseport socketa socketb result --------------------------------------------------------------------- on 192.168.0.1:21 192.168.0.1:21 ok
提醒一下,以上的结果都是bsd的结果,linux内核有一些不一样的地方,具体表现为
3.9版本支持so_reuseport,作为server的tcp socket一旦绑定到了具体的端口,启动了listen,即使它之前设置过so_reuseaddr, 也不会生效。这一点linux比bsd更加严格
so_reuseaddr socketa socketb result --------------------------------------------------------------------- on/off 192.168.0.1:21 0.0.0.0:21 error (eaddrinuse)
3.9版本之前,作为client的socket,so_reuseaddr选项具有bsd中的so_reuseport的效果。这一点linux又比bsd更加宽松。
so_reuseaddr socketa socketb result --------------------------------------------------------------------- on 192.168.0.2:55555 192.168.0.2:55555 ok
linux中reuseport的演进
linux sk_reuse = (valbool ? sk_can_reuse : sk_no_reuse); break; }
inet_bind_bucket表示一个绑定的端口。
struct inet_bind_bucket { /* omitted */ unsigned short port; signed short fastreuse; int num_owners; struct hlist_node node; struct hlist_head owners; };
上面结构中的fastreuse表示该端口是否支持共享,所有共享该端口的socket挂到owner成员上。在用户使用bind()时,内核使用tcp:inet_csk_get_port(),udp:udp_v4_get_port()来绑定端口。
/* inet_connection_sock.c: inet_csk_get_port() */ tb_found: if (!hlist_empty(&tb->owners)) { ...... if (tb->fastreuse > 0 && sk->sk_reuse && sk->sk_state != tcp_listen && smallest_size == -1) { goto success;
所以,当该端口支持共享,且socket也设置了so_reuseaddr并且不为listen状态时,此次bind()可以成功。
3.9 =< linux sk_reuse = (valbool ? sk_can_reuse : sk_no_reuse); break; + case so_reuseport: + sk->sk_reuseport = valbool; + break;
然后对inet_bind_bucket也相应进行了扩展
struct inet_bind_bucket { /* omitted */ unsigned short port; - signed short fastreuse; + signed char fastreuse; + signed char fastreuseport; + kuid_t fastuid;
而在绑定端口时,增加了一个队reuseport的通过条件
/* inet_connection_sock.c: inet_csk_get_port() */ tb_found: if (sk->sk_reuse == sk_force_reuse) goto success; - if (tb->fastreuse > 0 && - sk->sk_reuse && sk->sk_state != tcp_listen && + if (((tb->fastreuse > 0 && + sk->sk_reuse && sk->sk_state != tcp_listen) || + (tb->fastreuseport > 0 && + sk->sk_reuseport && uid_eq(tb->fastuid, uid))) && smallest_size == -1) { goto success;
而当client的syn报文到达时,server会首先根据本地端口(syn报文的)计算出一条hash冲突链,然后遍历该链表上的所有socket,根据四元组匹配程度进行打分;
如果使能了reuseport,那么可能有多个socket都将拿到最高分,此时内核将随机选择一个进行后续处理。
/* inet_hashtables.c */ struct sock *__inet_lookup_listener(struct......) { struct sock *sk, *result; unsigned int hash = inet_lhashfn(net, hnum); struct inet_listen_hashbucket *ilb = &hashinfo->listening_hash[hash]; // 根据本地端口找到hash冲突链 /* code omitted */ result = null; hiscore = 0; sk_nulls_for_each_rcu(sk, node, &ilb->head) { score = compute_score(sk, net, hnum, daddr, dif); // 根据匹配程度进行打分 if (score > hiscore) { result = sk; hiscore = score; reuseport = sk->sk_reuseport; if (reuseport) { phash = inet_ehashfn(net, daddr, hnum, saddr, sport); matches = 1; // 如果是reuseport 则累计多少个socket满足 } } else if (score == hiscore && reuseport) { matches++; if (reciprocal_scale(phash, matches) == 0) result = sk; phash = next_pseudo_random32(phash); } } /* * if the nulls value we got at the end of this lookup is * not the expected one, we must restart lookup. * we probably met an item that was moved to another chain. */ return result; }
举个栗子,假设内核有4条listening socket的hash冲突链,然后用户建立了4个server:a、b、c、d,监听的地址和端口如下图所示,a和b使能了so_reuseport。
冲突链是以端口为key的,因此a、b、d会挂到同一条冲突链上。
如果此时收到对端一个syn报文,那么内核会遍历listening_hash[0],为上面的7个socket进行打分,而由于b监听的是精确的地址,所以b的得分会比a高,内核最终选择出一个socketb进行后续处理。
4.5 listening_hash[hash]; int score, hiscore, matches = 0, reuseport = 0; + bool select_ok = true; u32 phash = 0; rcu_read_lock(); @@ -230,6 +233,15 @@ begin: if (reuseport) { phash = inet_ehashfn(net, daddr, hnum, saddr, sport); + if (select_ok) { + struct sock *sk2; + sk2 = reuseport_select_sock(sk, phash, + skb, doff); + if (sk2) { + result = sk2; + goto found; + } + } matches = 1; } }
上海集成电路设计业首成产业链龙头
薄膜涂层在线测厚仪的工作原理及技术指标
魅族Pro7什么时候上市?魅族Pro7最新消息:魅族Pro7带领魅族走向巅峰!销量一定会超过华为P10
如何配置锐捷SSH远程登陆?
正弦稳态电路的功率计算
SO_REUSEADDR 与 SO_REUSEPORT是什么?
电子分频放大器的制作
特斯拉Model Y:价格香,质量成最大缺点
创维&国美与你共创100个维美瞬间
你们知道碳纤维是怎样“炼”成的吗?
视频监控系统中越来越多的使用了高级的数据存储设备和系统
谷歌的AI钢琴精灵:只需用八个按钮,就可帮助人们即兴创作
AI医学影像研究新突破 为肝癌患者无创分级
中国半导体存储器产业竞争激烈 市场进入新的整合阶段
荷兰情报机构正在调查华为?担心华为参与5G基础设施建设会存在风险
联发科技TAS 2.0到底是什么?
340kW HVDC配置的集中式锂电池机柜助力打造数据中心的新生态
但眼拓客云——构建完整的数字化营销体系
深蓝科技响应号召,复工复产的同时积极开拓海外市场
2021年智能手表市场将迎来增长