網絡編程中TCP基礎鞏固以及Linux打開的文件過多文件句柄的總結

1.TCP鏈接(短連接和長鏈接)html

什麼是TCP鏈接?TCP(Transmission Control Protocol 傳輸控制協議)是一種面向鏈接的、可靠的、基於字節流的傳輸層通訊協議。linux

當網絡通訊時採用TCP協議時,在真正的讀寫操做以前,server與client之間必須創建一個鏈接,當讀寫操做完成後,雙方再也不須要這個鏈接時它們能夠釋放這個鏈接,鏈接的創建是須要三次握手的,而釋放則須要4次揮手,因此說每一個鏈接的創建都是須要資源消耗和時間消耗的。git

TCP短連接:短鏈接是指通訊雙方有數據交互時,就創建一個TCP鏈接,數據發送完成後,則斷開此TCP鏈接(管理起來比較簡單,存在的鏈接都是有用的鏈接,不須要額外的控制手段)。github

鏈接→數據傳輸→關閉鏈接;數據庫

TCP長鏈接:所謂長鏈接,指在一個TCP鏈接上能夠連續發送多個數據包,在TCP鏈接保持期間,若是沒有數據包發送,須要雙方發檢測包以維持此鏈接,通常須要本身作在線維持(不發生RST(reset)包用於強制關閉TCP鏈接的包和四次揮手)。編程

 鏈接→數據傳輸→保持鏈接(心跳)→數據傳輸→保持鏈接(心跳)→……→關閉鏈接(一個TCP鏈接通道多個讀寫通訊);vim

這就要求長鏈接在沒有數據通訊時,定時發送數據包(心跳),以維持鏈接狀態。這就是TCP保活功能,保活功能主要爲服務器應用提供,服務器應用但願知道客戶主機是否崩潰,從而能夠釋放客戶端佔用的資源。服務器

心跳包:它像心跳同樣每隔固定時間發一次,以此來告訴服務器,這個客戶端還活着。用於保持長鏈接,至於這個包的內容,是沒有什麼特別規定的,不過通常都是很小的包,或者只包含包頭的一個空包。在TCP的機制裏面,自己是存在有心跳包的機制的,也就是TCP的選項:SO_KEEPALIVE。系統默認是設置的2小時的心跳頻率。可是它檢查不到機器斷電、網線拔出、防火牆這些斷線。心跳包主要也就是用於長鏈接的保活和斷線處理。通常的應用下,斷定時間在30-40秒比較不錯。若是實在要求高,那就在6-9秒。網絡

應用場景:長鏈接多用於操做頻繁(讀寫),點對點的通信,並且鏈接數不能太多狀況。每一個TCP鏈接都須要三步握手,這須要時間,若是每一個操做都是先鏈接,再操做的話那麼處理速度會下降不少,因此每一個操做完後都不斷開,下次處理時直接發送數據包就OK了,不用創建TCP鏈接。例如:數據庫的鏈接用長鏈接, 若是用短鏈接頻繁的通訊會形成socket錯誤,並且頻繁的socket 建立也是對資源的浪費。併發

而像WEB網站的http服務通常都用短連接(http1.0只支持短鏈接,http1.1keep alive 帶時間,操做次數限制的長鏈接),由於長鏈接對於服務端來講會耗費必定的資源,而像WEB網站這麼頻繁的成千上萬甚至上億客戶端的鏈接用短鏈接會更省一些資源,若是用長鏈接,並且同時有成千上萬的用戶,若是每一個用戶都佔用一個鏈接的話,那可想而知吧。因此併發量大,但每一個用戶無需頻繁操做狀況下需用短連好;

在長鏈接中通常是沒有條件可以判斷讀寫何時結束,因此必需要加長度報文頭。讀函數先是讀取報文頭的長度,以及報文開始結束符號,再根據這個長度去讀相應長度的報文。

 

 2.單臺服務器支持最大的TCP鏈接數

在tcp應用中,server事先在某個固定端口監聽,client主動發起鏈接,通過三路握手後創建tcp鏈接。那麼對單機,其最大併發tcp鏈接數是多少?

如何標識一個TCP鏈接:系統用一個4四元組來惟一標識一個TCP鏈接:{local ip, local port,remote ip,remote port}。

Client最大tcp鏈接數:client每次發起tcp鏈接請求時,除非綁定端口,一般會讓系統選取一個空閒的本地端口(local port),該端口是獨佔的,不能和其餘tcp鏈接共享。tcp端口的數據類型是unsigned short,所以本地端口個數最大隻有65536,端口0有特殊含義,不能使用,這樣可用端口最多隻有65535,因此在所有做爲client端的狀況下,客戶端發起的最大tcp鏈接數爲65535,這些鏈接能夠連到不一樣的server ip。

Server最大tcp鏈接數:server一般固定在某個本地端口上監聽,等待client的鏈接請求。不考慮地址重用(unix的SO_REUSEADDR選項)的狀況下,即便server端有多個ip,本地監聽端口也是獨佔的,所以server端tcp鏈接4元組中只有remote ip(也就是client ip)和remote port(客戶端port)是可變的,所以最大tcp鏈接爲客戶端ip數×客戶端port數,對IPV4,不考慮ip地址分類等因素,最大tcp鏈接數約爲2的32次方(ip數)×2的16次方(port數),也就是server端單機最大tcp鏈接數約爲2的48次方。

實際的tcp鏈接數:上面給出的是理論上的單機最大鏈接數,在實際環境中,受到機器資源、操做系統等的限制,特別是sever端,其最大併發tcp鏈接數遠不能達到理論上限。在unix/linux下限制鏈接數的主要因素是內存和容許的文件描述符個數(每一個tcp鏈接都要佔用必定內存,每一個socket就是一個文件描述符),另外1024如下的端口一般爲保留端口。在默認2.6內核配置下,通過試驗,每一個socket佔用內存在15~20k之間。

對server端,經過增長內存、修改最大文件描述符個數等參數,單機最大併發TCP鏈接數超過10萬 是沒問題的,國外 Urban Airship 公司在產品環境中已作到 50 萬併發 。在實際應用中,對大規模網絡應用,還須要考慮C10K 問題。咱們先假設單臺服務器最多隻能支持萬級併發鏈接,其實對絕大多數應用來講已經遠遠足夠了,可是對於一些擁有很大用戶基數的互聯網公司,每每面臨的併發鏈接數是百萬,千萬,甚至騰訊的上億(注:QQ默認用的UDP協議)。雖然如今的集羣,分佈式技術能夠爲咱們將併發負載分擔在多臺服務器上,那咱們只須要擴展出數十臺電腦就能夠解決問題,可是咱們更但願能更大的挖掘單臺服務器的資源,先努力垂直擴展,再進行水平擴展,這樣能夠有效的節省服務器相關的開支(硬件資源,機房,運維,電力其實也是一筆不小的開支)。

 補充知識:

1、文件句柄限制

在linux下編寫網絡服務器程序時每個tcp鏈接都要佔一個文件描述符,一旦這個文件描述符使用完了,新的鏈接到來返回給咱們的錯誤是「Socket/File:Can't open so many files」即文件打開過多!

這時你須要明白操做系統對能夠打開的最大文件數的限制。

  • 進程限制

    • 執行 ulimit -n 輸出 1024,說明對於一個進程而言最多隻能打開1024個文件,因此你要採用此默認配置最多也就能夠併發上千個TCP鏈接。

    • 臨時修改:ulimit -n 1000000,可是這種臨時修改只對當前登陸用戶目前的使用環境有效,系統重啓或用戶退出後就會失效。

    • 重啓後失效的修改(不過我在CentOS 6.5下測試,重啓後未發現失效):編輯 /etc/security/limits.conf 文件, 修改後內容爲

      * soft nofile 1000000

      * hard nofile 1000000

    • 永久修改:編輯/etc/rc.local,在其後添加以下內容

      ulimit -SHn 1000000

  • 全侷限制

    • 執行 cat /proc/sys/fs/file-nr 輸出 9344 0 592026,分別爲:1.已經分配的文件句柄數,2.已經分配但沒有使用的文件句柄數,3.最大文件句柄數。但在kernel 2.6版本中第二項的值總爲0,這並非一個錯誤,它實際上意味着已經分配的文件描述符無一浪費的都已經被使用了 。

    • 咱們能夠把這個數值改大些,用 root 權限修改 /etc/sysctl.conf 文件:

      fs.file-max = 1000000

      net.ipv4.ip_conntrack_max = 1000000

      net.ipv4.netfilter.ip_conntrack_max = 1000000

2、端口號範圍限制?

操做系統上端口號1024如下是系統保留的,從1024-65535是用戶使用的。因爲每一個TCP鏈接都要佔一個端口號,因此咱們最多能夠有60000多個併發鏈接。我想有這種錯誤思路朋友不在少數吧?(其中我過去就一直這麼認爲)

  • 如何標識一個TCP鏈接:系統用一個4四元組來惟一標識一個TCP鏈接:{local ip, local port,remote ip,remote port}。好吧,咱們拿出《UNIX網絡編程:卷一》第四章中對accept的講解來看看概念性的東西,第二個參數cliaddr表明了客戶端的ip地址和端口號。而咱們做爲服務端實際只使用了bind時這一個端口,說明端口號65535並非併發量的限制。

  • server最大tcp鏈接數:server一般固定在某個本地端口上監聽,等待client的鏈接請求。不考慮地址重用(unix的SO_REUSEADDR選項)的狀況下,即便server端有多個ip,本地監聽端口也是獨佔的,所以server端tcp鏈接4元組中只有remote ip(也就是client ip)和remote port(客戶端port)是可變的,所以最大tcp鏈接爲客戶端ip數×客戶端port數,對IPV4,不考慮ip地址分類等因素,最大tcp鏈接數約爲2的32次方(ip數)×2的16次方(port數),也就是server端單機最大tcp鏈接數約爲2的48次方。

3、總結

TCP/IP 協議規定的,只用了2個字節表示端口號。容易讓人誤解爲1個server只容許鏈接65535個Client。

typedef struct _NETWORK_ADDRESS_IP
{ USHORT      sin_port;//0~65535
 ULONG       in_addr;
 UCHAR       sin_zero[8];
} NETWORK_ADDRESS_IP, *PNETWORK_ADDRESS_IP;

(1)其實65535這個數字,只是決定了服務器端最多能夠擁有65535個Bind的Socket。也就是說,最多能夠開65535個服務器進程,可是你要知道這個可以鏈接客戶端的數量沒有任何關係,Accept過來的Socket是不須要Bind任何IP地址的,也沒有端口占用這一說。做爲Server端的Socket自己只負責監聽和接受鏈接操做。

(2)TCP協議裏面是用[源IP+源Port+目的IP+目的 Port]來區別兩個不一樣鏈接,因此連入和連出是兩個不一樣的概念。連出Connect就不錯了,須要生成隨機端口,這個是有限的連入的話, 因SOCKET的分配受內存分頁限制,而鏈接受限制(WINDOWS)。

(3)因此,千萬不要誤覺得1個server只容許鏈接65535個Client。記住,TCP連出受端口限制,連入僅受內存限制

例如server,IP:192.168.16.254,Port:8009

Client1:IP:192.168.16.1,Port:2378

Client2:IP:192.168.16.2,Port:2378

Client1和Client2雖然Port相同,可是IP不一樣,因此是不一樣的鏈接。

(4)想讓1個server併發高效得鏈接幾萬個Client,須要使用IOCP「完成端口(Completion Port)」的技術。

上面給出的結論都是理論上的單機TCP併發鏈接數,實際上單機併發鏈接數確定要受硬件資源(內存)、網絡資源(帶寬)的限制。

這種單臺機器10w併發,不考慮內存cpu的實現,主要是程序網絡模型的選擇。項目在Github上有提供https://github.com/yaocoder/HPNetServer

 linux系統優化設置調整參見:https://www.cnblogs.com/duanxz/p/4464178.html

 3.too many open files(打開的文件過多)解決方法

 

Netty做爲通信服務器,發生了打開的文件過多異常,以後在有新的鏈接就沒法鏈接上來,致使程序崩潰。首先要檢查程序斷開後是否釋放資源,確認無誤後在調整文件句柄限制。

too many open files(打開的文件過多)是Linux系統中常見的錯誤,從字面意思上看就是說程序打開的文件數過多,不過這裏的files不單是文件的意思,也包括打開的通信連接(好比socket),正在監聽的端口等等,因此有時候也能夠叫作句柄(handle),這個錯誤一般也能夠叫作句柄數超出系統限制。

引發的緣由就是進程在某個時刻打開了超過系統限制的文件數量以及通信連接數,經過命令ulimit -a能夠查看當前系統設置的最大句柄數是多少:

[dsuser@test02-ds-gps01 ~]$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 15220
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 10240
cpu time (seconds, -t) unlimited
max user processes (-u) 1024
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited

open files那一行就表明系統目前容許單個進程打開的最大句柄數,這裏是1024。 

[dsuser@test02-ds-gps01 ~]$ jps -l
9314 com.hns.gps.gw.jt808.app.Main
17135 sun.tools.jps.Jps
使用命令lsof -p 進程id能夠查看單個進程全部打開的文件詳情,使用命令lsof -p 進程id | wc -l能夠統計進程打開了多少文件:

[dsuser@test02-ds-gps01 ~]$ lsof -p 9314 | wc -l
226

以目前的通信程序爲例,能夠看到它目前打開了226個文件數,若是文件數過多使用lsof -p 進程id命令沒法徹底查看的話,可使用lsof -p 進程id > openfiles.log將執行結果內容輸出到日誌文件中查看。
解決方法:

一、增大容許打開的文件數——命令方式
ulimit -n 2048
這樣就能夠把當前用戶的最大容許打開文件數量設置爲2048了,但這種設置方法在重啓後會還原爲默認值。
ulimit -n命令非root用戶只能設置到4096。
想要設置到8192須要sudo權限或者root用戶。

二、增大容許打開的文件數——修改系統配置文件
vim /etc/security/limits.conf
#在最後加入
* soft nofile 4096
* hard nofile 4096
或者只加入

* - hard nofile 8192
最前的 * 表示全部用戶,可根據須要設置某一用戶,例如

dsuser soft nofile 8192
dsuser hard nofile 8192
注意」nofile」項有兩個可能的限制措施。就是項下的hard和soft。 要使修改過得最大打開文件數生效,必須對這兩種限制進行設定。 若是使用」-「字符設定, 則hard和soft設定會同時被設定。

三、檢查程序問題
若是你對你的程序有必定的解的話,應該對程序打開文件數(連接數)上限有必定的估算,若是感受數字異常,請使用第一步的lsof -p 進程id > openfiles.log命令,得到當前佔用句柄的所有詳情進行分析,

1)打開的這些文件是否是都是必要的?
2)定位到打開這些文件的代碼
3)是否程序操做了文件寫入,可是沒有進行正常關閉
4)是否程序進行了通信,可是沒有正常關閉(也就是沒有超時結束的機制)
若是程序中存在這些問題的話,不管系統句柄數設置的多麼大,隨着時間的推移,也必定會佔用完。

總結完畢!延伸閱讀:Netty系列之Netty百萬級推送服務設計要點 (https://www.cnblogs.com/ruixueyan/p/6382770.html)

相關文章
相關標籤/搜索