淺談網絡九-你真的知道Socket是什麼嗎?

Socket 一說起來肯定都並不陌生,大概一說出來所有人的第一反應就是,這是個做端到端長連接通信的。一點問題沒有,很對。但是如果要是有人問起,基於UDP的Socket和基於TCP的Socket有什麼區別?一個Socket的連接都經歷了什麼?監聽Socket和真正Socket傳輸是一個Socket嗎?Socket編程都要設置啥參數?等等的一系列問題。如果這些問題你能夠做到胸有成竹,大概這邊文章對你也沒啥用處。如果想要了解下的可以接着往下看。

建立Socket需要設置很多參數?

並不是的,其實學習它並不需要記住很多亂七八糟意義的參數,因爲Socket 編程進行的是端到端的通信,往往意識不到中間經過多少局域網,多少路由,,因而能夠設置的參數,也只能是端到端協議之上網絡層和傳輸層的。

在網絡層,Socket 函數需要指定到底是 IPv4 還是 IPv6,分別對應設置爲 AF_INET 和 AF_INET6。另外,還要指定到底是 TCP 還是 UDP。TCP 協議是基於數據流的,所以設置爲 SOCK_STREAM,而 UDP 是基於數據報的,因而設置爲 SOCK_DGRAM。

監聽Socket和真正Socket傳輸是一個Socket嗎?

上面講到了,Socket會分爲是基於TCP的還是基於UDP的。

對於TCP而言,首先說明,監聽和傳輸並不是一個Socket。爲什麼呢?那就要先說明下基於TCP的Socket的連接過程。

TCP 的服務端要先監聽一個端口,一般是先調用 bind 函數,給這個 Socket 賦予一個 IP 地址和端口。當服務端有了 IP 和端口號,就可以調用 listen 函數進行監聽。在 TCP 的狀態裏面,有一個 listen 狀態,當調用這個函數之後,服務端就進入了這個狀態,這個時候客戶端就可以發起連接了。在內核中,爲每個 Socket 維護兩個隊列。一個是已經建立了連接的隊列,這時候連接三次握手已經完畢,處於 established 狀態;一個是還沒有完全建立連接的隊列,這個時候三次握手還沒完成,處於 syn_rcvd 的狀態。接下來,服務端調用 accept 函數,拿出一個已經完成的連接進行處理。如果還沒有完成,就要等着。在服務端等待的時候,客戶端可以通過 connect 函數發起連接。先在參數中指明要連接的 IP 地址和端口號,然後開始發起三次握手。內核會給客戶端分配一個臨時的端口。一旦握手成功,服務端的 accept 就會返回另一個 Socket。

對於 UDP 來講,過程有些不一樣。UDP 是沒有連接的,所以不需要三次握手,也就不需要調用 listen 和 connect,但是,UDP 的的交互仍然需要 IP 和端口號,因而也需要 bind。UDP 是沒有維護連接狀態的,因而不需要每對連接建立一組 Socket,而是只要有一個 Socket,就能夠和多個客戶端通信。也正是因爲沒有連接狀態,每次通信的時候,都調用 sendto 和 recvfrom,都可以傳入 IP 地址和端口。

基於TCP和基於UDP的Socket連接過程是怎樣的?

上面基本介紹了Socket的連接過程,不過需要注意的是以下幾點:

1.TCP的服務端要先監聽一個端口,一般是先調用 bind 函數,給這個 Socket 賦予一個 IP 地址和端口。爲什麼需要端口呢?要知道,你寫的是一個應用程序,當一個網絡包來的時候,內核要通過 TCP 頭裏面的這個端口,來找到你這個應用程序,把包給你。

2.基於TCP的Scoket爲什麼要 IP 地址呢?有時候,一臺機器會有多個網卡,也就會有多個 IP 地址,你可以選擇監聽所有的網卡,也可以選擇監聽一個網卡,這樣,只有發給這個網卡的包,纔會給你。

3.說 TCP 的 Socket 在 Linux 中就是以文件的形式存在的。除此之外,還存在文件描述符。寫入和讀出,也是通過文件描述符。

有限的服務器資源如何接更多的連接?

首先沒事下爲什麼要考慮這個問題?答案很簡單,就是如何在有限的資源內支持更多的連接。

一般來說,服務器通常固定在某個本地端口上監聽,等待客戶端的連接請求。因此,服務端端 TCP 連接四元組中只有對端 IP, 也就是客戶端的 IP 和對端的端口,也即客戶端的端口是可變的,因此,最大 TCP 連接數 = 客戶端 IP 數×客戶端端口數。對 IPv4,客戶端的 IP 數最多爲 2 的 32 次方,客戶端的端口數最多爲 2 的 16 次方,也就是服務端單機最大 TCP 連接數,約爲 2 的 48 次方。

當然,服務端最大併發 TCP 連接數遠不能達到理論上限。首先主要是文件描述符限制,按照上面的原理,Socket 都是文件,所以首先要通過 ulimit 配置文件描述符的數目;另一個限制是內存,按上面的數據結構,每個 TCP 連接都要佔用一定內存,操作系統是有限的。

這裏我提出一個現在我個人覺得最好的一種方式:基於消息的IO多路複用。

所謂的多路複用通俗的類比一個例子就是,一個項目組照看多個項目,每個項目組都應該有個項目進度牆,將自己組看的項目列在那裏,一旦某個項目有了進展,就派人去盯一下。這個“項目進度牆”,是指一個文件描述符集合 fd_set ,由於 Socket 是文件描述符,所以某個線程盯的所有的 Socket,都會被存放在裏面。

如果有了改變,就會觸發事件進行通知,從而進行處理。能完成這件事情的函數叫 epoll,它在內核中的實現不是通過輪詢的方式,而是通過註冊 callback 函數的方式,當某個文件描述符發送變化的時候,就會主動通知。

如圖所示,假設進程打開了 Socket m, n, x 等多個文件描述符,現在需要通過 epoll 來監聽是否這些 Socket 都有事件發生。其中 epoll_create 創建一個 epoll 對象,也是一個文件,也對應一個文件描述符,同樣也對應着打開文件列表中的一項。在這項裏面有一個紅黑樹,在紅黑樹裏,要保存這個 epoll 要監聽的所有 Socket。

當 epoll_ctl 添加一個 Socket 的時候,其實是加入這個紅黑樹,同時紅黑樹裏面的節點指向一個結構,將這個結構掛在被監聽的 Socket 的事件列表中。當一個 Socket 來了一個事件的時候,可以從這個列表中得到 epoll 對象,並調用 call back 通知它。

這種通知方式使得監聽的 Socket 數據增加的時候,效率不會大幅度降低,能夠同時監聽的 Socket 的數目也非常的多了。上限就爲系統定義的、進程打開的最大文件描述符個數。因而,epoll 被稱爲解決 C10K 問題的利器。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章