摘要:文件描述符是一個整數(shù)索引值,是這個數(shù)組的下標(biāo)。基于協(xié)議的基于的編程過程和有些不同。通過名字可以看出,這是在父進(jìn)程的基礎(chǔ)上完全拷貝一個子進(jìn)程。因此父進(jìn)程剛才因為創(chuàng)建的已連接也是一個文件描述符,同樣也會被子進(jìn)程獲得。
系列文章傳送門:
網(wǎng)絡(luò)協(xié)議 1 - 概述
網(wǎng)絡(luò)協(xié)議 2 - IP 是怎么來,又是怎么沒的?
網(wǎng)絡(luò)協(xié)議 3 - 從物理層到 MAC 層
網(wǎng)絡(luò)協(xié)議 4 - 交換機(jī)與 VLAN:辦公室太復(fù)雜,我要回學(xué)校
網(wǎng)絡(luò)協(xié)議 5 - ICMP 與 ping:投石問路的偵察兵
網(wǎng)絡(luò)協(xié)議 6 - 路由協(xié)議:敢問路在何方?
網(wǎng)絡(luò)協(xié)議 7 - UDP 協(xié)議:性善碰到城會玩
網(wǎng)絡(luò)協(xié)議 8 - TCP 協(xié)議(上):性惡就要套路深
網(wǎng)絡(luò)協(xié)議 9 - TCP協(xié)議(下):聰明反被聰明誤
????前面一直在說各種協(xié)議,偏理論方面的知識,這次咱們就來認(rèn)識下基于 TCP 和 UDP 協(xié)議這些理論知識的 Socket 編程。
????說 TCP 和 UDP 的時候,我們是分成客戶端和服務(wù)端來認(rèn)識的,那在寫 Socket 的時候,我們也這樣分。
????Socket 這個名字很有意思,可以作插口或者插槽講。我們寫程序時,就可以將 Socket 想象為,一頭插在客戶端,一頭插在服務(wù)端,然后進(jìn)行通信。
????在建立 Socket 的時候,應(yīng)該設(shè)置什么參數(shù)呢?Socket 編程進(jìn)行的是端到端的通信,往往意識不到中間經(jīng)過多少局域網(wǎng),多少路由器,因而能夠設(shè)置的參數(shù),也只能是端到端協(xié)議之上網(wǎng)絡(luò)層和傳輸層的。
????對于網(wǎng)絡(luò)層和傳輸層,有以下參數(shù)需要設(shè)置:
IP協(xié)議:IPv4 對應(yīng) AF_INEF,IPv6 對應(yīng) AF_INET6;
傳輸層協(xié)議:TCP 與 UDP。TCP 協(xié)議基于數(shù)據(jù)流,其對應(yīng)值是 SOCKET_STREAM,而 UDP 是基于數(shù)據(jù)報的,其對應(yīng)值是 SOCKET_DGRAM。
????兩端創(chuàng)建了 Socket 之后,而后面的過程中,TCP 和 UDP 稍有不同,我們先來看看 TCP。
基于 TCP 協(xié)議的 Socket????對于 TCP 創(chuàng)建 Socket 的過程,有以下幾步走:
????1)TCP 調(diào)用 bind 函數(shù)賦予 Socket IP 地址和端口。
????為什么需要 IP 地址?還記得嗎?咱們之前了解過,一臺機(jī)器會有多個網(wǎng)卡,而每個網(wǎng)卡就有一個 IP 地址,我們可以選擇監(jiān)聽所有的網(wǎng)卡,也可以選擇監(jiān)聽一個網(wǎng)卡,只有,發(fā)給指定網(wǎng)卡的包才會發(fā)給你。
????為什么需要端口?要知道,咱們寫的是一個應(yīng)用程序,當(dāng)一個網(wǎng)絡(luò)包來的時候,內(nèi)核就是要通過 TCP 里面的端口號來找到對應(yīng)的應(yīng)用程序,把包給你。
????2)調(diào)用 listen 函數(shù)監(jiān)聽端口。 在 TCP 的狀態(tài)圖了,有一個 listen 狀態(tài),當(dāng)調(diào)用這個函數(shù)之后,服務(wù)端就進(jìn)入了這個狀態(tài),這個時候客戶端就可以發(fā)起連接了。
????在內(nèi)核中,為每個 Socket 維護(hù)兩個隊列。一個是已經(jīng)建立了連接的隊列,這里面的連接已經(jīng)完成三次握手,處于 established 狀態(tài);另一個是還沒有完全建立連接的隊列,這里面的連接還沒有完成三次握手,處于 syn_rcvd 狀態(tài)。
????3)服務(wù)端調(diào)用 accept 函數(shù)。 這時候服務(wù)端會拿出一個已經(jīng)完成的連接進(jìn)行處理,如果還沒有已經(jīng)完成的連接,就要等著。
????在服務(wù)端等待的時候,客戶端可以通過 connect 函數(shù)發(fā)起連接??蛻舳讼仍趨?shù)中指明要連接的 IP 地址和端口號,然后開始發(fā)起三次握手。內(nèi)核會給客戶端分配一個臨時的端口,一旦握手成功,服務(wù)端的 accept 就會返回另一個 Socket。
????注意,從上面的過程中可以看出,監(jiān)聽的 Socket 和真正用來傳數(shù)據(jù)的 Socket 是不同的兩個。 一個叫做監(jiān)聽 Socket,一個叫做已連接 Socket。
????下圖就是基于 TCP 協(xié)議的 Socket 函數(shù)調(diào)用過程:
????連接建立成功之后,雙方開始通過 read 和write 函數(shù)來讀寫數(shù)據(jù),就像往一個文件流里寫東西一樣。
????這里說 TCP 的 Socket 是一個文件流,是非常準(zhǔn)確的。因為 Socket 在 linux 中就是以文件的形式存在的。除此之外,還存在文件描述符。寫入和讀出,也是通過文件描述符。
????每一個進(jìn)程都有一個數(shù)據(jù)結(jié)構(gòu) task_struct,里面指向一個文件描述符數(shù)組,來列出這個進(jìn)程打開的所有文件的文件描述符。文件描述符是一個整數(shù)索引值,是這個數(shù)組的下標(biāo)。
????這個數(shù)組中的內(nèi)容是一個指針,指向內(nèi)核中所有打開的文件列表。而每個文件也會有一個 inode(索引節(jié)點)。
????對于 Socke 而言,它是一個文件,也就有對于的文件描述符。與真正的文件系統(tǒng)不一樣的是,Socket 對于的 inode 并不是保存在硬盤上,而是在內(nèi)存中。在這個 inode 中,指向了 Socket 在內(nèi)核中的 Socket 結(jié)構(gòu)。
????在這個機(jī)構(gòu)里面,主要有兩個隊列。一個發(fā)送隊列,一個接收隊列。這兩個隊列里面,保存的是一個緩存 sk_buff。這個緩存里能夠看到完整的包結(jié)構(gòu)。說到這里,你應(yīng)該就會發(fā)現(xiàn),數(shù)據(jù)結(jié)構(gòu)以及和前面了解的收發(fā)包的場景聯(lián)系起來了。
????上面整個過程說起來稍顯混亂,可對比下圖加深理解。
基于 UDP 協(xié)議的 Socket????基于 UDP 的 Socket 編程過程和 TCP 有些不同。UDP 是沒有連接狀態(tài)的,所以不需要三次握手,也就不需要調(diào)用 listen 和 connect。沒有連接狀態(tài),也就不需要維護(hù)連接狀態(tài),因而不需要對每個連接建立一組 Socket,只要建立一組 Socket,就能和多個客戶端通信。也正是因為沒有連接狀態(tài),每次通信的時候,都可以調(diào)用 sendto 和 recvfrom 傳入 IP 地址和端口。
????下圖是基于 UDP 的 Socket 函數(shù)調(diào)用過程:
服務(wù)器最大并發(fā)量????了解了基本的 Socket 函數(shù)后,就可以寫出一個網(wǎng)絡(luò)交互的程序了。就像上面的過程一樣,在建立連接后,進(jìn)行一個 while 循環(huán),客戶端發(fā)了收,服務(wù)端收了發(fā)。
????很明顯,這種一臺服務(wù)器服務(wù)一個客戶的方式和我們的實際需要相差甚遠(yuǎn)。這就相當(dāng)于老板成立了一個公司,只有自己一個人,自己親自服務(wù)客戶,只能干完一家再干下一家。這種方式肯定賺不了錢,這時候,就要想,我最多能接多少項目呢?
????我們可以先來算下理論最大值,也就是理論最大連接數(shù)。系統(tǒng)會用一個四元組來標(biāo)識一個 TCP 連接:
{本機(jī) IP,本機(jī)端口,對端 IP,對端端口}
????服務(wù)器通常固定監(jiān)聽某個本地端口,等待客戶端連接請求。因此,上面四元組中,可變的項只有對端 IP 和對端端口,也就是客戶端 IP 和客戶端端口。不難得出:
最大 TCP 連接數(shù) = 客戶端 IP 數(shù) x 客戶端端口數(shù)。
????對于 IPv4:
客戶端最大 IP 數(shù) = 2 的 32 次方
????對于端口數(shù):
客戶端最大端口數(shù) = 2 的 16 次方
????因此:
最大 TCP 連接數(shù) = 2 的 48 次方(估算值)
????當(dāng)然,服務(wù)端最大并發(fā) TCP 連接數(shù)遠(yuǎn)不能達(dá)到理論最大值。主要有以下原因:
文件描述符限制。按照上面的原理,Socket 都是文件,所以首先要通過 ulimit 配置文件描述符的數(shù)目;
內(nèi)存限制。按上面的數(shù)據(jù)結(jié)構(gòu),每個 TCP 連接都要占用一定的內(nèi)存,而系統(tǒng)內(nèi)存是有限的。
????所以,作為老板,在資源有限的情況下,要想接更多的項目,賺更多的錢,就要降低每個項目消耗的資源數(shù)目。
????本著這個原則,我們可以找到以下幾種方式來最可能的降低消耗項目消耗資源。
1)將項目外包給其他公司(多進(jìn)程方式)????這就相當(dāng)于你是一個代理,監(jiān)聽來的請求,一旦建立一個連接,就會有一個已連接的 Socket,這時候你可以創(chuàng)建一個紫禁城,然后將基于已連接的 Socket 交互交給這個新的子進(jìn)程來做。就像來了一個新項目,你可以注冊一家子公司,招人,然后把項目轉(zhuǎn)包給這就公司做,這樣你就又可以去接新的項目了。
????這里有個問題是,如何創(chuàng)建子公司,并將項目移交給子公司?
????在 Linux 下,創(chuàng)建子進(jìn)程使用 fork 函數(shù)。通過名字可以看出,這是在父進(jìn)程的基礎(chǔ)上完全拷貝一個子進(jìn)程。在 Linux 內(nèi)核中,會復(fù)制文件描述符的列表,也會復(fù)制內(nèi)存空間,還會復(fù)制一條記錄當(dāng)前執(zhí)行到了哪一行程序的進(jìn)程。
????這樣,復(fù)制完成后,父進(jìn)程和子進(jìn)程都會記錄當(dāng)前剛剛執(zhí)行完 fork。這兩個進(jìn)程剛復(fù)制完的時候,幾乎一模一樣,只是根據(jù) fork 的返回值來區(qū)分是父進(jìn)程還是子進(jìn)程。如果返回值是 0,則是子進(jìn)程,如果返回值是其他的整數(shù),就是父進(jìn)程,這里返回的整數(shù),就是子進(jìn)程的 ID。
????進(jìn)程復(fù)制過程如下圖:
????因為復(fù)制了文件描述符列表,而文件描述符都是指向整個內(nèi)核統(tǒng)一的打開文件列表的。因此父進(jìn)程剛才因為 accept 創(chuàng)建的已連接 Socket 也是一個文件描述符,同樣也會被子進(jìn)程獲得。
????接下來,子進(jìn)程就可以通過這個已連接 Socket 和客戶端進(jìn)行通信了。當(dāng)通信完成后,就可以退出進(jìn)程。那父進(jìn)程如何知道子進(jìn)程干完了項目要退出呢?父進(jìn)程中 fork 函數(shù)返回的整數(shù)就是子進(jìn)程的 ID,父進(jìn)程可以通過這個 ID 查看子進(jìn)程是否完成項目,是否需要退出。
2)將項目轉(zhuǎn)包給獨立的項目組(多線程方式)????上面這種方式你應(yīng)該能發(fā)現(xiàn)問題,如果每接一個項目,都申請一個新公司,然后干完了,就注銷掉,實在是太麻煩了。而且新公司要有新公司的資產(chǎn)、辦公家具,每次都買了再賣,不劃算。
????這時候,我們應(yīng)該已經(jīng)想到了線程。相比于進(jìn)程來講,線程更加輕量級。如果創(chuàng)建進(jìn)程相當(dāng)于成立新公司,而創(chuàng)建線程,就相當(dāng)于在同一個公司成立新的項目組。一個項目做完了,就解散項目組,成立新的項目組,辦公家具還可以共用。
????在 Linux 下,通過 pthread_create 創(chuàng)建一個線程,也是調(diào)用 do_fork。不同的是,雖然新的線程在 task 列表會新創(chuàng)建一項,但是很多資源,例如文件描述符列表、進(jìn)程空間,這些還是共享的,只不過多了一個引用而已。
????下圖是線程復(fù)制過程:
????新的線程也可以通過已連接 Socket 處理請求,從而達(dá)到并發(fā)處理的目的。
????上面兩種方式,無論是基于進(jìn)程還是線程模型的,其實還是有問題的。新到來一個 TCP 連接,就需要分配一個進(jìn)程或者線程。一臺機(jī)器能創(chuàng)建的進(jìn)程和線程數(shù)是有限的,并不能很好的發(fā)揮服務(wù)器的性能。著名的C10K問題,就是說一臺機(jī)器如何維護(hù) 1 萬了連接。按我們上面的方式,系統(tǒng)就要創(chuàng)建 1 萬個進(jìn)程或者線程,這是操作系統(tǒng)無法承受的。
????那既然一個線程負(fù)責(zé)一個 TCP 連接不行,能不能一個進(jìn)程或線程負(fù)責(zé)多個 TCP 連接呢?這就引出了下面兩種方式。
3)一個項目組支撐多個項目(IO 多路復(fù)用,一個線程維護(hù)多個 Socket)????當(dāng)一個項目組負(fù)責(zé)多個項目時,就要有個項目進(jìn)度墻來把控每個項目的進(jìn)度,除此之外,還得有個人專門盯著進(jìn)度墻。
????上面說過,Socket 是文件描述符,因此某個線程盯的所有的 Socket,都放在一個文件描述符集合 fd_set 中,這就是項目進(jìn)度墻。然后調(diào)用 select 函數(shù)來監(jiān)聽文件描述符集合是否有變化,一旦有變化,就會依次查看每個文件描述符。那些發(fā)生變化的文件描述符在 fd_set 對應(yīng)的位都設(shè)為 1,表示 Socket 可讀或者可寫,從而可以進(jìn)行讀寫操作,然后再調(diào)用 select,接著盯著下一輪的變化。
4)一個項目組支撐多個項目(IO 多路復(fù)用,從“派人盯著”到“有事通知”)????上面 select 函數(shù)還是有問題的,因為每次 Socket 所在的文件描述符集合中有發(fā)生變化的時候,都需要通過輪詢的方式將所有的 Socket 查看一遍,這大大影響了一個進(jìn)程或者線程能夠支撐的最大連接數(shù)量。使用 select,能夠同時監(jiān)聽的數(shù)量由 FD_SETSIZE 限制。
????如果改成事件通知的方式,情況就會好很多。項目組不需要通過輪詢挨個盯著所有項目,而是當(dāng)項目進(jìn)度發(fā)生變化的時候,主動通知項目組,然后項目組再根據(jù)項目進(jìn)展情況做相應(yīng)的操作。
????而 epoll 函數(shù)就能完成事件通知。它在內(nèi)核中的實現(xiàn)不是通過輪詢的方式,而是通過注冊 callback 函數(shù)的方式,當(dāng)某個文件描述符發(fā)生變化的時候,主動通知。
????如上圖所示,假設(shè)進(jìn)程打開了 Socket m、n、x 等多個文件描述符,現(xiàn)在需要通過 epoll 來監(jiān)聽這些 Socket 是否有事件發(fā)生。其中 epoll_create 創(chuàng)建一個 epoll 對象,也是一個文件,對應(yīng)一個文件描述符,同樣也對應(yīng)著打開文件列表中的一項。在這項里面有一個紅黑樹,在紅黑樹里,要保存這個 epoll 監(jiān)聽的所有的 Socket。
????當(dāng) epoll_ctl 添加一個 Scoket 的時候,其實就是加入這個紅黑樹中。同時,紅黑樹里面的節(jié)點指向一個結(jié)構(gòu),將這個結(jié)構(gòu)掛在被監(jiān)聽的 Socket 的事件列表中。當(dāng)一個 Socket 發(fā)生某個事件時,可以從這個列表中得到 epoll 對象,并調(diào)用 call_back 通知它。
????這種事件通知的方式使得監(jiān)聽的 Socket 數(shù)量增加的同時,效率也不會大幅度降低。因此,能夠同時監(jiān)聽的 Socket 的數(shù)量就非常的多了。上限為系統(tǒng)定義的,進(jìn)程打開的最大文件描述符個數(shù)。因而,epoll 被稱為解決 C10K 問題的利器。
小結(jié)牢記基于 TCP 和 UDP 的 Socket 編程中,客戶端和服務(wù)端需要調(diào)用的函數(shù);
epoll 機(jī)制能夠解決 C10K 問題。
參考:
The TCP/IP Guide;
百度百科 - Socket 詞條;
劉超 - 趣談網(wǎng)絡(luò)協(xié)議系列課;
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/29762.html
摘要:有興趣的博友可以按各編程語言進(jìn)行相關(guān)改寫,然后拿著我們的分析系統(tǒng)真實的看看網(wǎng)絡(luò)通信過程。本機(jī)請求轉(zhuǎn)發(fā)到網(wǎng)關(guān)代碼中的是內(nèi)網(wǎng)另一臺服務(wù)器,樓主的是。主要是下面的分析過程。分析系統(tǒng)介紹上面用到的分析系統(tǒng)叫科來網(wǎng)絡(luò)分析系統(tǒng),點我下載。 系列文章傳送門: 網(wǎng)絡(luò)協(xié)議 1 - 概述 網(wǎng)絡(luò)協(xié)議 2 - IP 是怎么來,又是怎么沒的? 網(wǎng)絡(luò)協(xié)議 3 - 從物理層到 MAC 層 網(wǎng)絡(luò)協(xié)議 4 - 交換機(jī)...
摘要:有興趣的博友可以按各編程語言進(jìn)行相關(guān)改寫,然后拿著我們的分析系統(tǒng)真實的看看網(wǎng)絡(luò)通信過程。本機(jī)請求轉(zhuǎn)發(fā)到網(wǎng)關(guān)代碼中的是內(nèi)網(wǎng)另一臺服務(wù)器,樓主的是。主要是下面的分析過程。分析系統(tǒng)介紹上面用到的分析系統(tǒng)叫科來網(wǎng)絡(luò)分析系統(tǒng),點我下載。 系列文章傳送門: 網(wǎng)絡(luò)協(xié)議 1 - 概述 網(wǎng)絡(luò)協(xié)議 2 - IP 是怎么來,又是怎么沒的? 網(wǎng)絡(luò)協(xié)議 3 - 從物理層到 MAC 層 網(wǎng)絡(luò)協(xié)議 4 - 交換機(jī)...
閱讀 3031·2021-11-24 10:21
閱讀 1602·2021-10-11 10:57
閱讀 2814·2021-09-22 15:24
閱讀 2678·2021-09-22 14:58
閱讀 2337·2019-08-30 13:16
閱讀 3489·2019-08-29 13:05
閱讀 3422·2019-08-29 12:14
閱讀 3461·2019-08-27 10:55