Connection refused间歇性出现的问题定位

出现Connection refused的问题原因一般有三种:

1. 服务器的端口没有打开       这种直接就是一直会Connection refused,不会间歇出现,可以直接排除;

2. 服务器的防火墙没有开白名单    很多跟外部对接的时候,是需要将公司出口ip加到对方防火墙白名单,这种也会直接Connection refused,不会间歇出现,可以直接排除;

3. 服务器上的backlog设置的太小,导致连接队列满了,服务器可能会报Connection refused,或者Connecttion reset by peer,这个看服务器上的连接队列满时的设置;

详细的异常堆栈信息如下:

 

 看报错方法:

 

 是个native方法,毫不意外。因为是跟第三方云服务商对接,只能让他们查服务器配置的backlog大小(最后通过将backlog从50调到了4096),这里回顾一下tcp三次握手的过程。

 

正常的发起请求的三次握手如下:

 

 第一步:client 发送syn到server发起握手;

第二步:  server收到syn后回复syn + ack 给client;

第三步:client收到syn + ack后,回复server一个ack表示收到server的syn + ack;

 

Tcp连接详细状态如下图:

 

 1. 服务端调用bind()  & listen() 函数后,会监听本地某个端口,例如8080;

 2. 客户端发SYN,服务端收到,连接状态变为SYN_RCVD,将连接放到半连接队列syns queue中,同时回复syn+ack给client;

 3. 客户端收到syn + ack,回复ack,客户端连接状态变为ESTABLISHED,服务器接收到客户端的ack,先看accept queue是否已满,如果没有满,将连接放到全连接队列,如果满了的话,有二种处理方式:

    根据服务端tcp_abort_on_overflow的配置决定,如果配置为0,会丢弃客户端的ack, 过段时间重发syn + ack,也就是三次握手的第二步(如果客户端超时时间设置的太短,就容易引发Connection refused),如果配置为1,会直接返回RET,客户端的表示就是Connection reset by peer。

其中半连接队列的大小看: max(64, /proc/sys/net/ipv4/tcp_max_syn_backlog

 

上面是我机器上半连接的配置,挺大的,26万。

全连接队列的大小: min(backlog, somaxconn), backlog是在socket创建的时候传入的,somaxconn是一个os级别的系统参数,不同操作系统不一样。

代码涉及到Socket这一层的操作时,需要自己传backlog的大小,否则默认值是50.

1
2
3
public ServerSocket(int port, int backlog) throws IOException {
        this(port, backlog, null);
}

所有上面Connection Refused很容易因为backlog设置的太小而发生,例如,nginx的配置就有backlog, 默认是511,Tomcat 默认是100。

一般来说,如果是公司自己的服务器,可以通过TCP建连接的时候全连接队列(accept队列)满了,通过一些命令可以查询队列情况:

 netstat -s 命令

通过netstat -s | egrep "listen" 看队列的溢出统计数据,多执行几次,看全连接队列overflow次数有没有增长:

 

ss 命令

 

 上面看Send-Q的值就是listen端口上全连接队列的最大值,Recv-Q就是当前全连接队列用了多少。

netstat跟ss命令一样也能看到Send-Q、Recv-Q这些状态信息,不过如果这个连接不是Listen状态的话,Recv-Q就是指收到的数据还在缓存中,还没被进程读取,这个值就是还没被进程读取的 bytes;而 Send 则是发送队列中没有被远程主机确认的 bytes 数。

因此如果出现间歇性Connection Refused,检查是否有设置backlog, backlog设置的是否过小。

压力测试实践:

服务端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
public class BaseSocketServer {
 
    private ServerSocket server;
    private Socket socket;
    private int port;
    private InputStream inputStream;
    private static final int MAX_BUFFER_SIZE = 1024;
 
    public int getPort() {
        return port;
    }
 
    public void setPort(int port) {
        this.port = port;
    }
 
    public BaseSocketServer(int port) {
        this.port = port;
    }
 
 
 
    public void runServerMulti() throws IOException {
        //故意设置backlog就只有10
        this.server = new ServerSocket(this.port, 10);
 
        System.out.println("base socket server started.");
 
        ExecutorService executorService = Executors.newFixedThreadPool(10);
 
        while(true){
            // the code will block here till the request come.
            this.socket = server.accept();
 
            Runnable run = () ->{
                InputStream inputStream = null;
                try {
                    inputStream = this.socket.getInputStream();
                    byte[] readBytes = new byte[1024];
 
                    int msgLen;
                    StringBuilder stringBuilder = new StringBuilder();
 
                    while ((msgLen = inputStream.read(readBytes)) != -1) {
                        stringBuilder.append(new String(readBytes,0, msgLen,"UTF-8"));
                    }
                    System.out.println("get message from client: " + stringBuilder);
                }catch (Exception ex){
                    ex.printStackTrace();
                }finally {
                    try {
                        Thread.sleep(10000);
                        inputStream.close();
                        socket.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            };
            executorService.submit(run);
        }
        //server.close();
    }
 
    public static void main(String[] args) {
 
        BaseSocketServer bs = new BaseSocketServer(9799);
        try {
            bs.runServerMulti();
        }catch (IOException e) {
            e.printStackTrace();
        }
    }
}

  backlog设置的只有10,这样让客户端连接的时候可以快速队列满。

客户端代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class BaseSocketClient {
    private String serverHost;
    private int serverPort;
    private Socket socket;
    private OutputStream outputStream;
 
    public BaseSocketClient(String host, int port) {
        this.serverHost = host;
        this.serverPort = port;
    }
 
    public void connetServer() throws IOException {
        this.socket = new Socket(this.serverHost, this.serverPort);
        this.outputStream = socket.getOutputStream();
        // why the output stream?
    }
 
    public void sendSingle(String message) throws IOException {
        try {
            this.outputStream.write(message.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            System.out.println(e.getMessage());
        }
        this.outputStream.close();
        this.socket.close();
    }
 
    public static void main(String[] args) {
 
        ExecutorService executorService = Executors.newFixedThreadPool(100);
 
        Runnable run  = () ->{
            BaseSocketClient bc = new BaseSocketClient("127.0.0.1",9799);
            try {
                bc.connetServer();
                bc.sendSingle(String.format("%s, 你好, 我是吉米", Thread.currentThread().getName()));
            }catch (IOException e) {
                e.printStackTrace();
            }
        };
 
        for(int i = 0; i < 28; i++){
            executorService.submit(run);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

  然后ss 可以看到全队列overflow次数马上爆了。

posted @   安琪拉的博客(公众号)  阅读(24358)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示