SO_REUSEADDR和SO_REUSEPORT区别

内容来源于StackOverflow的精彩回答,StackOverflow.

以BSD系统为例。
首先,一个TCP/UDP连接(Connection)的id,就是由下面五个值组成元组。

{<protocol>, <src addr>, <src port>, <dest addr>, <dest port>}

任何合法的五个值的组合都可以定义一个连接,同时,没有任何两个连接具有完全相同的元组。

第一个值protocol是在socket()设定的,src addrsrc port是在bind()的时候设定的,dest addrdest port是在connect()的时候设定的。虽然UDP是一个无连接协议,并不需要connect(),但是在第一次发送数据的时候,UDP connection还是被系统非显式地绑定到了dest addr / port上。

当绑定ip的时候,可以通过绑定到0.0.0.0: port来绑定到所有本地网络地址的对应端口上,也可以绑定到192.168.0.100: port来绑定到特定本地网络地址(回环)的特定端口。

在默认设置下,没有socket能够绑定到同一地址的同一端口。比如在Socket A已经绑定了0.0.0.0:8000以后,Socket B若是想要绑定192.168.0.100:8000,那就会报EADDRINUSE。因为Socket A已经绑定了所有ip地址的8000端口,包括192.168.0.100:8000

SO_REUSEADDR

作用一

在为Socket B设置了SO_REUSEADDR以后,判断冲突的方式就变了。只要地址不是正好(exactly)相同,那么多个Socket就能绑定到同一ip上。比如0.0.0.0192.168.0.100,虽然逻辑意义上前者包含了后者,但是0.0.0.0泛指所有本地ip,而192.168.0.100特指某一ip,两者并不是完全相同,所以Socket B尝试绑定的时候,不会再报EADDRINUSE,而是绑定成功。
下面是测试不同设置下的绑定情况:

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)

可以看到,如果想绑定addr字符串完全相同的ip,那么无论SO_REUSEADDR设置与否,都会报地址已使用。但是在设置了SO_REUSEADDR以后,就可以同时绑定0.0.0.0192.168.1.0两个地址了。

作用二

SO_REUSEADDR的另一个作用是,可以绑定TIME_WAIT状态的地址。

TCP Socket的send()是一个异步调用,当数据送入socket send buffer以后就会返回。也就是说,在send()返回以后,数据仍然需要经历漫长的tcp拥塞控制冲突避免等过程,才能被成功发送。在没有TIME_WAIT状态的前提下,假如这个时候上层程序判断通信完成,关闭了socket,那么缓冲区的数据就会丢失。所以TIME_WAIT这个状态就被用来保证,socket能够续命到buffer中的数据能够全部发送完成或者超时。

TIME_WAIT的时间,也就是超时的时间取决于一个配置项Linger Time。在大多数系统中,他是非常长的2分钟。这意味着两分钟内,socket对应的地址端口是被占用的,无法重新绑定。

一个非常现实的问题是,假如一个systemd托管的service异常退出了,留下了TIME_WAIT状态的socket,那么systemd将会尝试重启这个service。但是因为端口被占用,会导致启动失败,造成两分钟的服务空档期,systemd也可能在这期间放弃重启服务。

但是在设置了SO_REUSEADDR以后,处于TIME_WAIT状态的地址也可以被绑定,就杜绝了这个问题。因为TIME_WAIT其实本身就是半死状态,虽然这样重用TIME_WAIT可能会造成不可预料的副作用,但是在现实中问题很少发生,所以也忽略了它的副作用。

另外,上文中所有SO_REUSEADDR的生效,只需要在后绑定的socket中设置SO_REUSEADDR即可,并没有强制要求以前绑定的socket也设置这一个选项才能完成共享。

SO_REUSEPORT

SO_REUSEPORT干的其实是大众期望SO_REUSEADDR能够干的事,将多个socket绑定到同一ip和端口。并且它要求所有绑定同一ip/port的socket都设置了SO_REUSEPORT。不过可能有的操作系统并没有这个option。

Connect() 返回 EADDRINUSE 问题

在默认情况下,一般在bind()时可能会出现EADDRINUSE问题,connect()时因为src ipsrc port已经不同,不可能报EADDRINUSE。但是在SO_REUSEADDRSO_REUSEPORT下,因为地址有重用,那么当重用的地址端口尝试连接同一个远端主机的同一端口时,就会报EADDRINUSE

比如本机只有两个地址,127.0.0.1192.168.0.1,其中后者是可访问因特网的网卡的地址。在SO_REUSEADDR下,并且Socket A绑定了Socket A0.0.0.0:8000, Socket B绑定了192.168.0.1:8000以后,Socket A发起了与远端主机111.13.101.208:80的连接。此时根据路由表规则,连接将被绑定到192.168.0.1,产生的连接ID为{<SOCK_STREAM>, <192.168.0.1>, <8000>, <111.13.101.208>, <80>},Socket A连接成功。但是如果Socket B也想尝试发起与远端主机111.13.101.208:80的连接,就会产生一样的连接ID,所以报了EADDRINUSE

操作系统的区别

BSD/mac os

没区别

Linux
Linux < 3.9

在Linux 3.9之前,只存在SO_REUSEADDR配置项。他的主要逻辑与BSD相同,但是存在两个意外:

  1. Linux在绑定端口上比BSD更加严格,类似于BSD那种同时绑定通配地址和特定地址的行为,在linux中不被允许。比如在Linux中已经绑定了192.168.0.100:8000,那么即使设置了SO_REUSEADDR,也将无法继续绑定0.0.0.0:8000

  2. 另一个区别是,在Linux的client socket(也就是不需要显式绑定端口,不需要listen的socket),如果设置了SO_REUSEADDR,那么它的作用与BSD中的SO_REUSEPORT完全相同。即Linux允许多个client socket绑定到同一ip的同一端口。这是为了应对需要绑定多个socket到udp地址端口以处理不同的protocol的场景。

Linux >= 3.9

Linux 3.9及之后的版本都添加了SO_REUSEPORT选项,它的工作原理与BSD基本相同,但是依旧多了两个限制:

  1. 为了防止端口挟持,只用属于同一有效uid的进程可以通过设置选项来共享ip及端口。

  2. 对共享端口的UDP socket而言,内核均匀地分发数据报给每一个socket。而对共享端口的TCP socket而言,内核将均匀地分发连接请求(也就是accept()阶段)。这个特性可以用来作为朴素的负载均衡。

Windows

Windows中没有SO_REUSEPORT选项,SO_REUSEADDR承担了SO_REUSEPORT的功能。另外,设置了SO_REUSEADDR的socket总是能绑定到一个已经被占用的ip端口上,即使先来的socket没有设置SO_REUSEADDR。这是很强的安全风险,所以微软后来新加了一个SO_EXCLUSIVEADDRUS的选项来让程序显式地绑定到ip端口上,这样其他socket即使设置了SO_REUSEPORT也无法重用此端口。
Windows提供了详细的介绍,详见Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,165评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,503评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,295评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,589评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,439评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,342评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,749评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,397评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,700评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,740评论 2 313
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,523评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,364评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,755评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,024评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,297评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,721评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,918评论 2 336

推荐阅读更多精彩内容