精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

從一次線上問題說起,詳解 TCP 半連接隊列、全連接隊列

網絡 通信技術
某次大促值班 ing,對系統穩(wěn)定性有著充分信心、心態(tài)穩(wěn)如老狗的筆者突然收到上游反饋有萬分幾的概率請求我們 endpoint 會出現 Connection timeout 。

[[431611]]

本文轉載自微信公眾號「云巔論劍」,作者黃剛。轉載本文請聯系云巔論劍公眾號。

前言

某次大促值班 ing,對系統穩(wěn)定性有著充分信心、心態(tài)穩(wěn)如老狗的筆者突然收到上游反饋有萬分幾的概率請求我們 endpoint 會出現 Connection timeout 。此時系統側的 apiserver 集群水位在 40%,離極限水位還有著很大的距離,當時通過緊急擴容 apiserver 集群后錯誤率降為了 0。事后進行了詳細的問題排查,定位分析到問題根因出現在系統連接隊列被打滿導致,之前筆者對 TCP 半連接隊列、全連接隊列不太了解,只依稀記得 《TCP/IP 詳解》中好像有好像提到過這兩個名詞。

目前網上相關資料都比較零散,并且有些是過時或錯誤的結論,筆者在調查問題時踩了很多坑。痛定思痛,筆者查閱了大量資料并做了眾多實驗進行驗證,梳理了這篇 TCP 半連接隊列、全連接詳解,當你細心閱讀完這篇文章后相信你可以對 TCP 半連接隊列、全連接隊列有更充分的認識。

本篇文章將結合理論知識、內核代碼、操作實驗為你呈現如下內容:

  • 半連接隊列、全連接隊列介紹
  • 常用命令介紹
  • 全連接隊列實戰(zhàn) —— 最大長度控制、全連接隊列溢出實驗、實驗結果分析...
  • 半連接隊列實戰(zhàn) —— 最大長度控制、半連接隊列溢出實驗、實驗結果分析...
  • ...

半連接隊列、全連接隊列

在 TCP 三次握手的過程中,Linux 內核會維護兩個隊列,分別是:

  • 半連接隊列 (SYN Queue)
  • 全連接隊列 (Accept Queue)

正常的 TCP 三次握手過程:

1、Client 端向 Server 端發(fā)送 SYN 發(fā)起握手,Client 端進入 SYN_SENT 狀態(tài)

2、Server 端收到 Client 端的 SYN 請求后,Server 端進入 SYN_RECV 狀態(tài),此時內核會將連接存儲到半連接隊列(SYN Queue),并向 Client 端回復 SYN+ACK

3、Client 端收到 Server 端的 SYN+ACK 后,Client 端回復 ACK 并進入 ESTABLISHED 狀態(tài)

4、Server 端收到 Client 端的 ACK 后,內核將連接從半連接隊列(SYN Queue)中取出,添加到全連接隊列(Accept Queue),Server 端進入 ESTABLISHED 狀態(tài)

5、Server 端應用進程調用 accept 函數時,將連接從全連接隊列(Accept Queue)中取出

半連接隊列和全連接隊列都有長度大小限制,超過限制時內核會將連接 Drop 丟棄或者返回 RST 包。

相關指標查看

ss 命令

通過 ss 命令可以查看到全連接隊列的信息

  1. # -n 不解析服務名稱 
  2. # -t 只顯示 tcp sockets 
  3. # -l 顯示正在監(jiān)聽(LISTEN)的 sockets 
  4.  
  5. $ ss -lnt 
  6. State      Recv-Q Send-Q    Local Address:Port         Peer Address:Port 
  7. LISTEN     0      128       [::]:2380                  [::]:* 
  8. LISTEN     0      128       [::]:80                    [::]:* 
  9. LISTEN     0      128       [::]:8080                  [::]:* 
  10. LISTEN     0      128       [::]:8090                  [::]:* 
  11.  
  12. $ ss -nt 
  13. State      Recv-Q Send-Q    Local Address:Port         Peer Address:Port 
  14. ESTAB      0      0         [::ffff:33.9.95.134]:80                   [::ffff:33.51.103.59]:47452 
  15. ESTAB      0      536       [::ffff:33.9.95.134]:80                  [::ffff:33.43.108.144]:37656 
  16. ESTAB      0      0         [::ffff:33.9.95.134]:80                   [::ffff:33.51.103.59]:38130 
  17. ESTAB      0      536       [::ffff:33.9.95.134]:80                   [::ffff:33.51.103.59]:38280 
  18. ESTAB      0      0         [::ffff:33.9.95.134]:80                   [:: 

對于 LISTEN 狀態(tài)的 socket

  • Recv-Q:當前全連接隊列的大小,即已完成三次握手等待應用程序 accept() 的 TCP 鏈接
  • Send-Q:全連接隊列的最大長度,即全連接隊列的大小

對于非 LISTEN 狀態(tài)的 socket

  • Recv-Q:已收到但未被應用程序讀取的字節(jié)數
  • Send-Q:已發(fā)送但未收到確認的字節(jié)數

相關內核代碼:

  1. // https://github.com/torvalds/linux/blob/master/net/ipv4/tcp_diag.c 
  2. static void tcp_diag_get_info(struct sock *sk, struct inet_diag_msg *r, 
  3.             void *_info) 
  4.   struct tcp_info *info = _info; 
  5.  
  6.   if (inet_sk_state_load(sk) == TCP_LISTEN) { // socket 狀態(tài)是 LISTEN 時 
  7.     r->idiag_rqueue = READ_ONCE(sk->sk_ack_backlog);  // 當前全連接隊列大小 
  8.     r->idiag_wqueue = READ_ONCE(sk->sk_max_ack_backlog); // 全連接隊列最大長度 
  9.   } else if (sk->sk_type == SOCK_STREAM) {    // socket 狀態(tài)不是 LISTEN 時 
  10.     const struct tcp_sock *tp = tcp_sk(sk); 
  11.  
  12.     r->idiag_rqueue = max_t(int, READ_ONCE(tp->rcv_nxt) - 
  13.                READ_ONCE(tp->copied_seq), 0);    // 已收到但未被應用程序讀取的字節(jié)數 
  14.     r->idiag_wqueue = READ_ONCE(tp->write_seq) - tp->snd_una;   // 已發(fā)送但未收到確認的字節(jié)數 
  15.   } 
  16.   if (info) 
  17.     tcp_get_info(sk, info); 

netstat 命令

通過 netstat -s 命令可以查看 TCP 半連接隊列、全連接隊列的溢出情況

  1. $ netstat -s | grep -i "listen" 
  2.     189088 times the listen queue of a socket overflowed 
  3.     30140232 SYNs to LISTEN sockets dropped 

上面輸出的數值是累計值,分別表示有多少 TCP socket 鏈接因為全連接隊列、半連接隊列滿了而被丟棄

  • 189088 times the listen queue of a socket overflowed 代表有 189088 次全連接隊列溢出
  • 30140232 SYNs to LISTEN sockets dropped 代表有 30140232 次半連接隊列溢出

在排查線上問題時,如果一段時間內相關數值一直在上升,則表明半連接隊列、全連接隊列有溢出情況

實戰(zhàn) —— 全連接隊列

全連接隊列最大長度控制

TCP 全連接隊列的最大長度由 min(somaxconn, backlog) 控制,其中:

  • somaxconn 是 Linux 內核參數,由 /proc/sys/net/core/somaxconn 指定
  • backlog 是 TCP 協議中 listen 函數的參數之一,即 int listen(int sockfd, int backlog) 函數中的 backlog 大小。在 Golang 中,listen 的 backlog 參數使用的是 /proc/sys/net/core/somaxconn 文件中的值。

相關內核代碼:

  1. // https://github.com/torvalds/linux/blob/master/net/socket.c 
  2.  
  3. /* 
  4.  *  Perform a listen. Basically, we allow the protocol to do anything 
  5.  *  necessary for a listen, and if that works, we mark the socket as 
  6.  *  ready for listening. 
  7.  */ 
  8. int __sys_listen(int fd, int backlog) 
  9.   struct socket *sock; 
  10.   int err, fput_needed; 
  11.   int somaxconn; 
  12.  
  13.   sock = sockfd_lookup_light(fd, &err, &fput_needed); 
  14.   if (sock) { 
  15.     somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;  // /proc/sys/net/core/somaxconn 
  16.     if ((unsigned int)backlog > somaxconn) 
  17.       backlog = somaxconn;   // TCP 全連接隊列最大長度 min(somaxconn, backlog) 
  18.  
  19.     err = security_socket_listen(sock, backlog); 
  20.     if (!err) 
  21.       err = sock->ops->listen(sock, backlog); 
  22.  
  23.     fput_light(sock->file, fput_needed); 
  24.   } 
  25.   return err; 

實驗

服務端 server 代碼

  1. package main 
  2.  
  3. import ( 
  4.   "log" 
  5.   "net" 
  6.   "time" 
  7.  
  8. func main() { 
  9.   l, err := net.Listen("tcp"":8888"
  10.   if err != nil { 
  11.     log.Printf("failed to listen due to %v", err) 
  12.   } 
  13.   defer l.Close() 
  14.   log.Println("listen :8888 success"
  15.  
  16.   for { 
  17.     time.Sleep(time.Second * 100) 
  18.   } 

在測試環(huán)境查看 somaxconn 的值為 128

  1. $ cat /proc/sys/net/core/somaxconn 
  2. 128 

啟動服務端,通過 ss -lnt | grep :8888 確認全連接隊列大小

  1. LISTEN     0      128       [::]:8888                  [::]:* 

全連接隊列最大長度為 128

現在更新 somaxconn 值為 1024,再重新啟動服務端。

1、更新 /etc/sysctl.conf 文件,該文件為內核參數配置文件

a.新增一行 net.core.somaxconn=1024

2、執(zhí)行 sysctl -p 使配置生效

  1. $ sudo sysctl -p 
  2. net.core.somaxconn = 1024 

3、檢查 /proc/sys/net/core/somaxconn 文件,確認 somaxconn 為更新后的 1024

  1. $ cat /proc/sys/net/core/somaxconn 
  2. 1024 

重新啟動服務端, 通過 ss -lnt | grep :8888 確認全連接隊列大小

  1. $ ss -lnt | grep 8888 
  2. LISTEN     0      1024      [::]:8888                  [::]:* 

可以看到,現在全鏈接隊列最大長度為 1024,成功更新。

全連接隊列溢出

下面來驗證下全連接隊列溢出會發(fā)生什么情況,可以通過讓服務端應用只負責 Listen 對應端口而不執(zhí)行 accept() TCP 連接,使 TCP 全連接隊列溢出。

實驗物料

服務端 server 代碼

  1. // server 端監(jiān)聽 8888 tcp 端口 
  2.  
  3. package main 
  4.  
  5. import ( 
  6.   "log" 
  7.   "net" 
  8.   "time" 
  9.  
  10. func main() { 
  11.   l, err := net.Listen("tcp"":8888"
  12.   if err != nil { 
  13.     log.Printf("failed to listen due to %v", err) 
  14.   } 
  15.   defer l.Close() 
  16.   log.Println("listen :8888 success"
  17.  
  18.   for { 
  19.     time.Sleep(time.Second * 100) 
  20.   } 

客戶端 client 代碼

  1. // client 端并發(fā)請求 10 次 server 端,成功建立 tcp 連接后向 server 端發(fā)送數據 
  2. package main 
  3.  
  4. import ( 
  5.   "context" 
  6.   "log" 
  7.   "net" 
  8.   "os" 
  9.   "os/signal" 
  10.   "sync" 
  11.   "syscall" 
  12.   "time" 
  13.  
  14. var wg sync.WaitGroup 
  15.  
  16. func establishConn(ctx context.Context, i int) { 
  17.   defer wg.Done() 
  18.   conn, err := net.DialTimeout("tcp"":8888"time.Second*5) 
  19.   if err != nil { 
  20.     log.Printf("%d, dial error: %v", i, err) 
  21.     return 
  22.   } 
  23.   log.Printf("%d, dial success", i) 
  24.   _, err = conn.Write([]byte("hello world")) 
  25.   if err != nil { 
  26.     log.Printf("%d, send error: %v", i, err) 
  27.     return 
  28.   } 
  29.   select { 
  30.   case <-ctx.Done(): 
  31.     log.Printf("%d, dail close", i) 
  32.   } 
  33.  
  34. func main() { 
  35.   ctx, cancel := context.WithCancel(context.Background()) 
  36.   for i := 0; i < 10; i++ { 
  37.     wg.Add(1) 
  38.     go establishConn(ctx, i) 
  39.   } 
  40.  
  41.   go func() { 
  42.     sc := make(chan os.Signal, 1) 
  43.     signal.Notify(sc, syscall.SIGINT) 
  44.     select { 
  45.     case <-sc: 
  46.       cancel() 
  47.     } 
  48.   }() 
  49.  
  50.   wg.Wait() 
  51.   log.Printf("client exit"

為了方便實驗,將 somaxconn 全連接隊列最大長度更新為 5:

1、更新 /etc/sysctl.conf 文件,將 net.core.somaxconn 更新為 5

2、執(zhí)行 sysctl -p 使配置生效

  1. $ sudo sysctl -p 
  2. net.core.somaxconn = 5 

實驗結果

客戶端日志輸出

  1. 2021/10/11 17:24:48 8, dial success 
  2. 2021/10/11 17:24:48 3, dial success 
  3. 2021/10/11 17:24:48 4, dial success 
  4. 2021/10/11 17:24:48 6, dial success 
  5. 2021/10/11 17:24:48 5, dial success 
  6. 2021/10/11 17:24:48 2, dial success 
  7. 2021/10/11 17:24:48 1, dial success 
  8. 2021/10/11 17:24:48 0, dial success 
  9. 2021/10/11 17:24:48 7, dial success 
  10. 2021/10/11 17:24:53 9, dial error: dial tcp 33.9.192.157:8888: i/o timeout 

客戶端 socket 情況

  1. tcp        0      0 33.9.192.155:40372      33.9.192.157:8888       ESTABLISHED 
  2. tcp        0      0 33.9.192.155:40376      33.9.192.157:8888       ESTABLISHED 
  3. tcp        0      0 33.9.192.155:40370      33.9.192.157:8888       ESTABLISHED 
  4. tcp        0      0 33.9.192.155:40366      33.9.192.157:8888       ESTABLISHED 
  5. tcp        0      0 33.9.192.155:40374      33.9.192.157:8888       ESTABLISHED 
  6. tcp        0      0 33.9.192.155:40368      33.9.192.157:8888       ESTABLISHED 

服務端 socket 情況

  1. tcp6      11      0 33.9.192.157:8888       33.9.192.155:40376      ESTABLISHED 
  2. tcp6      11      0 33.9.192.157:8888       33.9.192.155:40370      ESTABLISHED 
  3. tcp6      11      0 33.9.192.157:8888       33.9.192.155:40368      ESTABLISHED 
  4. tcp6      11      0 33.9.192.157:8888       33.9.192.155:40372      ESTABLISHED 
  5. tcp6      11      0 33.9.192.157:8888       33.9.192.155:40374      ESTABLISHED 
  6. tcp6      11      0 33.9.192.157:8888       33.9.192.155:40366      ESTABLISHED 
  7.  
  8. tcp    LISTEN     6      5      [::]:8888               [::]:*                   users:(("main",pid=84244,fd=3)) 

抓包結果

對客戶端、服務端抓包后,發(fā)現出現了三種情況,分別是:

  • client 成功與 server 端建立 tcp socket 連接,發(fā)送數據成功
  • client 認為成功與 server 端建立 tcp socket 連接,發(fā)送數據失敗,一直在 RETRY;server 端認為 tcp 連接未建立,一直在發(fā)送 SYN+ACK
  • client 向 server 發(fā)送 SYN 未得到響應,一直在 RETRY

全連接隊列實驗結果分析

上述實驗結果出現了三種情況,我們分別對抓包內容進行分析

情況一:Client 成功與 Server 端建立 tcp socket 鏈接,發(fā)送數據成功

上圖可以看到如下請求:

  • Client 端向 Server 端發(fā)送 SYN 發(fā)起握手
  • Server 端收到 Client 端 SYN 后,向 Client 端回復 SYN+ACK,socket 連接存儲到半連接隊列(SYN Queue)
  • Client 端收到 Server 端 SYN+ACK 后,向 Server 端回復 ACK,Client 端進入 ESTABLISHED 狀態(tài)
  • Server 端收到 Client 端 ACK 后,進入 ESTABLISHED 狀態(tài),socket 連接存儲到全連接隊列(Accept Queue)
  • Client 端向 Server 端發(fā)送數據 [PSH, ACK],Server 端確認接收到數據 [ACK]

這種情況就是正常的請求,即全連接隊列、半連接隊列未滿,client 成功與 server 建立了 tcp 鏈接,并成功發(fā)送數據。

情況二:Client 認為成功與 Server 端建立 tcp socket 連接,后續(xù)發(fā)送數據失敗,持續(xù) RETRY;Server 端認為 TCP 連接未建立,一直在發(fā)送SYN+ACK

上圖可以看到如下請求:

  • Client 端向 Server 端發(fā)送 SYN 發(fā)起握手
  • Server 端收到 Client 端 SYN 后,向 Client 端回復 SYN+ACK,socket 連接存儲到半連接隊列(SYN Queue)
  • Client 端收到 Server 端 SYN+ACK 后,向 Server 端回復 ACK,Client 端進入 ESTABLISHED狀態(tài)(重要:此時僅僅是 Client 端認為 tcp 連接建立成功)
  • 由于 Client 端認為 TCP 連接已經建立完成,所以向 Server 端發(fā)送數據 [PSH,ACK],但是一直未收到 Server 端的確認 ACK,所以一直在 RETRY
  • Server 端一直在 RETRY 發(fā)送 SYN+ACK

為什么會出現上述情況?Server 端為什么一直在 RETRY 發(fā)送 SYN+ACK?Server 端不是已經收到了 Client 端的 ACK 確認了嗎?

上述情況是由于 Server 端 socket 連接進入了半連接隊列,在收到 Client 端 ACK 后,本應將 socket 連接存儲到全連接隊列,但是全連接隊列已滿,所以 Server 端 DROP 了該 ACK 請求。

之所以 Server 端一直在 RETRY 發(fā)送 SYN+ACK,是因為 DROP 了 client 端的 ACK 請求,所以 socket 連接仍舊在半連接隊列中,等待 Client 端回復 ACK。

tcp_abort_on_overflow 參數控制

全連接隊列滿DROP 請求是默認行為,可以通過設置 /proc/sys/net/ipv4/tcp_abort_on_overflow 使 Server 端在全連接隊列滿時,向 Client 端發(fā)送 RST 報文。

tcp_abort_on_overflow 有兩種可選值:

  • 0:如果全連接隊列滿了,Server 端 DROP Client 端回復的 ACK
  • 1:如果全連接隊列滿了,Server 端向 Client 端發(fā)送 RST 報文,終止 TCP socket 鏈接 (TODO:后續(xù)有時間補充下該實驗)

為什么實驗結果中當前全連接隊列大小 > 全連接隊列最大長度配置?

上述結果中可以看到 Listen 狀態(tài)的 socket 鏈接:

  • Recv-Q 當前全連接隊列的大小是 6
  • Send-Q 全連接隊列最大長度是 5
  1. State      Recv-Q Send-Q    Local Address:Port         Peer Address:Port 
  2. LISTEN     6      5         [::]:8888                  [::]:* 

為什么全連接隊列大小 > 全連接隊列最大長度配置呢?

經過多次實驗發(fā)現,能夠進入全連接隊列的 Socket 最大數量始終比配置的全連接隊列最大長度 + 1。

結合其他文章以及內核代碼,發(fā)現內核在判斷全連接隊列是否滿的情況下,使用的是 > 而非 >= (具體是為什么沒有找到相關資源 : ) )。

相關內核代碼:

  1. /* Note: If you think the test should be: 
  2.  *  return READ_ONCE(sk->sk_ack_backlog) >= READ_ONCE(sk->sk_max_ack_backlog); 
  3.  * Then please take a look at commit 64a146513f8f ("[NET]: Revert incorrect accept queue backlog changes."
  4.  */ 
  5. static inline bool sk_acceptq_is_full(const struct sock *sk) 
  6.   return READ_ONCE(sk->sk_ack_backlog) > READ_ONCE(sk->sk_max_ack_backlog); 

情況三:Client 向 Server 發(fā)送 SYN 未得到相應,一直在 RETRY

圖片上圖可以看到如下請求:

  • Client 端向 Server 端發(fā)送 SYN 發(fā)起握手,未得到 Server 回應,一直在 RETRY

(這種情況涉及到半連接隊列,這里先給上述情況發(fā)生的原因結論,具體內容將在下文半連接隊列中展開。)

發(fā)生上述情況的原因由以下兩方面導致:

1、開啟了 /proc/sys/net/ipv4/tcp_syncookies 功能

2、全連接隊列滿了

實戰(zhàn) —— 半連接隊列

半連接隊列最大長度控制

翻閱了很多博文,查找關于半連接隊列最大長度控制的相關內容,大多含糊其辭或不準確,經過不懈努力,最終找到了比較確切的內容(相關博文鏈接在附錄中)。

很多博文中說半連接隊列最大長度由 /proc/sys/net/ipv4/tcp_max_syn_backlog 參數指定,實際上只有在 linux 內核版本小于 2.6.20 時,半連接隊列才等于 backlog 的大小。

這塊的源碼比較復雜,這里給一下大體的計算方式,詳細的內容可以參考附錄中的相關博文。半連接隊列長度的計算過程:

  1. backlog = min(somaxconn, backlog) 
  2. nr_table_entries = backlog 
  3. nr_table_entries = min(backlog, sysctl_max_syn_backlog) 
  4. nr_table_entries = max(nr_table_entries, 8) 
  5. // roundup_pow_of_two: 將參數向上取整到最小的 2^n,注意這里存在一個 +1 
  6. nr_table_entries = roundup_pow_of_two(nr_table_entries + 1) 
  7. max_qlen_log = max(3, log2(nr_table_entries)) 
  8. max_queue_length = 2^max_qlen_log 

可以看到,半連接隊列的長度由三個參數指定:

  • 調用 listen 時,傳入的 backlog
  • /proc/sys/net/core/somaxconn 默認值為 128
  • /proc/sys/net/ipv4/tcp_max_syn_backlog 默認值為 1024

我們假設 listen 傳入的 backlog = 128 (Golang 中調用 listen 時傳遞的 backlog 參數使用的是 /proc/sys/net/core/somaxconn),其他配置采用默認值,來計算下半連接隊列的最大長度

  1. backlog = min(somaxconn, backlog) = min(128, 128) = 128 
  2. nr_table_entries = backlog = 128 
  3. nr_table_entries = min(backlog, sysctl_max_syn_backlog) = min(128, 1024) = 128 
  4. nr_table_entries = max(nr_table_entries, 8) = max(128, 8) = 128 
  5. nr_table_entries = roundup_pow_of_two(nr_table_entries + 1) = 256 
  6. max_qlen_log = max(3, log2(nr_table_entries)) = max(3, 8) = 8 
  7. max_queue_length = 2^max_qlen_log = 2^8 = 256 

可以得到半隊列大小是 256。

判斷是否 Drop SYN 請求

當 Client 端向 Server 端發(fā)送 SYN 報文后,Server 端會將該 socket 連接存儲到半連接隊列(SYN Queue),如果 Server 端判斷半連接隊列滿了則會將連接 Drop 丟棄。

那么 Server 端是如何判斷半連接隊列是否滿的呢?除了上面一小節(jié)提到的半連接隊列最大長度控制外,還和 /proc/sys/net/ipv4/tcp_syncookies 參數有關。(tcp_syncookies 的作用是為了防止 SYN Flood 攻擊的,下文會給出相關鏈接介紹)

流程圖

判斷是否 Drop SYN 請求的流程圖:

上圖是整理了多份資料后,整理出來的判斷是否 Drop SYN 請求的流程圖。

注意:第一個判斷條件 「當前半連接隊列是否已超過半連接隊列最大長度」在不同內核版本中的判斷不一樣,Linux4.19.91 內核判斷的是當前半連接隊列長度是否 >= 全連接隊列最大長度。

相關內核代碼:

  1. static inline int inet_csk_reqsk_queue_is_full(const struct sock *sk) 
  2.   return inet_csk_reqsk_queue_len(sk) >= sk->sk_max_ack_backlog; 

我們假設如下參數,來計算下當 Client 端只發(fā)送 SYN 包,理論上 Server 端何時會 Drop SYN 請求:

  • 調用 listen 時傳入的 backlog = 1024
  • /proc/sys/net/core/somaxconn 值為 1024
  • /proc/sys/net/ipv4/tcp_max_syn_backlog 值為 128

當 /proc/sys/net/ipv4/tcp_syncookies 值為 0 時

  • 計算出的半連接隊列最大長度為 256
  • 當半連接隊列長度增長至 96 后,再新增 SYN 請求,就會觸發(fā) Drop SYN 請求

當 /proc/sys/net/ipv4/tcp_syncookies 值為 1 時

1.計算出的半連接隊列最大長度為 256

2.由于開啟了 tcp_syncookies

  • 當全連接隊列未滿時,永遠不會 Drop 請求 (注意:經實驗發(fā)現這個理論是錯誤的,實驗發(fā)現只要半連接隊列的大小 > 全連接隊列最大長度就會觸發(fā) Drop SYN 請求)
  • 當全連接隊列滿了后,即全連接隊列大小到 1024 后,就會觸發(fā) Drop SYN 請求

PS:/proc/sys/net/ipv4/tcp_syncookies 的取值還可以為 2,筆者沒有詳細實驗。

回顧全連接隊列實驗結果

在上文全連接隊列實驗中,有一類實驗結果是:client 向 Server 發(fā)送 SYN 未得到響應,一直在 RETRY。

發(fā)生上述情況的原因由以下兩方面導致:

1. 開啟了 /proc/sys/net/ipv4/tcp_syncookies 功能

2. 全連接隊列滿了

半連接隊列溢出實驗

上文我們已經知道如何計算理論上半連接隊列何時會溢出,下面我們來具體實驗下

(Golang 調用 listen 時傳入的 backlog 值為 somaxconn)

實驗一:syncookies=0,somaxconn=1024,tcp_max_syn_backlog=128

理論上:

  • 計算出的半連接隊列最大長度為 256
  • 當半連接隊列長度增長至 96 后,后續(xù) SYN 請求就會觸發(fā) Drop

將相關參數的配置更新

  1. $ sudo sysctl -p 
  2. net.core.somaxconn = 1024 
  3. net.ipv4.tcp_max_syn_backlog = 128 
  4. net.ipv4.tcp_syncookies = 0 

啟動服務端 Server 監(jiān)聽 8888 端口(代碼參考全連接隊列實驗物料)

客戶端 Client 發(fā)起 SYN Flood 攻擊:

  1. $ sudo hping3 -S 33.9.192.157 -p 8888 --flood 
  2. HPING 33.9.192.157 (eth0 33.9.192.157): S set, 40 headers + 0 data bytes 
  3. hping in flood mode, no replies will be shown 

查看服務端 Server 8888端口處于 SYN_RECV 狀態(tài)的 socket 最大個數:

  1. [zechen.hg@function-compute033009192157.na63 /home/zechen.hg] 
  2. $ sudo netstat -nat | grep :8888 | grep SYN_RECV  | wc -l 
  3. 96 
  4.  
  5. [zechen.hg@function-compute033009192157.na63 /home/zechen.hg] 
  6. $ sudo netstat -nat | grep :8888 | grep SYN_RECV  | wc -l 
  7. 96 

實驗結果符合預期,當半連接隊列長度增長至 96 后,后續(xù) SYN 請求就會觸發(fā) Drop。

實驗二:syncookies = 0,somaxconn=128,tcp_max_syn_backlog=512

理論上:

  • 計算出的半連接隊列最大長度為 256,由于筆者實驗機器上的內核版本是 4.19.91,所以當半連接隊列長度 >= 全連接隊列最大長度時,內核就認為半連接隊列溢出了
  • 所以當半連接隊列長度增長至 128 后,后續(xù) SYN 請求就會觸發(fā) DROP

將相關參數的配置更新

  1. $ sudo sysctl -p 
  2. net.core.somaxconn = 128 
  3. net.ipv4.tcp_max_syn_backlog = 512 
  4. net.ipv4.tcp_syncookies = 0 

啟動服務端 Server 監(jiān)聽 8888 端口(代碼參考全連接隊列實驗物料)

客戶端 Client 發(fā)起 SYN Flood 攻擊:

  1. $ sudo hping3 -S 33.9.192.157 -p 8888 --flood 
  2. HPING 33.9.192.157 (eth0 33.9.192.157): S set, 40 headers + 0 data bytes 
  3. hping in flood mode, no replies will be shown 

查看服務端 Server 8888端口處于 SYN_RECV 狀態(tài)的 socket 最大個數:

  1. [zechen.hg@function-compute033009192157.na63 /home/zechen.hg] 
  2. $ sudo netstat -nat | grep :8888 | grep SYN_RECV  | wc -l 
  3. 128 
  4.  
  5. [zechen.hg@function-compute033009192157.na63 /home/zechen.hg] 
  6. $ sudo netstat -nat | grep :8888 | grep SYN_RECV  | wc -l 
  7. 128 

實驗結果符合預期,當半連接隊列長度增長至 128 后,后續(xù) SYN 請求就會觸發(fā) Drop

實驗三:syncookies = 1,somaxconn=128,tcp_max_syn_backlog=512

理論上:

  • 當全連接隊列未滿,syncookies = 1,理論上 SYN 請求永遠不會被 Drop

將相關參數的配置更新

  1. $ sudo sysctl -p 
  2. net.core.somaxconn = 128 
  3. net.ipv4.tcp_max_syn_backlog = 512 
  4. net.ipv4.tcp_syncookies = 1 

啟動服務端 Server 監(jiān)聽 8888 端口(代碼參考全連接隊列實驗物料)

客戶端 Client 發(fā)起 SYN Flood 攻擊:

  1. $ sudo hping3 -S 33.9.192.157 -p 8888 --flood 
  2. HPING 33.9.192.157 (eth0 33.9.192.157): S set, 40 headers + 0 data bytes 
  3. hping in flood mode, no replies will be shown 

查看服務端 Server 8888端口處于 SYN_RECV 狀態(tài)的 socket 最大個數:

  1. [zechen.hg@function-compute033009192157.na63 /home/zechen.hg] 
  2. $ sudo netstat -nat | grep :8888 | grep SYN_RECV  | wc -l 
  3. 128 
  4.  
  5. [zechen.hg@function-compute033009192157.na63 /home/zechen.hg] 
  6. $ sudo netstat -nat | grep :8888 | grep SYN_RECV  | wc -l 
  7. 128 

實驗發(fā)現即使syncookies=1,當半連接隊列長度 > 全連接隊列最大長度時,就會觸發(fā) DROP SYN 請求!!!(TODO:有時間閱讀下相關內核源碼,再分析下)

繼續(xù)做實驗,將 somaxconn 更新為 5

  1. $ sudo sysctl -p 
  2. net.core.somaxconn = 5 
  3. net.ipv4.tcp_max_syn_backlog = 512 
  4. net.ipv4.tcp_syncookies = 1 

發(fā)起 SYN Flood 攻擊后,查看服務端 Server 8888端口處于 SYN_RECV 狀態(tài)的 socket 最大個數:

  1. [zechen.hg@function-compute033009192157.na63 /home/zechen.hg] 
  2. $ sudo netstat -nat | grep :8888 | grep SYN_RECV  | wc -l 
  3.  
  4. [zechen.hg@function-compute033009192157.na63 /home/zechen.hg] 
  5. $ sudo netstat -nat | grep :8888 | grep SYN_RECV  | wc -l 

確實 即使 syncookies=1,當半連接隊列長度 > 全連接最大長度時,就會觸發(fā) DROP SYN 請求。

實驗四:syncookies = 1,somaxconn=256,tcp_max_syn_backlog=128

理論上:

  • 當半連接隊列大小到 256 后,后觸發(fā) DROP SYN 請求

將相關參數的配置更新

  1. $ sudo sysctl -p 
  2. net.core.somaxconn = 256 
  3. net.ipv4.tcp_max_syn_backlog = 128 
  4. net.ipv4.tcp_syncookies = 1 

啟動服務端 Server 監(jiān)聽 8888 端口(代碼參考全連接隊列實驗物料)。

客戶端 Client 發(fā)起 SYN Flood 攻擊:

  1. $ sudo hping3 -S 33.9.192.157 -p 8888 --flood 
  2. HPING 33.9.192.157 (eth0 33.9.192.157): S set, 40 headers + 0 data bytes 
  3. hping in flood mode, no replies will be shown 

查看服務端 Server 8888端口處于 SYN_RECV 狀態(tài)的 socket 最大個數:

  1. [zechen.hg@function-compute033009192157.na63 /home/zechen.hg] 
  2. $ sudo netstat -nat | grep :8888 | grep SYN_RECV  | wc -l 
  3. 256 
  4.  
  5. [zechen.hg@function-compute033009192157.na63 /home/zechen.hg] 
  6. $ sudo netstat -nat | grep :8888 | grep SYN_RECV  | wc -l 
  7. 256 

實驗結果符合預期,當半連接隊列長度增長至 256 后,后續(xù) SYN 請求就會觸發(fā) Drop。

回顧線上問題

再回顧值班時遇到的 Connection timeout 問題,當時相關系統參數配置為:

  • net.core.somaxconn = 128
  • net.ipv4.tcp_max_syn_backlog = 512
  • net.ipv4.tcp_syncookies = 1
  • net.ipv4.tcp_abort_on_overflow = 0

所以出現 Connection timeout 有兩種可能情況:

1、半連接隊列未滿,全連接隊列滿,Client 端向 Server 端發(fā)起 SYN 被 DROP (參考全連接隊列實驗結果情況三分析、半連接隊列溢出實驗情況三)

2、全連接隊列未滿,半連接隊列大小超過全鏈接隊列最大長度(參考半連接隊列溢出實驗情況三、半連接隊列溢出實驗情況四)

問題的最快修復方式是將 net.core.somaxconn 調大,以及 net.ipv4.tcp_abort_on_overflow 設置為 1,net.ipv4.tcp_abort_on_overflow 設置為 1 是為了讓 client fail fast。

總結

半連接隊列溢出、全連接隊列溢出這類問題很容易被忽略,同時這類問題又很致命。當半連接隊列、全連接隊列溢出時 Server 端,從監(jiān)控上來看系統 cpu 水位、內存水位、網絡連接數等一切正常,然而卻會持續(xù)影響 Client 端業(yè)務請求。對于高負載上游使用短連接的情況,出現這類問題的可能性更大。

本文詳細梳理了 TCP 半連接隊列、全連接隊列的理論知識,同時結合 Linux 相關內核代碼以及詳細的動手實驗,講解了 TCP 半連接隊列、全連接隊列的相關原理、溢出判斷、問題分析等內容,希望大家在閱讀后可以對 TCP 半連接隊列、全連接隊列有更充分的認識。

PS:可以去線上檢查下服務器的相關參數喲~

附錄

這里羅列下相關參考博文資料:

Linux 源碼

  • https://github.com/torvalds/linux

Linux 詭異的半連接隊列長度

  • https://www.cnblogs.com/zengkefu/p/5606696.html

TCP 半連接隊列和全連接隊列滿了會發(fā)生什么

  • https://www.cnblogs.com/xiaolincoding/p/12995358.html

一次 HTTP connect-timeout 排查

  • https://www.jianshu.com/p/3b9c4216b822

Connection Reset 排查

  • https://cjting.me/2019/08/28/tcp-queue/

深入淺出 TCP 中的 SYN-Cookies

  • https://segmentfault.com/a/1190000019292140

 

責任編輯:武曉燕 來源: 云巔論劍
相關推薦

2019-09-16 09:29:01

TCP全連接隊列半連接隊列

2015-04-23 18:46:38

TCPTCP協議

2023-04-06 07:53:56

Redis連接問題K8s

2018-07-05 14:25:01

TCP握手原理

2020-10-14 14:31:37

LinuxTCP連接

2024-01-19 19:22:45

TCPTIME_WAIT

2021-03-17 09:51:31

網絡編程TCP網絡協議

2020-01-18 14:11:13

數據庫線程技術

2019-11-17 22:11:11

TCPSYN隊列Accept隊列

2021-11-23 21:21:07

線上排查服務

2020-11-16 07:19:17

線上函數性能

2021-12-12 18:12:13

Hbase線上問題

2020-02-17 10:10:43

TCP三次握手四次揮手

2010-07-07 10:45:22

TCP UDP協議

2020-08-24 07:34:39

網絡超時請求

2021-10-14 20:33:16

TCP連接關閉

2023-11-29 12:12:24

Oceanbase數據庫

2012-07-02 13:26:28

電線連接

2014-08-22 09:10:46

2020-10-21 08:17:11

隊列數據
點贊
收藏

51CTO技術棧公眾號

日一区二区三区| 少妇高潮一区二区三区99小说| 成人视屏在线观看| 国产欧美1区2区3区| 成人福利免费观看| 国产精品成人网站| 青青久久精品| 久久综合狠狠综合久久激情 | 国产成人免费观看| 变态另类ts人妖一区二区| a'aaa级片在线观看| 久久久久久97三级| 96精品久久久久中文字幕| 亚洲激情图片网| 国产96在线亚洲| 欧美日韩美女一区二区| a级黄色一级片| 日韩欧美小视频| 99精品国产视频| 成人精品一区二区三区电影黑人| 青青操免费在线视频| 77成人影视| 欧美群妇大交群中文字幕| 久久久亚洲精品无码| 国产盗摄在线观看| 久久精品欧美日韩| 九九九九精品| 国产chinasex对白videos麻豆| 久久亚洲一区| 孩xxxx性bbbb欧美| 欧美成人aaa片一区国产精品| 国产亚洲欧美日韩在线观看一区二区| 欧美一区二区免费视频| 噼里啪啦国语在线观看免费版高清版| 日本性爱视频在线观看| 北条麻妃国产九九精品视频| 成人福利在线视频| 9i看片成人免费看片| 91精品一区二区三区综合在线爱| 日韩的一区二区| 99视频在线观看视频| 中文字幕21页在线看| 久久99国产精品麻豆| 欧美一区二区视频观看视频| 国产日韩亚洲欧美在线| av中文天堂在线| 丁香婷婷综合五月| 成人黄色生活片| 美女又爽又黄免费视频| 国内久久视频| 久久精品视频网站| 一二三四国产精品| 制服丝袜日韩| 精品美女被调教视频大全网站| 久久久久国产精品熟女影院| 精品丝袜在线| 亚洲精品国产精华液| 亚洲视频在线观看日本a| 精品人妻一区二区色欲产成人| 综合精品久久| 最新国产成人av网站网址麻豆| 亚洲av无码成人精品国产| heyzo欧美激情| 欧美日韩黄色大片| 免费在线黄网站| 亚洲人在线观看视频| 风流少妇一区二区| 99国产超薄肉色丝袜交足的后果| 精品人妻一区二区三区免费看 | 久久伊人久久| 色爱区综合激月婷婷| 国产精品sss| 国产青青草视频| 国产一区中文字幕| 成人性生交xxxxx网站| 亚洲手机在线观看| 精品一区二区三区免费| 国产九九精品视频| 在线免费日韩av| 亚洲蜜桃视频| 久久综合五月天| 五月天激情丁香| 永久亚洲成a人片777777| 久久久精品免费| 综合五月激情网| 欧美a级一区| 久久乐国产精品| 五月婷婷中文字幕| 天堂蜜桃91精品| 国产日韩欧美在线观看| 免看一级a毛片一片成人不卡| 911精品美国片911久久久| 久久视频在线看| 久久久精品视频在线 | www黄色日本| 欧美xoxoxo| 欧美日韩一区中文字幕| 中文字幕线观看| 视频一区在线| 日韩av有码在线| 久操视频免费看| 99久久久久国产精品| 欧美成人午夜免费视在线看片| 久久久久无码国产精品| 国产视频久久| 国产色综合天天综合网 | 亚洲色图第四色| 亚洲精品在线观看91| 欧美贵妇videos办公室| 九九热在线免费观看| 蓝色福利精品导航| 国产视频一区二区不卡| 成人亚洲性情网站www在线观看| 国产精品蜜臀在线观看| 99视频网站| 日韩a在线观看| 国产sm精品调教视频网站| 国内精品久久久久久久果冻传媒| 国产51人人成人人人人爽色哟哟 | 成人在线网址| 亚洲成精国产精品女| 亚洲中文字幕久久精品无码喷水| 日韩五码电影| 日韩高清人体午夜| 青青草自拍偷拍| 亚洲国产综合在线看不卡| 国产精品久久婷婷六月丁香| 国产成人啪精品午夜在线观看| 香蕉久久网站| 欧美亚洲视频在线看网址| 91在线精品入口| 麻豆成人久久精品二区三区小说| 2014亚洲精品| 尤物网址在线观看| 亚洲成a人v欧美综合天堂下载| 亚洲视频第二页| 红杏一区二区三区| 欧美精品一区二区三区很污很色的| 色一情一交一乱一区二区三区| 欧美.www| 91欧美视频网站| jizzjizz在线观看| 精品女厕一区二区三区| 国产一精品一aⅴ一免费| 日韩欧美中字| 国产精品福利网站| 婷婷丁香花五月天| 亚洲一区二区三区三| av中文字幕网址| 区一区二视频| 81精品国产乱码久久久久久| www.久久精品.com| 亚洲在线视频一区| 日本人dh亚洲人ⅹxx| 93在线视频精品免费观看| 浅井舞香一区二区| 欧美自拍偷拍一区二区| 一区二区三区在线不卡| 亚洲午夜激情影院| 日韩三级在线| 国产精品久久久久91| 你懂的免费在线观看| 欧美日韩亚洲一区二区| 欧类av怡春院| 午夜亚洲性色福利视频| 久久国产精品 国产精品| 国产777精品精品热热热一区二区| 日韩欧美一区二区久久婷婷| 欧美国产日韩在线观看成人| 国产一区二区伦理片| 一区二区不卡在线视频 午夜欧美不卡' | 依依综合在线| 亚洲欧美国产精品| 伊人手机在线视频| 国产日产欧产精品推荐色| www.欧美日本| 人人香蕉久久| 国产精品大陆在线观看| 成人免费一区二区三区视频网站| 色综合久久久久综合体桃花网| 麻豆精品免费视频| 美女视频黄 久久| 在线观看视频黄色| 欧洲一区在线| 欧美精品videosex极品1| 蜜臀久久精品久久久久| 天天av天天翘天天综合网色鬼国产 | 欧美精品一级片| av在线播放不卡| 国产精品亚洲αv天堂无码| 成人a'v在线播放| 成人黄色生活片| 久久大胆人体| 在线观看日韩精品| 91大神福利视频| 国产精品一品二品| 久久综合九色综合88i| 亚洲日本三级| 国产日本欧美在线观看| 日本不卡影院| 亚洲人在线视频| 亚洲一区二区三区高清视频| 亚洲高清一区二区三区| 午夜精产品一区二区在线观看的| 九九**精品视频免费播放| 成人国产一区二区三区| 亚洲小说图片视频| 成人精品视频在线| 成人美女黄网站| 超碰91人人草人人干| 西西人体44www大胆无码| 欧美日韩免费一区二区三区 | 国产精品三级网站| 欧美人与性动交α欧美精品图片| 亚洲欧美综合区自拍另类| 国产精品无码专区av免费播放| 亚洲成人综合在线| 精品视频第一页| 97久久超碰精品国产| caoporm在线视频| 老司机一区二区三区| 99热一区二区三区| 波多野结衣的一区二区三区| 精品不卡在线| 欧美日韩中出| 日本高清视频一区| 少女频道在线观看高清| 国产一区二区三区丝袜| 黄色一级大片在线免费看国产| 欧美性欧美巨大黑白大战| 国产无精乱码一区二区三区| 国产精品初高中害羞小美女文| 97xxxxx| 欧美日韩精选| 亚洲一卡二卡区| 亚洲婷婷影院| 精品麻豆av| 亚洲精品三区| 国产精品久久一区主播| 国产社区精品视频| 欧美成人亚洲成人| 国产在线一二三| 国产视频一区在线| 四虎精品一区二区三区| 日韩女优av电影在线观看| 在线观看中文字幕码| 日韩欧美在线视频| 日韩精品一区二区在线播放| 一区二区三区中文字幕精品精品| 精品少妇一区二区三区密爱| 久久久精品天堂| 亚洲专区区免费| wwwwxxxxx欧美| 中文字幕在线播放视频| 成人精品亚洲人成在线| 三上悠亚 电影| 国产一区二区在线看| 成年网站免费在线观看| 蜜臂av日日欢夜夜爽一区| 国产精品国产亚洲精品看不卡| 亚洲国产裸拍裸体视频在线观看乱了中文 | 久久久久这里只有精品| 色yeye免费人成网站在线观看| 欧美成人午夜视频| 女同一区二区免费aⅴ| 欧美激情三级免费| 波多野结衣在线观看| 久久久久久久久久久免费精品| 性爱视频在线播放| 欧美精品免费在线| 免费av网站在线看| 欧美国产日韩一区二区三区| 青草在线视频| 久久人人爽人人| 久久青草伊人| 2025国产精品视频| 日韩精品影片| 国产噜噜噜噜噜久久久久久久久 | 国产精品视频公开费视频| 欧美日韩尤物久久| 成人免费视频在线观看超级碰| 亚洲高清影院| 国产精品大全| 日韩美脚连裤袜丝袜在线| 蜜桃麻豆91| 成人婷婷网色偷偷亚洲男人的天堂| 亚洲成人自拍| 66国产精品| 国产 日韩 欧美在线| 午夜一级在线看亚洲| 国产九九在线观看| 国产成人一区在线| 亚洲美女精品视频| 99久久久久久| 国产人与禽zoz0性伦| 一区二区三区日韩欧美| 欧美特黄aaaaaa| 欧美视频在线一区二区三区| 国产毛片毛片毛片毛片| 精品剧情v国产在线观看在线| 午夜视频福利在线观看| 中文国产亚洲喷潮| 香蕉成人app免费看片| 欧美一区二区.| 91精品福利观看| 蜜桃传媒一区二区| 亚洲男女av一区二区| 免费看国产曰批40分钟| 精品在线免费视频| 久久人妻少妇嫩草av无码专区| 欧美极品另类videosde| 青青草原在线免费观看| 色婷婷综合激情| 国内老熟妇对白xxxxhd| 亚洲人a成www在线影院| 亚洲卡一卡二| 欧美一级片久久久久久久| 国产精品麻豆| 日本在线播放不卡| 亚洲网址在线| 中文字幕视频三区| 久久久美女毛片| 久久久综合久久久| 欧美日韩国产美| 国产香蕉在线| 国内精品久久久久久中文字幕| 欧美xnxx| 九九九热999| 午夜国产精品视频免费体验区| 欧美日韩大尺度| 97精品国产露脸对白| 国产免费无码一区二区视频| 欧洲生活片亚洲生活在线观看| 熟妇人妻系列aⅴ无码专区友真希| 久久国产精品久久久| avav成人| 欧美日韩亚洲在线| 综合在线视频| 九九九在线观看视频| 91麻豆精东视频| 久久视频免费在线观看| 日韩一区二区三区在线观看| 成人在线免费观看| 日韩av电影免费观看高清| 欧美日韩看看2015永久免费 | 狠狠躁天天躁日日躁欧美| 亚洲福利在线观看视频| 欧美大尺度激情区在线播放 | 欧美亚洲图片小说| 男操女在线观看| 欧美一区视频在线| 欧美电影免费网站| 欧美变态另类刺激| 成人国产精品免费网站| 国产精品成人免费一区二区视频| 精品美女一区二区三区| 久久一卡二卡| 国产欧美日韩视频一区二区三区| 欧美日韩ab| 风韵丰满熟妇啪啪区老熟熟女| 亚洲精品久久久蜜桃| 最新国产中文字幕| 亚洲性猛交xxxxwww| 国产一区一一区高清不卡| 色综合久久久久久久久五月| 日本午夜精品一区二区三区电影 | 国产亚洲精品精华液| 国产字幕在线观看| 亚洲欧洲在线观看| 亚洲伦乱视频| 亚洲乱码一区二区三区| 国产麻豆成人传媒免费观看| 午夜国产福利一区二区| 亚洲精品国产suv| 高清不卡亚洲| 欧美日韩国产不卡在线看| 免费观看成人av| 免费国产羞羞网站美图| 日韩欧美精品在线视频| 国模雨婷捆绑高清在线| 久久国产精品-国产精品| 久久久久欧美精品| 国产精品麻豆免费版现看视频| 51精品久久久久久久蜜臀| 国产最新在线| 成人在线免费网站| 亚洲一区欧美二区| 波多野结衣家庭教师在线观看| 91精品国产乱码久久蜜臀| 国产网红在线观看| 欧美精品亚洲精品| 久久成人久久鬼色| 久久久国产成人| 亚洲国产日韩精品在线| 欧美韩国亚洲| 日本黄xxxxxxxxx100| 26uuu久久天堂性欧美| 一级黄色片免费看| 欧美激情影音先锋| 成人3d动漫在线观看|