常識一:文件句柄限制
在linux下編寫網絡服務器程序的朋友肯定都知道每一個tcp連接都要占一個文件描述符,一旦這個文件描述符使用完了,新的連接到來返回給我們的錯誤是“Socket/File:Can'topen so many files”。
這時你需要明白操作系統對可以打開的最大文件數的限制。
-
進程限制
-
執行ulimit -n 輸出1024,說明對于一個進程而言最多只能打開1024個文件,所以你要采用此默認配置最多也就可以并發上千個TCP連接。
-
臨時修改:ulimit -n1000000,但是這種臨時修改只對當前登錄用戶目前的使用環境有效,系統重啟或用戶退出后就會失效。
-
永久修改:編輯/etc/security/limits.conf 文件, 修改后內容為
* soft nofile 1000000
* hard nofile 1000000
-
-
全局限制
-
執行 cat/proc/sys/fs/file-nr 輸出
9344 0592026
,分別為:1.已經分配的文件句柄數,2.已經分配但沒有使用的文件句柄數,3.最大文件句柄數。但在kernel2.6版本中第二項的值總為0,這并不是一個錯誤,它實際上意味著已經分配的文件描述符無一浪費的都已經被使用了 。 -
我們可以把這個數值改大些,用 root 權限修改 /etc/sysctl.conf 文件:
fs.file-max = 1000000
net.ipv4.ip_conntrack_max = 1000000
net.ipv4.netfilter.ip_conntrack_max = 1000000
-
常識二:端口號范圍限制?
操作系統上端口號1024以下是系統保留的,從1024-65535是用戶使用的。由于每個TCP連接都要占一個端口號,所以我們最多可以有60000多個并發連接。我想有這種錯誤思路朋友不在少數吧?(其中我過去就一直這么認為)
我們來分析一下吧
-
如何標識一個TCP連接:系統用一個4四元組來唯一標識一個TCP連接:{local ip, local port,remoteip,remoteport}。好吧,我們拿出《UNIX網絡編程:卷一》第四章中對accept的講解來看看概念性的東西,第二個參數cliaddr代表了客戶端的ip地址和端口號。而我們作為服務端實際只使用了bind時這一個端口,說明端口號65535并不是并發量的限制。
-
server最大tcp連接數:server通常固定在某個本地端口上監聽,等待client的連接請求。不考慮地址重用(unix的SO_REUSEADDR選項)的情況下,即使server端有多個ip,本地監聽端口也是獨占的,因此server端tcp連接4元組中只有remoteip(也就是client ip)和remoteport(客戶端port)是可變的,因此最大tcp連接為客戶端ip數×客戶端port數,對IPV4,不考慮ip地址分類等因素,最大tcp連接數約為2的32次方(ip數)×2的16次方(port數),也就是server端單機最大tcp連接數約為2的48次方。
要寫網絡程序就必須用Socket,這是程序員都知道的。而且,面試的時候,我們也會問對方會不會Socket編程?一般來說,很多人都會說,Socket編程基本就是listen,accept以及send,write等幾個基本的操作。是的,就跟常見的文件操作一樣,只要寫過就一定知道。
對于網絡編程,我們也言必稱TCP/IP,似乎其它網絡協議已經不存在了。對于TCP/IP,我們還知道TCP和UDP,前者可以保證數據的正確和可靠性,后者則允許數據丟失。最后,我們還知道,在建立連接前,必須知道對方的IP地址和端口號。除此,普通的程序員就不會知道太多了,很多時候這些知識已經夠用了。最多,寫服務程序的時候,會使用多線程來處理并發訪問。
我們還知道如下幾個事實:
1。一個指定的端口號不能被多個程序共用。比如,如果IIS占用了80端口,那么Apache就不能也用80端口了。
2。很多防火墻只允許特定目標端口的數據包通過。
3。服務程序在listen某個端口并accept某個連接請求后,會生成一個新的socket來對該請求進行處理。
于是,一個困惑了我很久的問題就產生了。如果一個socket創建后并與80端口綁定后,是否就意味著該socket占用了80端口呢?如果是這樣的,那么當其accept一個請求后,生成的新的socket到底使用的是什么端口呢(我一直以為系統會默認給其分配一個空閑的端口號)?如果是一個空閑的端口,那一定不是80端口了,于是以后的TCP數據包的目標端口就不是80了--防火墻一定會組織其通過的!實際上,我們可以看到,防火墻并沒有阻止這樣的連接,而且這是最常見的連接請求和處理方式。我的不解就是,為什么防火墻沒有阻止這樣的連接?它是如何判定那條連接是因為connet80端口而生成的?是不是TCP數據包里有什么特別的標志?或者防火墻記住了什么東西?
后來,我又仔細研讀了TCP/IP的協議棧的原理,對很多概念有了更深刻的認識。比如,在TCP和UDP同屬于傳輸層,共同架設在IP層(網絡層)之上。而IP層主要負責的是在節點之間(End?
所以,我有理由懷疑,防火墻并沒有足夠的信息判斷TCP數據包的更多信息,除了IP地址和端口號。而且,我們也看到,所謂的端口,是為了區分不同的應用的,以在不同的IP包來到的時候能夠正確轉發。
TCP/IP只是一個協議棧,就像操作系統的運行機制一樣,必須要具體實現,同時還要提供對外的操作接口。就像操作系統會提供標準的編程接口,比如Win32編程接口一樣,TCP/IP也必須對外提供編程接口,這就是Socket編程接口--原來是這么回事啊!
在Socket編程接口里,設計者提出了一個很重要的概念,那就是socket。這個socket跟文件句柄很相似,實際上在BSD系統里就是跟文件句柄一樣存放在一樣的進程句柄表里。這個socket其實是一個序號,表示其在句柄表中的位置。這一點,我們已經見過很多了,比如文件句柄,窗口句柄等等。這些句柄,其實是代表了系統中的某些特定的對象,用于在各種函數中作為參數傳入,以對特定的對象進行操作--這其實是C語言的問題,在C++語言里,這個句柄其實就是this指針,實際就是對象指針啦。
現在我們知道,socket跟TCP/IP并沒有必然的聯系。Socket編程接口在設計的時候,就希望也能適應其他的網絡協議。所以,socket的出現只是可以更方便的使用TCP/IP協議棧而已,其對TCP/IP進行了抽象,形成了幾個最基本的函數接口。比如create,listen,accept,connect,read和write等等。
現在我們明白,如果一個程序創建了一個socket,并讓其監聽80端口,其實是向TCP/IP協議棧聲明了其對80端口的占有。以后,所有目標是80端口的TCP數據包都會轉發給該程序(這里的程序,因為使用的是Socket編程接口,所以首先由Socket層來處理)。所謂accept函數,其實抽象的是TCP的連接建立過程。accept函數返回的新socket其實指代的是本次創建的連接,而一個連接是包括兩部分信息的,一個是源IP和源端口,另一個是宿IP和宿端口。所以,accept可以產生多個不同的socket,而這些socket里包含的宿IP和宿端口是不變的,變化的只是源IP和源端口。這樣的話,這些socket宿端口就可以都是80,而Socket層還是能根據源/宿對來準確地分辨出IP包和socket的歸屬關系,從而完成對TCP/IP協議的操作封裝!而同時,放火墻的對IP包的處理規則也是清晰明了,不存在前面設想的種種復雜的情形。
明白socket只是對TCP/IP協議棧操作的抽象,而不是簡單的映射關系,這很重要!