SO_REUSEADDR和SO_REUSEPORT选项

SO_REUSEADDR和SO_REUSEPORT选项_第1张图片

SO_REUSEADDR

(1)首先对于一个server服务进程来说,它的创建流程是

socket->bind ->listen->accept

创建监听套接字,bind一个指定端口,listen监听端口,为每个连接提供服务(一般会创建子进程)。当服务进程重启时,那么会重新执行一遍上面的流程,如果在创建socket后没有使用SO_REUSEADDR选项进行设置,再次进行bind就会失败。那么原因是什么?
首先需要了解一下TCP协议的4次挥手动作,如文章开头的图片所示,当一个连接在关闭时,会有4次挥手的过程,TIME_WAIT状态存在于关闭发起方发送完最后一个ACK,之所以需要有一个等待时间,是为了以防最后一个ACK丢包。如果最后一个ACK丢包了会导致接收方再次重发一个FIN包,如果发起方此时还在等待,那么将还有正常处理的机会。

正常关闭
主动close socket的一方再发送最后一个ACK后会进入TIME_WAIT,默认情况下,处于TIME_WAIT状态的端口是不能用来listen的。对于服务端listen socket来说,如果在执行close时,等待队列中没有对应的连接,那么状态会直接进入closed,此时再次使用bind操作时理论上是不会有异常的,但是当我们关闭listen socket时,如果不够幸运,等待队列中还有等待处理的连接,那么后续将执行TCP的正常关闭流程,并且服务端作为发起方,那么无疑最后是会进入到TIME_WAIT状态,并且等待一定时间确保接收方已经接收了最后的一个ACK。实际上对于应用来说,执行完close它就已经退出了,而TCP的关闭流程是由内核协议栈管理的,在这个TIME_WAIT的过程中,内核是不允许再次对该地址和端口进行bind的。
异常关闭
如果是异常kill掉进程,对于server端连接状态会直接变为TIME_WAIT,如果是client端异常关闭,那么会导致server端口状态在多个状态上都需要进行等待。因此除非我们显式的指定了SO_REUSEADDR,否则我们都无法在立即重启服务后执行bind操作。

以上这就是SO_REUSEADDR对于服务端来说存在的意义。

(2)对于client进程来说,创建套接字去连接某个监听套接口,一般操作为:

socket->connect

在连接到某个服务器端口上时,我们不用执行bind操作,内核会随机为客户端生成对应的本地地址和端口号。因此这个选项使用到的机会并不会很大,加入我们没有对socket执行该选项,那么主动关闭后,虽然处于TIME_WAIT状态,但是下次执行客户端区连接时,内核会重新为客户端随机产生其他的端口号去连接,所以大多数情况下是用不到的,除非我们想要在客户端也bind端口号。

杀死进程,保证client端执行主动关闭。那么重启进程后,除非上一个连接退出了TIME_WAIT状态,否则重启的进程在调用connect时候错误返回。
主动close socket的一方再发送最后一个ACK后会进入TIME_WAIT,默认情况下,处于TIME_WAIT状态的端口是不能用来LISTEN的。会遇到的问题处于TIME_WAIT的数量过多导致后续无法连接,可以通过tcp_max_syn_backlog修改等待队列长度(这个值包括了ESTABLISHED连接和半连接),还可以用tcp_max_tw_buckets配置项来清除处于TIME_WAIT的连接。
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,(const void *)&reuse , sizeof(int));

SO_REUSEPORT

对于服务器来说默认情况下一个PORT只能被一个服务进程所占用,那么为了增加服务的能力,可以使能该特性,让多个服务进程同时复用同一个端口,提升服务吞吐量。使能该选项可以让多进程或者多线程创建多个socket绑定到同一个PORT上,提高服务器的并发能力。该特性在kernel 3.9 中引入的,因此在旧版本的内核版本上不能会用。


https://hea-www.harvard.edu/~fine/Tech/addrinuse.html

你可能感兴趣的:(网络子系统)