浅谈从Linux源码分析bind系统调用
扫描二维码
随时随地手机看文章
作者一直认为,从应用程序到框架再到系统,使用每一个代码是一回事理解。使用“今天,作者将研究服务器端套接字的功能。准确地说,它是bind(基于linux3.10)。
一个最简单的Server端例子
众所周知,一个Server端Socket的建立,需要socket、bind、listen、accept四个步骤。
代码如下:
首先我们通过socket系统调用创建了一个socket,其中指定了SOCK_STREAM,而且最后一个参数为0,也就是建立了一个通常所有的TCP Socket。在这里,我们直接给出TCP Socket所对应的ops也就是操作函数。
如果你想知道上图中的结构是怎么来的,可以看下笔者以前的博客:
bind系统调用
bind将一个本地协议地址(protocol:ip:port)赋予一个套接字。例如32位的ipv4地址或128位的ipv6地址+16位的TCP活UDP端口号。
好了,我们直接进入Linux源码调用栈吧。
inet_bind
inet_bind这个函数主要做了两个操作,一是检测是否允许bind,而是获取可用的端口号。这边值得注意的是。如果我们设置需要bind的端口号为0,那么Kernel会帮我们随机选择一个可用的端口号来进行bind!
让我们看下inet_bind的流程
值得注意的是,由于对于<1024的端口号需要CAP_NET_BIND_SERVICE,我们在监听80端口号(例如启动nginx时候),需要使用root用户或者赋予这个可执行文件CAP_NET_BIND_SERVICE权限。
我们的bind允许绑定到0.0.0.0即INADDR_ANY这个地址上(一般都用这个),它意味着内核去选择IP地址。对我们最直接的影响如下图所示:
然后,我们看下一个比较复杂的函数,即可用端口号的选择过程inet_csk_get_port (sk->sk_prot->get_port)
inet_csk_get_port
第一段,如果bind port为0,随机搜索可用端口号
直接上源码,第一段代码为端口号为0的搜索过程
由于,我们在使用bind的时候很少随机端口号(在TCP服务器来说尤其如此),这段代码笔者就注释一下。一般只有一些特殊的远程过程调用(RPC)中会使用随机Server端随机端口号。
第二段,找到端口号或已经指定
判断端口号是否冲突
在上述源码中,判断端口号时否冲突的代码为
上面代码的逻辑如下图所示:
SO_REUSEADDR和SO_REUSEPORT
上面的代码有点绕,笔者就讲一下,对于我们日常开发要关心什么。 我们在上面的bind里面经常见到sk_reuse和sk_reuseport这两个socket的Flag。这两个Flag能够决定是否能够bind(绑定)成功。这两个Flag的设置在C语言里面如下代码所示:
在原生JAVA中
在Netty(Netty版本 >= 4.0.16且Linux内核版本>=3.9以上)中,可以使用SO_REUSEPORT。
SO_REUSEADDR
在之前的源码里面,我们看到判断bind是否冲突的时候,有这么一个分支
如果sk2(即已bind的socket)是TCP_LISTEN状态或者,sk2和新sk两者都没有设置_REUSEADDR的时候,可以判断为冲突。
我们可以得出,如果原sock和新sock都设置了SO_REUSEADDR的时候,只要原sock不是Listen状态,都可以绑定成功,甚至ESTABLISHED状态也可以!
这个在我们平常工作中,最常见的就是原sock处于TIME_WAIT状态,这通常在我们关闭Server的时候出现,如果不设置SO_REUSEADDR,则会绑定失败,进而启动不来服务。而设置了SO_REUSEADDR,由于不是TCP_LISTEN,所以可以成功。
这个特性在紧急重启以及线下调试的非常有用,建议开启。
SO_REUSEPORT
SO_REUSEPORT是Linux在3.9版本引入的新功能。
我们看下一般的Reactor线程模型,
明显的其单线程listen/accept会存在瓶颈(如果采用多线程epoll accept,则会惊群,加WQ_FLAG_EXCLUSIVE可以解决一部分),尤其是在采用短链接的情况下。 鉴于此,Linux增加了SO_REUSEPORT,而之前bind中判断是否冲突的下面代码也是为这个参数而添加的逻辑:
这段代码让我们在多次bind的时候,如果设置了SO_REUSEPORT的时候不会报错,也就是让我们有个多线程(进程)bind/listen的能力。如下图所示:
而开启了SO_REUSEPORT后,代码栈如下:
直接在内核层面做负载均衡,将accept的任务分散到不同的线程的不同socket上(Sharding),毫无疑问可以多核能力,大幅提升连接成功后的socket分发能力。
Nginx已经采用SO_REUSEPORT
Nginx在1.9.1版本的时候引入了SO_REUSEPORT,配置如下:
总结
Linux有一个非常复杂的内核源代码,希望能对读者有所帮助。