在Linux下編寫網絡服務器程序的朋友確定都知道每個tcp鏈接都要佔一個文件描述符,一旦這個文件描述符使用完了,新的鏈接到來返回給咱們的錯誤是「Socket/File: Can't open so many files」。程序員
這時你須要明白操做系統對能夠打開的最大文件數的限制。面試
進程限制編程
執行ulimit -n 輸出1024,說明對於一個進程而言最多隻能打開1024個文件,因此你要採用此默認配置最多也就能夠併發上千個TCP鏈接。服務器
臨時修改:ulimit -n1000000,可是這種臨時修改只對當前登陸用戶目前的使用環境有效,系統重啓或用戶退出後就會失效。網絡
永久修改:編輯/etc/security/limits.conf 文件, 修改後內容爲多線程
* soft nofile 1000000
併發
* hard nofile 1000000
socket
全侷限制tcp
執行 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
to End)的數據包傳送,這裏的節點是一臺網絡設備,好比計算機。由於IP層只負責把數據送到節點,而不能區分上面的不一樣應用,因此TCP和UDP協議在其基礎上加入了端口的信息,端口因而標識的是一個節點上的一個應用。除了增長端口信息,UPD協議基本就沒有對IP層的數據進行任何的處理了。而TCP協議還加入了更加複雜的傳輸控制,好比滑動的數據發送窗口(Slice Window),以及接收確認和重發機制,以達到數據的可靠傳送。無論應用層看到的是怎樣一個穩定的TCP數據流,下面傳送的都是一個個的IP數據包,須要由TCP協議來進行數據重組。因此,我有理由懷疑,防火牆並無足夠的信息判斷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協議棧操做的抽象,而不是簡單的映射關係,這很重要!