Wireshark抓包與常見問題解決


簡介

Wireshark是一個網絡抓包分析軟件,當線上出現各種連接相關的問題,如連接不復用,大量CLOSE_WAIT時,可以方便的使用Wireshark抓包軟件進行抓包分析

安裝

Wirewark在windows系統上默認使用的是WinPcap來抓包的,只能看到經過網卡的流量,看不到訪問localhost的流量,可先安裝Npcap,安裝Wirewark時再選擇不安裝WInPcap即可抓localhost的包

基本使用

window下,直接只用wireshark客戶端進行抓包

Linux下,使用tcpdump產生pcap文件,再通過wireshark導入分析
tcpdump -s0 host 192.168.162.103 and port 9999 -w my.pcap

典型場景分析

服務端代碼:

public class SocketServer {
    public static void main(String[] args) throws Exception {
        ServerSocket server = new ServerSocket();
        server.bind(new InetSocketAddress((InetAddress)null, 9999));
        Socket socket = server.accept();
 //       socket.setKeepAlive(true); 默認情況下不進行心跳檢測
        InputStream in = socket.getInputStream();
        OutputStream out = socket.getOutputStream();
        Runtime.getRuntime().addShutdownHook(new Thread(()-> {
            try {
                socket.close();
                server.close();
            } catch (IOException e) {
                System.out.println("close exception" + e);
            }
        }));
        while(true) {
            byte[] bytes = new byte[1024];
            int size = in.read(bytes);
            System.out.println("server read " + new String(Arrays.copyOf(bytes, size)));
            out.write("hello client".getBytes());
            out.flush();
        }
    } 
}

客戶端代碼:

public class SocketClient {
    public static void main(String[] args) throws Exception {
        Socket socket = new Socket("192.168.162.105", 9999);
        OutputStream out = socket.getOutputStream();
        out.write("hello server".getBytes());
        out.flush();
        
        InputStream in = socket.getInputStream();
        byte[] bytes = new byte[1024];
        int size = in.read(bytes);
        System.out.println("server read " + new String(Arrays.copyOf(bytes, size)));
//        Thread.sleep(22 * 1000);  休眠一段時間使得服務端進行心跳檢測
        out.close();
        in.close();
        socket.close();
    } 
}

1 觀察三次握手、四次揮手


其中四次揮手只有3個tcp分組原因:四次揮手的時候,兩個方向的斷開是獨立的,每個方向發送一個FIN,對方回復一個ACK,但同時,TCP規定ACK可以捎帶在其他數據包當中,所以你看到的主動斷開連接一方本應收到的ACK,是被對方的FIN包捎帶過來的,就變成了三個包。並不是所有的情況下都是這樣,典型的一種情況是,主動斷開的一方發送FIN之后,被動一方仍然有數據要繼續發送,就會先ACK這個FIN,然后繼續發送數據(在此過程中主動斷開一方仍然會繼續ACK這些數據),直到數據發送完畢之后再發送FIN並接收對方的ACK。

2 觀察tcp心跳檢測機制(放開注釋)

tcp心跳服務端參數說明

2.1 模擬客戶端一段時間不傳輸數據


服務器net.ipv4.tcp_keepalive_intvl = 10,以上代碼客戶端sleep了22s,因此服務端進行了2次心跳檢測

2.2 模擬MySQL Client突然掉線,抓取Server端

  • 最后一次正常請求后10s,服務端開始發送心跳包
  • 心跳包間隔3秒,發送3次
  • 3次后,服務端關閉連接

3 線上CLOSE_WAIT問題

在被動關閉連接情況下,在已經接收到FIN,但是還沒有發送自己的FIN的時刻,連接處於CLOSE_WAIT狀態,由此分析可知通常是被動關閉方代碼問題。
線上應用程序引入連接池后,訪問clickhouse出現CLOSE_WAIT

  • 可以看到客戶端一直沒有回復FIN,研究clickhouse客戶端源碼實現發現clickhouse底層基於HttpClient實現JDBC接口,由於Http服務端並不會永久保持連接,當服務端超過Keep-Alive時間后會主動關閉連接,而客戶端使用連接池后,不會釋放關閉連接,導致客戶端CLOSE_WAIT
  • 因此通過增加服務端和客戶端的http層Keep-Alive時間,可以緩解這個問題,但是並不能根本解決
  • 由於HttpClient本身可以支持多個連接,所以對一個Connection進行管理,即可支持連接池,后續舍棄了Druid連接池,自己進行了客戶端JDBC封裝

此問題中客戶端沒有關閉連接,發送FIN導致客戶端處於CLOSE_WAIT狀態,理論上服務端沒有收到FIN,應該處於FIN_WAIT_2的狀態,但實際觀察發現服務端已經完全關閉了,查看TCP配置發現,FIN_WAIT_2可通過tcp_fin_timeout配置FIN_WAIT_2超時時間,一旦超時會直接進入CLOSED狀態,而不經過TIME_WAIT

4 服務端TIME_WAIT

主動關閉TIME_WAIT,被動關閉CLOSE_WAIT

TIME_WAIT時間配置內核沒有透出,如果要改需重新編譯內核
查看內核源碼發現,默認TIME_WAIT時間為60s

https://yq.aliyun.com/ziliao/256040

Java客戶端Socket常用配置(進程級別配置)

  • socket.setKeepAlive(true);
    是否開啟tcp心跳檢測機制,默認不開啟,開啟后會根據OS tcp_keepalive_time、tcp_keepalive_intvl、tcp_keepalive_probes進行心跳檢測
  • socket.setReuseAddress(true);
    允許復用處於TIME_WAIT的socket
  • socket.setTcpNoDelay(true);在默認情況下,客戶端向服務器發送數據時,會根據數據包的大小決定是否立即發送。當數據包中的數據很少時,如只有1個字節,而數據包的頭卻有幾十個字節(IP頭+TCP頭)時,系統會在發送之前先將較小的包合並到較大的包后,一起將數據發送出去。在發送下一個數據包時,系統會等待服務器對前一個數據包的響應,當收到服務器的響應后,再發送下一個數據包,這就是所謂的Nagle算法;在默認情況下,Nagle算法是開啟的。
    這種算法雖然可以有效地改善網絡傳輸的效率,但對於網絡速度比較慢,而且對實現性的要求比較高的情況下(如游戲、Telnet等),使用這種方式傳輸數據會使得客戶端有明顯的停頓現象。因此,最好的解決方案就是需要Nagle算法時就使用它,不需要時就關閉它。而使用setTcpToDelay正好可以滿足這個需求。當使用setTcpNoDelay(true)將Nagle算法關閉后,客戶端每發送一次數據,無論數據包的大小都會將這些數據發送出
  • socket.setSoLinger(true, 0);
  • socket.setSoTimeout(soTimeout);
    配置inputstream一個阻塞read的超時時間

Linux常用TCP參數(OS級別配置)

  • net.ipv4.tcp_keepalive_time
    當keepalive起用的時候,TCP發送keepalive消息的頻度,單位為秒,缺省是7200秒(即2小時)
  • net.ipv4.tcp_keepalive_intvl
    keepalive探測包的發送間隔
  • net.ipv4.tcp_keepalive_probes
    如果對方不予應答,探測包的發送次數
  • net.ipv4.tcp_timestamps
    為1表示開啟TCP時間戳,用來計算往返時間RTT(Round-Trip Time)和防止序列號回繞
  • net.ipv4.tcp_tw_reuse
    為1表示允許將TIME-WAIT的句柄重新用於新的TCP連接
  • net.ipv4.tcp_tw_recycle
    為1表示開啟TCP連接中TIME-WAIT的快速回收,NAT環境可能導致DROP掉SYN包(回復RST),不要輕易與net.ipv4.tcp_timestamps一起開啟
  • net.ipv4.tcp_fin_timeout
    FIN_WAIT_2狀態的超時時長
  • net.ipv4.tcp_syncookies
    為1時SYN Cookies,當SYN等待隊列溢出時啟用cookies來處理,可防范少量SYN攻擊
  • net.ipv4.tcp_max_tw_buckets
    保持TIME_WAIT套接字的最大個數,超過這個數字TIME_WAIT套接字將立刻被清除並打印警告信息
  • net.ipv4.ip_local_port_range
    設定tcp客戶端發起連接隨機端口范圍,默認32768,61000,這個配置限制了此機器訪問外部機器的連接數目
  • net.ipv4.tcp_max_syn_backlog
    端口最大backlog內核限制,防止占用過大內核內存
  • net.ipv4.tcp_syn_retries
    對一個新建連接,內核要發送多少個SYN連接請求才決定放棄,不應該大於255
  • net.ipv4.tcp_retries1
    放棄回應一個TCP連接請求前﹐需要進行多少次重試,RFC規定最低的數值是3,這也是默認值
  • net.ipv4.tcp_retries2
    在丟棄激活(已建立通訊狀況)的TCP連接之前﹐需要進行多少次重試,默認值為15
  • net.ipv4.tcp_synack_retries
    TCP三次握手的SYN/ACK階段重試次數,缺省5
  • net.ipv4.tcp_max_orphans
    不屬於任何進程(已經從進程上下文中刪除)的sockets最大個數,超過這個值會被立即RESET,並同時顯示警告信息
  • net.ipv4.tcp_orphan_retries
    孤兒sockets廢棄前重試的次數,缺省值是7
  • net.ipv4.tcp_mem
    內核分配給TCP連接的內存,單位是page:
    第一個數字表示TCP使用的page少於此值時,內核不進行任何處理(干預),
    第二個數字表示TCP使用的page超過此值時,內核進入“memory pressure”壓力模式,
    第三個數字表示TCP使用的page超過些值時,報“Out of socket memory”錯誤,TCP 連接將被拒絕
  • net.ipv4.tcp_rmem
    為每個TCP連接分配的讀緩沖區內存大小,單位是byte
  • net.ipv4.tcp_wmem
    為每個TCP連接分配的寫緩沖區內存大小,單位是byte:
    第一個數字表示,為TCP連接分配的最小內存,
    第二個數字表示,為TCP連接分配的缺省內存,
    第三個數字表示,為TCP連接分配的最大內存(net.core.wmem_max可覆蓋該值)

參考文檔:
https://www.cnblogs.com/wangjq19920210/p/8440824.htm
https://www.zhihu.com/question/55890292
https://yq.aliyun.com/articles/581106
http://elf8848.iteye.com/blog/1739598
https://segmentfault.com/a/1190000012345710
TIME_WAIT很好的文章:
https://jin-yang.github.io/post/network-tcpip-timewait.html
tcp_timestamps抓包分析文章:
http://www.bubuko.com/infodetail-1650846.html


免責聲明!

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



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