【计算机网络】Socket的SO_REUSEADDR选项与TIME_WAIT

SO_REUSEADDR用于设置套接字的地址重用。当一个套接字关闭后,它的端口可能会在一段时间内处于TIME_WAIT状态,此时无法立即再次绑定相同的地址和端口。使用SO_REUSEADDR选项可以允许新的套接字立即绑定到相同的地址和端口,即使之前的套接字仍处于TIME_WAIT状态。

TIME_WAIT状态的产生

客户端和服务器都可以主动发起关闭连接,上图是客户端主动发起的TCP连接关闭。首先调用close()发起主动关闭的一方,在发送最后一个ACK之后会进入time_wait的状态,也就说该发送方会保持2MSL时间之后才会回到初始状态。在time_wait的状态下,定义这个连接的四元组(客户端IP地址和端口,服务端IP地址和端口号)不能被使用。

MSL:Maximum Segment Lifetime,也就是最大报文生存时间 。

MSL到底是多长时间,可以通过下面的命令来查询,不同的操作系统时间不一样:

$ sysctl -a | grep "ipv4.tcp_fin_timeout"
sysctl: unable to open directory "/proc/sys/fs/binfmt_misc/"
net.ipv4.tcp_fin_timeout = 60

time_wait至少需要持续2MSL时长,这2个MSL中的第一个MSL是为了等自己发出去的最后一个ACK从网络中消失,而第二MSL是为了等在对端收到ACK之前的一刹那可能重传的FIN报文从网络中消失。如果time_wait时间是一个MSL,而time_wait结束后使用了相同的IP和Port建立了新的TCP连接,由于旧连接重传的FIN报文还没有在网络中消失,因此会干扰到新的TCP连接。

SO_REUSEADDR的使用

可以使用Socket类的setReuseAddress()方法来设置SO_REUSEADDR选项的值。示例代码如下:

ServerSocket serverSocket1 = new ServerSocket();
serverSocket1.setReuseAddress(false);
serverSocket1.bind(new InetSocketAddress(8099));
serverSocket1.close();

使用SO_REUSEADDR选项时有两点需要注意:

  1. 必须在调用bind方法之前使用setReuseAddress方法来打开SO_REUSEADDR选项。因此,要想使用SO_REUSEADDR选项,就不能通过Socket类的构造方法来绑定端口。

  2. 必须将绑定同一个端口的所有的Socket对象的SO_REUSEADDR选项都打开才能起作用。

SO_REUSEADDR异常的演示

Socket服务端代码如下:

package com.morris.socket;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

/**
 * Socket客户端,演示SO_REUSEADDR
 *
 * @see java.net.SocketOptions
 */
public class ReuseAddressServerDemo {
   
    public static void main(String[] args) throws IOException {
   

        ServerSocket serverSocket1 = new ServerSocket();
        System.out.println(serverSocket1.getReuseAddress());
        // serverSocket1.setReuseAddress(false);
        serverSocket1.bind(new InetSocketAddress(8099));
        Socket socket = serverSocket1.accept();
        socket.getOutputStream().write("hello".getBytes(StandardCharsets.UTF_8));
        socket.close();
    }
}

Socket客户端代码如下:

package com.morris.socket;

import java.io.IOException;
import java.net.Socket;

/**
 * Socket客户端,演示SO_REUSEADDR
 *
 * @see java.net.SocketOptions
 */
public class ReuseAddressClientDemo {
   
    public static void main(String[] args) throws IOException {
   
        // 这里只建立连接,不发送数据,也不接受数据,也不关闭连接,这样服务端才会出现TIME_WAIT
        new Socket("172.24.104.61", 8099);
        System.in.read();
    }
}

当客户端与服务端建立连接后,服务端发完数据就会立即关闭,此时会进入TIME_WAIT状态:

$ netstat -anotp| grep 8099
tcp6       0      0 172.24.104.61:8099      172.24.104.61:54542     FIN_WAIT2   -                    timewait (57.97/0/0)

我们可以看到四元组172.24.104.61:8099,172.24.104.61:54542代表的这个链接已经进入FIN_WAIT2状态,而且还需要等待57.97s才能进入CLOSE状态。

SO_REUSEADDR选项为true,也就是serverSocket1.setReuseAddress(true),也可以不进行设置,因为默认就是true,允许地址重用,此时再次启动ReuseAddressServerDemo程序,能够成功启动不会抛出端口被占用的异常。

SO_REUSEADDR选项为false,也就是serverSocket1.setReuseAddress(false),不允许地址重用,此时再次启动ReuseAddressServerDemo程序,就会抛出下面的异常:

Exception in thread "main" java.net.BindException: Address already in use
        at java.base/sun.nio.ch.Net.bind0(Native Method)
        at java.base/sun.nio.ch.Net.bind(Net.java:555)
        at java.base/sun.nio.ch.Net.bind(Net.java:544)
        at java.base/sun.nio.ch.NioSocketImpl.bind(NioSocketImpl.java:640)
        at java.base/java.net.ServerSocket.bind(ServerSocket.java:392)
        at java.base/java.net.ServerSocket.bind(ServerSocket.java:340)
        at com.morris.socket.ReuseAddressServerDemo.main(ReuseAddressServerDemo.java:20)

这时只有等四元组172.24.104.61:8099,172.24.104.61:54542代表的这个链接进入CLOSE状态后才能再次启动ReuseAddressServerDemo程序。

SO_REUSEADDR的注意事项与使用场景

在使用SO_REUSEADDR选项时,需要注意以下几点:

  • SO_REUSEADDR选项必须在调用bind()函数之前设置,否则设置不会生效。

  • 在使用SO_REUSEADDR选项时,需要确保不同的套接字使用相同的协议、地址和端口组合。

  • 使用SO_REUSEADDR选项不能使不同的线程或进程监听相同的端口,实现端口服用。

使用场景:

  • 服务器程序重启:当服务器程序需要重启时,为了避免等待操作系统回收端口的时间,可以设置SO_REUSEADDR选项。这样,重启后的服务器程序可以快速绑定到之前使用的端口上,实现快速恢复服务。

  • 测试和调试:在开发和测试阶段,可能需要频繁地启动和停止服务器。使用SO_REUSEADDR可以使得测试更加方便,因为不需要担心地址和端口已经被其他进程使用的问题。

time_wait产生的危害以及如何解决

大量的time_wait产生需要的条件:

  • 高并发

  • 服务器主动关闭连接

如果服务器不主动关闭连接,那么TIME_WAIT就是客户端的事情了

在高并发短连接的TCP服务器上,当服务器处理完请求后立刻主动正常关闭连接。这个场景下会出现大量socket处于time_wait状态。在time_wait的状态下,定义这个连接的四元组(客户端IP地址和端口,服务端IP地址和端口号)不能被使用。因此,高并发可以让服务器在短时间范围内同时占用大量端口。如果客户端的并发量持续很高,此时部分客户端就会因为端口已经被占用而显示连接不上。

如何解决大量time_wait产生的危害?

  • 调整优化linux内核参数:出现大量TIME_WAIT的情况,一般是服务端没有及时回收端口,可以缩减time_wait时间。

  • 服务器不主动关闭连接

  • 重用端口:设置套接字选项为SO_REUSEADDR,告诉操作系统,如果端口忙,但占用该端口TCP连接处于TIME_WAIT状态,则该端口可被重用。如果TCP连接处于其他状态,依然返回端口被占用。该选项对服务程序重启非常有用。

  • 使用长连接:HTTP请求的头部,connection设置为keep-alive

相关推荐

  1. Python网络编程:socket模块入门实践

    2024-02-02 16:02:03       23 阅读
  2. 计算机网络形成发展

    2024-02-02 16:02:03       29 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-02-02 16:02:03       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-02-02 16:02:03       100 阅读
  3. 在Django里面运行非项目文件

    2024-02-02 16:02:03       82 阅读
  4. Python语言-面向对象

    2024-02-02 16:02:03       91 阅读

热门阅读

  1. 浅谈 Unix Timestamp 时间戳

    2024-02-02 16:02:03       45 阅读
  2. ORA-65096: 公用用户名或角色名无效

    2024-02-02 16:02:03       46 阅读
  3. Redis为什么快?

    2024-02-02 16:02:03       54 阅读
  4. MySQL中的约束(七)

    2024-02-02 16:02:03       47 阅读
  5. mysql innodb 之 buffer pool

    2024-02-02 16:02:03       44 阅读
  6. HttpSession

    2024-02-02 16:02:03       55 阅读
  7. 零信任安全架构发展趋势

    2024-02-02 16:02:03       50 阅读
  8. go语言-context的基本使用

    2024-02-02 16:02:03       52 阅读
  9. 民安智库开展空气污染治理满意度调研

    2024-02-02 16:02:03       54 阅读
  10. 如何提高Bito生成函数代码的准确度

    2024-02-02 16:02:03       48 阅读
  11. L1-018 大笨钟分数 10

    2024-02-02 16:02:03       60 阅读
  12. SpringBoot集成Redisson实现限流(二)

    2024-02-02 16:02:03       59 阅读