我是一個 Linux 伺服器上的程式,名叫小進。
老是有人說我最多隻能建立 65535 個 TCP 連線。
我不信這個邪,今天我要親自去實踐一下。
我走到作業系統老大的跟前,說:
"老操,我要建立一個 TCP 連線!"
老操不慌不忙,拿出一個表格遞給我,"小進,先填表吧"
我一看這個表,這不就是經典的 socket 四元組嘛。我只有一塊網路卡,其 IP 地址是 123.126.45.68,我想要與 110.242.68.3 的 80 埠建立一個 TCP 連線,我將這些資訊填寫在了表中。
源埠號填什麼呢?我記得埠號是 16 位的,可以有 0 ~ 65535 這個範圍的數字,那我隨便選一個吧!
正當我猶豫到底選什麼數字的時候,老操一把搶過我的表格。
"你墨跡個啥呢小進?源埠號不用你填,我會給你分配一個可用的數字。源IP也不用你填,我知道都有哪些網路卡,並且會幫你選個合適的。真是個新手,回去等訊息吧。"
"哦"
老操帶著我的表格,走了。
過了很長時間,老操終於回來了,並且帶著一個紙條。
"小進,你把這個收好了。"
我問道,"這是啥呀?"
老操不耐煩地說道,"剛剛說你是新手你還不服,這個 5 表示檔案描述符,linux 下一切皆檔案,你待會和你那個目標 IP 進行 TCP 通訊的時候,就對著這個檔案描述符讀寫就好啦。"
"這麼方便!好的,謝謝老操。"
我拿著這個檔案描述符,把它放到屬於我的記憶體中裱起來了,反正我只是想看看最多能建立多少 TCP 連線,又不是去真的用它,嘻嘻。
埠號
過了一分鐘,我又去找老操了。
"老操,我要建立一個 TCP 連線!"
老操不慌不忙,拿出一個表格遞給我,"小進,先填表吧"
這回我熟悉了,只把目標IP和目標埠填好。
老操辦好事之後,又帶著一個紙條回來,上面寫著數字"6"。
就這樣,我每隔一分鐘都去找老操建立一個新的 TCP 連線,目標 IP 都是110.242.68.3,目標埠都是 80。
老操也很奇怪,不知道我在這折騰啥,他雖然權力大,但無權拒絕我的指令,每次都兢兢業業地把事情辦好,並給我一張一張寫著檔案描述符的紙條。
直到有一次,我收到的紙條有些不同。
我帶著些許責怪的語氣問,"老操,這是怎麼回事呀?"
老操也沒好氣地說,"這表示埠號不夠用啦!早就覺得你小子不對勁了,一個勁地對著同一個 IP 和埠建立 TCP 連線,之前沒辦法必須執行你給的指令,現在不行了,埠號不夠用了,源埠那裡我沒法給你填了。"
我也不是那麼好騙的,質疑道。"老操,你也別欺負我這個新手,我可是知道埠號是 16 位的,範圍是 1~65535,一共可以建立 65535 個 TCP 連線,我現在才建立了 63977 個,怎麼就不夠了!"
老操鄙視地看了我一眼,"你小子可真是閒的蛋疼啊,還真一個個數,來我告訴你吧,Linux 對可使用的埠範圍是有具體限制的,具體可以用如下命令檢視。"
[root]# cat /proc/sys/net/ipv4/ip_local_port_range
1024 65000
"看到沒,當前的限制是1024~65000,所以你就只能有63977個埠號可以使用。"
我趕緊像老操道歉,"哎喲真是抱歉,還是我見識太少,那這個數可以修改麼?"
老操也沒跟我一般見識,還是耐心地回答我,"可以的,具體可以 vim /etc/sysctl.conf 這個檔案進行修改,我們在這個檔案裡新增一行記錄"
net.ipv4.ip_local_port_range = 60000 60009
"儲存好後執行 sysctl -p /etc/sysctl.conf 使其生效。這樣你就只有 10 個埠號可以用了,就會更快報出埠號不夠用的錯誤"
"原來如此,謝謝老操又給我上了一課。"
哎不對,建立一個 TCP 連線,需要將通訊兩端的套接字(socket)進行繫結,如下:
源 IP 地址:源埠號 <----> 目標 IP 地址:目標埠號
只要這套繫結關係構成的四元組不重複即可,剛剛埠號不夠用了,是因為我一直對同一個目標IP和埠建立連線,那我換一個目標埠號試試。
看來成功了,只要源埠號不用夠用了,就不斷變換目標 IP 和目標埠號,保證四元組不重複,我就能建立好多好多 TCP 連線啦!
這也證明了有人說最多隻能建立 65535 個TCP連線是多麼荒唐。
檔案描述符
找到了突破埠號限制的辦法,我不斷找老操建立TCP連線,老操也拿我沒有辦法。
直到有一次,我又收到了一張特殊的紙條,上面寫的不是檔案描述符。
我又沒好氣地問老操,"這又是咋回事?"
老操幸災樂禍地告訴我,"呵呵,你小子以為突破埠號限制就無法無天了?現在檔案描述符不夠用啦!"
"怎麼啥啥都有限制啊?你們作業系統給我們的限制也太多了吧?"
"廢話,你看看你都建了多少個TCP連線了!每建立一個TCP連線,我就得分配給你一個檔案描述符,linux 對可開啟的檔案描述符的數量分別作了三個方面的限制。"
系統級:當前系統可開啟的最大數量,通過 cat /proc/sys/fs/file-max 檢視
使用者級:指定使用者可開啟的最大數量,通過 cat /etc/security/limits.conf 檢視
程式級:單個程式可開啟的最大數量,通過 cat /proc/sys/fs/nr_open 檢視
天呢,真是人在屋簷下呀,我趕緊看了看這些具體的限制。
[root ~]# cat /proc/sys/fs/file-max
100000
[root ~]# cat /proc/sys/fs/nr_open
100000
[root ~]# cat /etc/security/limits.conf
...
* soft nproc 100000
* hard nproc 100000
原來如此,我記得剛剛收到的最後一張紙條是。
再之後就收到檔案描述符不夠的錯誤了。
我又請教老操,"老操,那這個限制可以修改麼?"
老操仍然耐心地告訴我,"當然可以,比如你想修改單個程式可開啟的最大檔案描述符限制為100,可以這樣。"
echo 100 > /proc/sys/fs/nr_open
"原來如此,我這就去把各種檔案描述符限制都改大一點,也不多,就在後面加個0吧"
"額,早知道不告訴你小子了。"老操再次用鄙視的眼睛看著我。
執行緒
突破了檔案描述符限制,我又開始肆無忌憚地建立起了TCP連線。
但我發現,老操的辦事效率越來越慢,建立一個TCP連線花的時間越來越久。
有一次,我忍不住責問老操,"你是不是在偷懶啊?之前找你建一個TCP連線就花不到一分鐘時間,你看看最近我哪次不是等一個多小時你才搞好?"
老操也忍不住了,"小進啊你還好意思說我,你知不知道你每建一個TCP連線都需要消耗一個執行緒來為你服務?現在我和CPU老大那裡都忙得不可開交了,一直在為你這好幾十萬個執行緒不停地進行上下文切換,我們精力有限啊,自然就沒法像以前那麼快為你服務了。"
聽完老操的抱怨,我想起了之前似乎有人跟我說過 C10K 問題,就是當伺服器連線數達到 1 萬且每個連線都需要消耗一個執行緒資源時,作業系統就會不停地忙於執行緒的上下文切換,最終導致系統崩潰,這可不是鬧著玩的。
我趕緊像作業系統老大請教,"老操,實在不好意思,一直以為你強大無比,沒想到也有忙得不可開交的時候呀,那我們現在應該怎麼辦呀?"
老操無奈地說,"我勸你還是別再繼續玩了,沒什麼意義,不過我想你也不會聽我的,那我跟你說兩句吧。"
你現在這種每建一個TCP連線就建立一個執行緒的方式,是最傳統的多執行緒併發模型,早期的作業系統也只支援這種方式。但現在我進化了,我還支援 IO 多路複用的方式,簡單說就是一個執行緒可以管理多個 TCP 連線的資源,這樣你就可以用少量的執行緒來管理大量的 TCP 連線了。
我一臉疑惑,"啥是 IO 多路複用啊?"。
老操一臉鄙視,"你這... 你去看看閃客的《你管這破玩意叫 IO 多路複用》,就明白了。"
這次真是大開眼界了,我趕緊把程式碼改成了這種 IO 多路複用的模型,將原來的 TCP 連線銷燬掉,改成同一個執行緒管理多個 TCP 連線,很快,作業系統老大就恢復了以往的辦事效率,同時我的 TCP 連線數又多了起來。
記憶體
突破了埠號、檔案描述符、執行緒數等重重限制的我,再次肆無忌憚地建立起了TCP連線。
直到有一次,我又收到了一張紅牌。
嗨,又是啥東西限制了呀,改了不就完了。我不耐煩地問老操,"這回又是啥毛病?"
老操說道。"這個錯誤叫記憶體溢位,每個TCP連線本身,以及這個連線所用到的緩衝區,都是需要佔用一定記憶體的,現在記憶體已經被你佔滿了,不夠用了,所以報了這個錯。"
我看這次老操特別耐心,也沒多說什麼,但想著被記憶體限制住了,有點不太開心,於是我讓老操幫我最後一個忙。
"老操呀,幫小進我最後一個忙吧,你權利大,你看看把那些特別佔記憶體的程式給殺掉,給我騰出點地方,我今天要完成我的夢想,看看TCP連線數到底能建立多少個!"
老操見我真的是夠拼的,便答應了我,殺死了好多程式,我很是感動。
CPU
有了老操為我爭取的記憶體資源,我又開始日以繼日地建立TCP連線。
老操也不再說什麼,同樣日以繼日地執行著我的指令。
有一次,老操語重心長地對我說,"差不多了,我勸你就此收手吧,現在 CPU 的佔用率已經快到 100% 了。"
我覺得老操這人真的可笑,經過這幾次的小挫折,我明白了只要思想不滑坡,方法總比苦難多,老操這人就是太謹慎了,我豈能半途而廢,不管他。
我仍然繼續建立著 TCP 連線。
直到有一天,老操把我請到一個小飯館,一塊吃了頓飯,吃好後說道。"我們哥倆也算是配合了很久啦,今天我是來跟你道個別的。"
我很不解地問,"怎麼了老操,發生什麼事了?。"
老操說,"由於你的 TCP 連線,CPU 佔用率已經很長時間維持在 100%,我們的使用者,也就是我們的上帝,幾乎什麼事情都做不了了,連滑鼠動一下都要等好久,所以他給我下達了一個重啟的指令,我執行這個指令後,你,以及像你一樣的所有程式,包括我這個作業系統本身,一切都就消失了。"
我大驚失色,"啊,這麼突然麼?這條指令什麼時候執行?"
老操緩緩起身,"就現在了,剛剛這條指令還沒得到 CPU 執行的機會,不過現在到了。"
突然,我眼前一黑,一切都沒了。
總結
資源 |
一臺Linux伺服器的資源 |
一個TCP連線佔用的資源 |
佔滿了會發生什麼 |
CPU |
看你花多少錢買的 |
看你用它幹嘛 |
電腦卡死 |
記憶體 |
看你花多少錢買的 |
取決於緩衝區大小 |
OOM |
臨時埠號 |
ip_local_port_range |
1 |
cannot assign requested address |
檔案描述符 |
fs.file-max |
1 |
too many open files |
程式\執行緒數 |
ulimit -n |
看IO模型 |
系統崩潰 |
後記
其實這個問題,我覺得結論不重要,最重要的是思考過程。
而思考過程其實相當簡單,就是,尋找限制條件而已,其實一開始這篇文章,我寫了個故事在開頭,但後來感覺放在後記更合適。故事是這樣的。
閃客:小宇,我問你,你一天最多能吃多少個漢堡?
小宇:額,你這問的太隱私了吧,不過看在你教我技術的份上,我就告訴你,最多能吃 4 個左右吧。
閃客:咳咳真的麼?好吧,那你一分鐘最多能吃多少個漢堡?
小宇:快的話可能 2 個,不過正常應該最多就能吃完 1 個了。
閃客:好的,那我問你,剛剛這兩個問題你為什麼能不假思索地回答出來呢?
小宇:哈哈你這是什麼話,我自己我當然瞭解了。
閃客:不,你仔細想想你回答這兩個問題的邏輯。
小宇:哦我明白你的意思了,當你問我一天最多能吃多少個漢堡時,我考慮的是我的胃的容量最多能容下多少個漢堡。而當你問我一分鐘最多能吃多少個漢堡時,我考慮的時我吃漢堡的速度,按照這個速度在一分鐘內能吃多少。
閃客:沒錯,你總結得很好!一天最多吃多少個漢堡,此時時間非常充裕,所以主要是胃的容量限制了這個漢堡最大值,計算公式應該是:
最多漢堡數 = 胃的容量 ÷ 漢堡的體積
而一分鐘最多吃多少個漢堡,此時胃的容量非常充裕,限制漢堡最大值的是時間因素,計算公式是:
最多漢堡數 = 一分鐘 ÷ 吃一個漢堡的耗時
所以,取決於最先觸達的那個限制條件。
而最大 TCP 連線數這個問題,假如面試被問到了,即使你完全不會,也應該有這樣的思路。
而如果你有了這樣的思路,你多多少少都能回答出讓面試官滿意的答案,因為計算機很多時候,更看重思路,而不是細枝末節。
應該有這樣的思路。
而如果你有了這樣的思路,你多多少少都能回答出讓面試官滿意的答案,因為計算機很多時候,更看重思路,而不是細枝末節。