socket常見問題


socket編程中主動關閉VS被動關閉

tcp中server,client都可能是主動關閉方或者被動關閉方,現闡述下兩者之間的關系:

 

客戶端(client)                                                服務器(server)

close()                          Fin x  ->                   讀通道關閉(close_wait)

寫通道關閉                     <- Ack x+1    

 

讀通道關閉(time_wait)    <- Fin y                   close()

                                     ack y+1 ->              寫通道關閉      

2x msl                                                          closed

closed

 

 

1、 客戶端是調用函數close(),這時,客戶端會發送一個FIN給服務器。

2、 服務器收到FIN,關閉套接字讀通道,並將自己狀態設置為CLOSE_WAIT(表示被動關閉),

       並返回一個ACK給客戶端。

3、 客戶端收到ACK,關閉套接字寫通道

 

      接下來,服務器會調用close():

 

1、 服務器close(),發送一個FIN到客戶端。

2、 客戶端收到FIN,關閉讀通道,並將自己狀態設置成TIME_WAIT,發送一個ACK給服務器。

3、 服務器收到ACK,關閉寫通道,並將自己狀態設置為CLOSE。

4、 客戶端等待兩個最大數據傳輸時間,然后將自己狀態設置成CLOSED。

 

有了上面的背景知識,對於我們系統線上一個case分析就很簡單了!

首先是主動關閉日志很多,后來是被動關閉的日志

由於server端發現了大量閑置的沒有Io的socket連接,有監聽器在監聽是否存在閑置的socket連接,就釋放並關閉這些連接,time_wait就出現了,這個時候應用方客戶端重啟應用,釋放了資源包括一些客戶端連接,這個時候close_wait出現了,正好是以上日志所反映的

 

同時time_wait狀態的連接是不會釋放內核資源,所以服務端不要輕易close!

 

socket中的read返回0

在socket中服務器與客戶端進行通信,當其中一方調用close(即這一方會發送一個fin)關閉套接字之后,另一方read()會返回一個0。

我之前編寫的一個服務器與客戶端通信(一個服務器只連接一個客戶端):服務器開兩個進程,一個用於接收客戶端發送的數據,另一個進程用於

向客戶端發送數據。客戶端開兩個進程也是一個用於發送數據一個用於接收數據。由於創建了兩個進程,那么套接字的引用計數都為2,只有當客戶

端關閉兩次套接字,在服務器的read()才會返回0

另外,如果在虛擬機上運行,打開兩個shell,一個運行客戶端,一個運行服務器,當關閉運行客戶端的shell,則服務器的read()會返回0

python socket模塊4種異常

  • 與一般I/O和通信問題有關的socket.error;

  • 與查詢地址信息有關的socket.gaierror;

  • 與其他地址錯誤有關的socket.herror;

  • 與在一個socket上調用settimeout()后,處理超時有關的socket.timeout.

 

以 TCP 協議為例,若 socket 使用阻塞模式調用 recv(),返回空串時表示 TCP 連接已正常關閉。

示例代碼如下

# sk = socket(...) # ... while True: data = sk.recv(1024) if not data: break # 連接已經關閉 # ...


python 解決close_wait過多問題

 

最近在公司遇到CLOSE_WAIT過多的問題,現在解決后總結下,先說下CLOSE_WAIT產生的原因:

首先要知道客戶端和服務端的連接是使用套接字通信的,TCP/IP協議建立連接需要三次握手,而關閉client與server的連接需要進行四步,如圖:

建立連接后常用的三個狀態是:ESTABLISHED 表示正在通信,TIME_WAIT 表示主動關閉,CLOSE_WAIT 表示被動關閉。

通過上圖,我們來分析,什么情況下,連接處於CLOSE_WAIT狀態呢?

        就是服務端在被動關閉收到FIN,未發出自己FIN的情況下就處於CLOSE_WAIT狀態了,通常CLOSE_WAIT的持續時間很

短,但是在某些特殊狀態下就可能長時間存在,如果出現大量close_wait的現象,主要原因可能是一方關閉了socket鏈接,但是另一方

忙與讀或者寫,沒有關閉連接。代碼需要判斷socket,一旦讀到0,斷開連接,read返回負,檢查一下errno( errno 是記錄系統的最后

一次錯誤代碼。),如果不是AGAIN,就斷開連接。

 

在linux中查看socket狀態的命令:

    netstat -nt | awk '{wait[$NF]++}END{for(i in wait) print i,wait[i]}'

得到如下結果:

                            

 

然后查看處於CLOSE_WAIT的都是哪些端口:

    netstat -nt | awk '{if($NF=="CLOSE_WAIT"){wait[$5]++}}END{for(i in wait) print i,wait[i]}' 

 

得到如下結果:

                           

第一條命令會把當前所有的狀態進行分類,第二個命令,他會定位到具體出問題的端口,再根據端口找到相應的程序,修改相應的程序即可。

 

        上面還提到一種狀態就是TIME_WAIT ,如果TIME_WAIT過多,也會對系統造成影響。TIME_WAIT 和CLOSE_WAIT過多

都會影響系統的擴展性,影響用戶請求、其他系統對本系統的訪問,這兩種狀態過多產生的影響一致,但不可用同樣的方法去

解決,CLOSE_WAIT的情況需要從程序本身出發,而TIME_WAIT更傾向於修改系統參數。

   TIME_WAIT產生的原因第一張圖中已經說明了,是主動關閉的一方所處的狀態,然后在保持這個狀態2MSL(max segment lifetime)時間之

后,徹底關閉回收資源。為什么要等2MSL的時間呢?這是TCP/IP的設計者規定的,主要出於以下兩個方面的考慮:

  1.防止上一次連接中的包,迷路后重新出現,影響新連接(經過2MSL,上一次連接中所有的重復包都會消失)
  2.可靠的關閉TCP連接。在主動關閉方發送的最后一個 ack(fin) ,有可能丟失,這時被動方會重新發fin, 如果這時主動方處於 CLOSED 狀態 ,就

     會響應 rst 而不是 ack。所以主動方要處於 TIME_WAIT 狀態,而不能是 CLOSED 。另外這么設計TIME_WAIT 會定時的回收資源,並不會占用很

     大資源的,除非短時間內接受大量請求或者受到攻擊。

 

 

解決思路很簡單,就是讓服務器能夠快速回收和重用那些TIME_WAIT的資源。
 
下面我們來看一下/etc/sysctl.conf文件的配置:
#表示開啟重用。允許將TIME_WAIT sockets重新用於新的TCP連接,默認為0,表示關閉
1、net.ipv4.tcp_tw_reuse = 1

#表示開啟TCP連接中TIME_WAIT sockets的快速回收,默認為0,表示關閉
2、net.ipv4.tcp_tw_recycle = 1

#<span style="font-family:FangSong_GB2312;">表示如果套接字由本端要求關閉,這個參數決定了它保持在FIN_WAIT_2狀態的時間(可改為30,一般來說FIN_WAIT_2的連接也極少)</span>
3、net.ipv4.tcp_fin_timeout = 30

#<span style="font-family:SimHei;color:#333333;LINE-HEIGHT: 22px;">控制 TCP/IP 嘗試驗證空閑連接是否完好的頻率</span>
4、net.ipv4.tcp_keepalive_time = 600

#表示SYN隊列的長度,默認為1024,加大隊列長度為8192,可以容納更多等待連接的網絡連接數。
5、net.ipv4.tcp_max_syn_backlog = 8192 

#表示系統同時保持TIME_WAIT的最大數量,如果超過這個數字,TIME_WAIT將立刻被清除並打印警告信息。默認為180000,改為60000。
6、net.ipv4.tcp_max_tw_buckets = 60000

#記錄的那些尚未收到客戶端確認信息的連接請求的最大值。對於有128M內存的系統而言,缺省值是1024,小內存的系統則是128。
7、net.ipv4.tcp_max_syn_backlog = 65536

#每個網絡接口接收數據包的速率比內核處理這些包的速率快時,允許送到隊列的數據包的最大數目。
8、net.core.netdev_max_backlog = 32768

#web應用中listen函數的backlog默認會給我們內核參數的net.core.somaxconn限制到128,而nginx定義的NGX_LISTEN_BACKLOG默認為511,所以有必要調整這個值。
9、net.core.somaxconn = 32768

#定義默認的發送窗口大小;對於更大的 BDP 來說,這個大小也應該更大。
10、net.core.wmem_default = 8388608

#該文件指定了接收套接字緩沖區大小的缺省值(以字節為單位)。
11、net.core.rmem_default = 8388608

#最大socket讀buffer。
12、net.core.rmem_max = 16777216

#最大socket寫buffer。
13、net.core.wmem_max = 16777216

#為了打開對端的連接,內核需要發送一個SYN並附帶一個回應前面一個SYN的ACK。也就是所謂三次握手中的第二次握手。這個設置決定了內核放棄連接之前發送SYN+ACK包的數量。
14、net.ipv4.tcp_synack_retries = 2

#對於一個新建連接,內核要發送多少個 SYN 連接請求才決定放棄。不應該大於255,默認值是5,對應於180秒左右時間。
15、net.ipv4.tcp_syn_retries = 2 

#表示開啟TCP連接中TIME-WAITsockets的快速回收,默認為0,表示關閉
16、net.ipv4.tcp_tw_recycle = 1

#開啟重用。允許將TIME-WAITsockets重新用於新的TCP連接。
17、net.ipv4.tcp_tw_reuse = 1

#同樣有3個值,意思是:低於第一個值,TCP沒有內存壓力;在第二個值下,進入內存壓力階段;高於第三個值,TCP拒絕分配socket(內存單位是頁)。
18、net.ipv4.tcp_mem = 94500000 915000000 927000000

#系統中最多有多少個TCP套接字不被關聯到任何一個用戶文件句柄上。
19、net.ipv4.tcp_max_orphans = 3276800

#每個網絡接口接收數據包的速率比內核處理這些包的速率快時,允許送到隊列的數據包的最大數目。
20、net.core.netdev_max_backlog = 8096

#表示開啟SYNCookies。當出現SYN等待隊列溢出時,啟用cookies來處理,可防范少量SYN攻擊,默認為0,表示關閉。
21、net.ipv4.tcp_syncookies = 1
View Code
 
              

上面這些參數,如1、2、3、6、14、15、16、17等和CLOSE_WAIT或TIME_WAIT有關,優化相關參數讓服務跑的更好。

 

名詞解釋:

套接字:源IP地址和目的IP地址以及源端口號和目的端口號的組合稱為套接字。其用於標識客戶端請求的服務器和服務。套接字,是支持TCP/IP網絡通信的基本操作單元,可以看做是不同主機之間的進程進行雙向通信的端點,簡單的說就是通信的兩方的一種約定,用套接字中的相關函數來完成通信過程。

 

拔掉網線時Socket的檢查方法

最近在做有關於TCP采集程序時,發現在客戶端與服務器通過TCP socket進行通信的時候,如果客戶端應用程序正常或者異常退出時,服務器都可以在對應的socket通信連接上獲得響應(如返回0,或者拋出異常)。但是,如果在客戶端的網線被拔掉的情況下,那么默認情況下,服務器端需要很長的時間才會知道客戶端的網線斷掉。對於許多服務器應用程序來說,這么長的反應時間是不能允許的,在這種情況下通常使用“心跳機制”來解決類似的問題,這是一種可行的辦法。
    
    由於TCP采集程序只是通過長連接來接收消息,而不能與客戶端建立心跳機制,所以唯一可行的辦法就是設置超時機制,在非阻塞模式工作的情況下,可以通過空閑計數來判斷是否連接超時,在連接空閑情況下TCP采集程序會休眠10ms,並且空閑計數器加1,當收到數據時空閑計數器清零,因此當空閑計數達到3000次的時候,說明socket連接在30秒內沒有收到數據,此時認為連接超時,主動的斷開連接,釋放socket資源。
 如何檢查Socket是否斷開

最近在做一個TCP采集程序,使用到C/S的結構。功能比較的簡單,就是TCP采集程序作為服務器,信令采集設備作為客戶端,客戶端與服務器端之間建立長連接之后,開始發送信令報文給服務器。在服務器端使用多線程方式來處理每個客戶端的socket連接,服務器端不主動斷開鏈路,也沒有心跳機制來維護連接的狀態,客戶端發送數據的時間也是不一定的,只要有采集到信令數據時才進行發送。在客戶端socket斷開后,服務器端應該能夠知道並且釋放socket資源。

判斷socket是否已經斷開的方法是使用非阻塞的select方式進行socket檢查,步驟如下:

1)設置接收到的socket為異步方式;

2)使用select()函數測試一個socket是否可讀;

3)如果select()函數返回的值為1,但是使用recv()函數讀取的數據長度為0,那么說明該socket已經斷開。(windows和linux中select函數返回值是整數,python是經過封裝后的,所有返回值是有變化的描述符)

如果recv()返回值小於等於0時,客戶端的連接已經斷開,但是還需要判斷errno是否等於EINTR。如果errno=EINTR則說明recv()函數是由於程序接收到中斷信號后返回的,socket連接應該還是正常,步應該close掉socket連接。

       注:對於阻塞socket的recv函數會在以下三種情況下返回值:

1)接收到數據時會返回;

2)程序接收到信號時返回-1,errno=EINTR;

3)Socket出現問題時,返回-1,具體的錯誤碼請查看man recv;

4)一定要養成查看man說明,內容很詳細,很有幫助。

這種方法經過長時間的測試證明是有效的,僅供大家參考。

此外,UNP卷一上有很多socket異常情況下的模擬解釋,大家可以去閱讀下。如果網絡中間有多級路由,路由當掉等很多情況出現,所以建議程序中在應用層中加入心跳(heartbeat機制)和重連來維持連接的狀態。

TCP protocol has a timer to determine if the connection is abnormally closed. But this timeout value is very long by default and if you want to check this situation as soon as possible to improve performance, the best solution is to introduce a keepalive mechanism in application protocol design.

TCP協議有一個定時器來決定連接是否被異常關閉。但是該超時時間值缺省的情況下會非常長,如果你希望盡快的檢查出這種狀態改進性能,最好的方法就是在應用程序協議設計的時候引入keepalive(保持連接)機制。


免責聲明!

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



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