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.

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設置的是否過小。

壓力測試實踐:

服務端代碼:

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,這樣讓客戶端連接的時候可以快速隊列滿。

客戶端代碼如下:

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次數馬上爆了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM