作者一直认为,使用从应用程序到框架再到系统的所有代码都是一件容易理解的事情。
今天,作者将使用“服务器端套接字的功能”进行研究。
确切地说,它是绑定的(基于linux3.10)。
最简单的服务器端示例之一是众所周知的。
服务器端套接字的建立需要套接字,绑定,监听和接受四个步骤。
代码如下:首先,我们通过套接字系统调用创建一个套接字,其中指定了SOCK_STREAM,最后一个参数为0,即正常的所有TCP套接字均已建立。
在这里,我们直接给出与TCP套接字相对应的ops是操作函数。
如果您想知道上图中的结构是如何产生的,可以阅读作者以前的博客:绑定系统调用绑定到本地协议地址(protocol:ip:port)分配套接字。
例如,32位ipv4地址或128位ipv6地址+ 16位TCP或UDP端口号。
好的,让我们直接进入Linux源调用堆栈。
inet_bind inet_bind此函数主要执行两个操作,一个是检测是否允许绑定,而是获取可用的端口号。
这里值得一提。
如果我们将需要绑定的端口号设置为0,那么内核将帮助我们随机选择一个可用的端口号进行绑定!让我们看一下inet_bind的过程。
值得注意的是,由于允许我们的绑定绑定到地址0.0.0.0 INADDR_ANY(通常使用此地址),因此这意味着内核将选择IP地址。
对我们最直接的影响如下图所示:然后,让我们看下一个更复杂的函数,我们可以使用端口号选择过程inet_csk_get_port(sk-> sk_prot-> get_port)inet_csk_get_port第一段,如果绑定端口为0,则随机搜索可用的端口号。
直接转到源代码。
第一部分代码是端口号为0的搜索过程。
由于使用绑定时(特别是对于TCP服务器)我们很少随机使用端口号,因此我将对此代码进行注释。
通常,在特殊的远程过程调用(RPC)中仅使用一些随机服务器端口号。
在第二段中,找到或已指定端口号以确定端口号是否冲突。
在上面的源代码中,用于确定端口号是否冲突的代码是上面的代码。
逻辑如下图所示:SO_REUSEADDR和SO_REUSEPORT上面的代码有些复杂,作者将在下面讨论我们应该关注的内容。
我们的日常发展。
我们经常在上面的绑定中看到两个套接字sk_reuse和sk_reuseport的标志。
这两个标志可以确定绑定(绑定)是否成功。
这两个标志的设置以C语言在以下代码中显示:在Netty的本机JAVA中(Netty版本> = 4.0.16和Linux内核版本> = 3.9及更高版本),可以使用SO_REUSEPORT。
SO_REUSEADDR在前面的源代码中,我们看到在判断绑定是否冲突时,存在这样的分支。
如果sk2(即已绑定的套接字)处于TCP_LISTEN状态,或者sk2和new sk都未设置_REUSEADDR,则可以确定为冲突。
我们可以得出结论,如果原始袜子和新袜子都设置了SO_REUSEADDR,只要原始袜子不处于Listen状态,就可以成功绑定它,甚至ESTABLISHED状态也可以!在我们的日常工作中,最常见的是原始袜子,处于TIME_WAIT状态,这通常是在关闭服务器时发生的。
如果未设置SO_REUSEADDR,则绑定将失败,并且该服务将无法启动。
并且设置了SO_REUSEADDR,因为它不是TCP_LISTEN,所以可以成功。
此功能对于紧急重启和脱机调试非常有用。
建议启用它。
SO_REUSEPORT SO_REUSEPORT是Linux在3.9版中引入的一项新功能。
让我们看一下一般的Reactor线程模型。
显然,其单线程侦听/接受将有一个瓶颈(如果使用了多线程epoll accept,将会感到震惊,并且WQ_FLAG_EXCLUSIVE可以解决部分问题),尤其是在使用短链接时。
有鉴于此,Linux添加了SO_REUSEPORT,并且在前一个绑定中的以下代码(以确定是否存在冲突)也为该参数添加了逻辑:该代码允许我们绑定多次,如果设置了SO_REUSEPORT,它将不会绑定。
报告错误,是为了让我们拥有多线程(进程)的绑定/侦听能力。
如下图所示:打开SO_REUSEPORT之后,代码堆栈如下所示:直接在内核级别执行负载平衡,并将accept任务分配给不同线程的不同套接字(共享),这些套接字无疑可以核心功能,极大地提高了连接成功后套接字分配的能力。
Nginx已采用SO_REUSEPORT Nginx在版本1.9.1中引入了SO_REUSEPORT,即配置