有沒有做數(shù)學(xué)題掙錢的網(wǎng)站艾滋病多久可以查出來
一、套接字Socket
基于 TCP UDP 協(xié)議的 Socket 編程,在講 TCP 和 UDP 協(xié)議的時候,我們分客戶端和服務(wù)端,在寫程序的時候,我們也同樣這樣分。
在網(wǎng)絡(luò)層,Socket 函數(shù)需要指定到底是 IPv4 還是 IPv6,分別對應(yīng)設(shè)置為 AF_INET 和 AF_INET6。另外,還要指定到底是 TCP 還是 UDP。還記得咱們前面講過的,TCP 協(xié)議是基于數(shù)據(jù)流的,所以設(shè)置為 SOCK_STREAM,而 UDP 是基于數(shù)據(jù)報的,因而設(shè)置為 SOCK_DGRAM。
監(jiān)聽的 Socket 和真正用來傳數(shù)據(jù)的 Socket 是兩個,一個叫作監(jiān)聽 Socket,一個叫作已連接 Socket
比喻:接待客人
想象你在家里準(zhǔn)備接待客人,你有一個門鈴和一個客廳。門鈴相當(dāng)于監(jiān)聽 Socket,而客廳相當(dāng)于已連接 Socket。
-
監(jiān)聽 Socket(門鈴):
- 你家的門鈴一直在等待有人按下它,這就像服務(wù)器上的監(jiān)聽 Socket 一直在等待新的連接請求。
- 當(dāng)有人按下門鈴時,你知道有客人到訪了,但你還不知道是誰,也還沒有開始與客人交談。
-
已連接 Socket(客廳):
- 當(dāng)你開門迎接客人,并帶他們到客廳后,你就開始與客人交流了,這時候的客人就相當(dāng)于已連接 Socket。
- 在客廳里,你可以與每個客人進行獨立的對話,不會相互干擾。
技術(shù)解釋
在網(wǎng)絡(luò)編程中,特別是 TCP 服務(wù)器程序中,監(jiān)聽 Socket 和已連接 Socket 是兩個不同的概念和用途:
-
監(jiān)聽 Socket:
- 作用:用來監(jiān)聽和接受新的連接請求。
- 創(chuàng)建:在服務(wù)器啟動時創(chuàng)建,并綁定到特定的 IP 地址和端口。
- 工作方式:服務(wù)器調(diào)用
listen()
方法,使這個 Socket 進入監(jiān)聽狀態(tài),等待客戶端的連接請求。當(dāng)有客戶端請求連接時,服務(wù)器調(diào)用accept()
方法,從監(jiān)聽 Socket 接受連接請求。
-
已連接 Socket:
- 作用:用來與客戶端進行實際的數(shù)據(jù)傳輸。
- 創(chuàng)建:當(dāng)服務(wù)器調(diào)用
accept()
方法并成功接收一個客戶端連接后,會生成一個新的已連接 Socket。這個 Socket 專門用于與該客戶端進行通信。 - 工作方式:服務(wù)器使用這個已連接 Socket 調(diào)用
send()
和recv()
方法,與客戶端交換數(shù)據(jù)。
基于 TCP 協(xié)議的 Socket 程序函數(shù)調(diào)用過程
write() 和 read():適用于所有類型的文件描述符,包括 Socket,功能簡單直接。
send() 和 recv():專為網(wǎng)絡(luò) Socket 設(shè)計,提供額外的功能和靈活性,通過 flags 參數(shù)控制行為。
基于 UDP 協(xié)議的 Socket 程序函數(shù)調(diào)用過程
對于 UDP 來講,過程有些不一樣。UDP 是沒有連接的,所以不需要三次握手,也就不需要調(diào)用 listen 和 connect,但是,UDP 的的交互仍然需要 IP 和端口號,因而也需要 bind。UDP 是沒有維護連接狀態(tài)的,因而不需要每對連接建立一組 Socket,而是只要有一個 Socket,就能夠和多個客戶端通信。也正是因為沒有連接狀態(tài),每次通信的時候,都調(diào)用 sendto 和 recvfrom,都可以傳入 IP 地址和端口。
服務(wù)器如何接更多的項目?
會了這幾個基本的 Socket 函數(shù)之后,你就可以輕松地寫一個網(wǎng)絡(luò)交互的程序了。就像上面的過程一樣,在建立連接后,進行一個 while 循環(huán)。客戶端發(fā)了收,服務(wù)端收了發(fā)。
當(dāng)然這只是萬里長征的第一步,因為如果使用這種方法,基本上只能一對一溝通。如果你是一個服務(wù)器,同時只能服務(wù)一個客戶,肯定是不行的。這就相當(dāng)于老板成立一個公司,只有自己一個人,自己親自上來服務(wù)客戶,只能干完了一家再干下一家,這樣賺不來多少錢。
那作為老板你就要想了,我最多能接多少項目呢?當(dāng)然是越多越好。
我們先來算一下理論值,也就是最大連接數(shù),系統(tǒng)會用一個四元組來標(biāo)識一個 TCP 連接。
{本機 IP, 本機端口, 對端 IP, 對端端口}
服務(wù)器通常固定在某個本地端口上監(jiān)聽,等待客戶端的連接請求。因此,服務(wù)端端 TCP 連接四元組中只有對端 IP, 也就是客戶端的 IP 和對端的端口,也即客戶端的端口是可變的,因此,最大 TCP 連接數(shù) = 客戶端 IP 數(shù)×客戶端端口數(shù)。對 IPv4,客戶端的 IP 數(shù)最多為 2 的 32 次方,客戶端的端口數(shù)最多為 2 的 16 次方,也就是服務(wù)端單機最大 TCP 連接數(shù),約為 2 的 48 次方。
當(dāng)然,服務(wù)端最大并發(fā) TCP 連接數(shù)遠不能達到理論上限。首先主要是文件描述符限制,按照上面的原理,Socket 都是文件,所以首先要通過 ulimit 配置文件描述符的數(shù)目;另一個限制是內(nèi)存,按上面的數(shù)據(jù)結(jié)構(gòu),每個 TCP 連接都要占用一定內(nèi)存,操作系統(tǒng)是有限的。
所以,作為老板,在資源有限的情況下,要想接更多的項目,就需要降低每個項目消耗的資源數(shù)目。
方式一:將項目外包給其他公司(多進程方式)
方式二:將項目轉(zhuǎn)包給獨立的項目組(多線程方式)
上面這種方式你應(yīng)該也能發(fā)現(xiàn)問題,如果每次接一個項目,都申請一個新公司,然后干完了,就注銷掉這個公司,實在是太麻煩了。畢竟一個新公司要有新公司的資產(chǎn),有新的辦公家具,每次都買了再賣,不劃算。
于是你應(yīng)該想到了,我們可以使用線程。相比于進程來講,這樣要輕量級的多。如果創(chuàng)建進程相當(dāng)于成立新公司,購買新辦公家具,而創(chuàng)建線程,就相當(dāng)于在同一個公司成立項目組。一個項目做完了,那這個項目組就可以解散,組成另外的項目組,辦公家具可以共用。
上面基于進程或者線程模型的,其實還是有問題的。新到來一個 TCP 連接,就需要分配一個進程或者線程。一臺機器無法創(chuàng)建很多進程或者線程。有個C10K,它的意思是一臺機器要維護 1 萬個連接,就要創(chuàng)建 1 萬個進程或者線程,那么操作系統(tǒng)是無法承受的。如果維持 1 億用戶在線需要 10 萬臺服務(wù)器,成本也太高了。
方式三:一個項目組支撐多個項目(IO 多路復(fù)用,一個線程維護多個 Socket)
當(dāng)然,一個項目組可以看多個項目了。這個時候,每個項目組都應(yīng)該有個項目進度墻,將自己組看的項目列在那里,然后每天通過項目墻看每個項目的進度,一旦某個項目有了進展,就派人去盯一下。
由于 Socket 是文件描述符,因而某個線程盯的所有的 Socket,都放在一個文件描述符集合 fd_set 中,這就是項目進度墻,然后調(diào)用 select 函數(shù)來監(jiān)聽文件描述符集合是否有變化。一旦有變化,就會依次查看每個文件描述符。那些發(fā)生變化的文件描述符在 fd_set 對應(yīng)的位都設(shè)為 1,表示 Socket 可讀或者可寫,從而可以進行讀寫操作,然后再調(diào)用 select,接著盯著下一輪的變化。。
方式四:一個項目組支撐多個項目(IO 多路復(fù)用,從“派人盯著”到“有事通知”)
上面 select 函數(shù)還是有問題的,因為每次 Socket 所在的文件描述符集合中有 Socket 發(fā)生變化的時候,都需要通過輪詢的方式,也就是需要將全部項目都過一遍的方式來查看進度,這大大影響了一個項目組能夠支撐的最大的項目數(shù)量。因而使用 select,能夠同時盯的項目數(shù)量由 FD_SETSIZE 限制。
如果改成事件通知的方式,情況就會好很多,項目組不需要通過輪詢挨個盯著這些項目,而是當(dāng)項目進度發(fā)生變化的時候,主動通知項目組,然后項目組再根據(jù)項目進展情況做相應(yīng)的操作。
能完成這件事情的函數(shù)叫 epoll,它在內(nèi)核中的實現(xiàn)不是通過輪詢的方式,而是通過注冊 callback 函數(shù)的方式,當(dāng)某個文件描述符發(fā)送變化的時候,就會主動通知。
假設(shè)進程打開了 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 添加一個 Socket 的時候,其實是加入這個紅黑樹,同時紅黑樹里面的節(jié)點指向一個結(jié)構(gòu),將這個結(jié)構(gòu)掛在被監(jiān)聽的 Socket 的事件列表中。當(dāng)一個 Socket 來了一個事件的時候,可以從這個列表中得到 epoll 對象,并調(diào)用 call back 通知它。
這種通知方式使得監(jiān)聽的 Socket 數(shù)據(jù)增加的時候,效率不會大幅度降低,能夠同時監(jiān)聽的 Socket 的數(shù)目也非常的多了。上限就為系統(tǒng)定義的、進程打開的最大文件描述符個數(shù)。因而,epoll 被稱為解決 C10K 問題的利器
名詞解釋
文件描述符限制是指一個操作系統(tǒng)中能夠同時打開的文件和網(wǎng)絡(luò)連接的數(shù)量上限。為了理解這個概念,我們可以先了解什么是文件描述符,然后解釋為什么它會限制并發(fā) TCP 連接數(shù)。
什么是文件描述符?
在操作系統(tǒng)中,每個文件(包括網(wǎng)絡(luò)連接)在打開時,都會被分配一個唯一的標(biāo)識符,這個標(biāo)識符就叫做文件描述符(File Descriptor,簡稱 FD)。文件描述符是一個非負整數(shù),用來引用一個打開的文件或網(wǎng)絡(luò)連接。
- 文件:任何類型的文件,比如文本文件、圖片文件等。
- 網(wǎng)絡(luò)連接:TCP 連接、UDP 連接等。
- 其他資源:如管道、設(shè)備等。
文件描述符限制
操作系統(tǒng)對每個進程能夠同時打開的文件描述符數(shù)量有限制,這是出于資源管理和安全的考慮。這個限制通常可以分為兩個層次:
- 軟限制:用戶或進程可以更改的限制,一般默認較小,但可以通過修改系統(tǒng)設(shè)置或在程序中動態(tài)調(diào)整。
- 硬限制:系統(tǒng)級的限制,只有管理員可以更改,通常比軟限制要大。
舉個例子
假設(shè)你在編寫一個服務(wù)器程序,這個服務(wù)器需要處理很多客戶端的連接,每個連接對應(yīng)一個文件描述符。
- 默認限制:操作系統(tǒng)可能默認限制每個進程只能打開 1024 個文件描述符。如果你有超過 1024 個客戶端同時連接到服務(wù)器,新的連接將無法建立,因為文件描述符已經(jīng)用完。
- 調(diào)整限制:你可以通過修改系統(tǒng)配置來增加文件描述符的限制。例如,在 Linux 系統(tǒng)中,你可以通過修改
/etc/security/limits.conf
文件或使用ulimit
命令來調(diào)整這個限制。
為什么文件描述符限制會影響并發(fā)連接數(shù)?
每個 TCP 連接在服務(wù)器端都需要一個文件描述符來表示和管理。如果文件描述符用完了,服務(wù)器將無法接受新的連接,即使硬件和其他資源還能夠處理更多的連接。這就導(dǎo)致了并發(fā) TCP 連接數(shù)遠不能達到理論上的上限。
應(yīng)用層協(xié)議
二、HTTP協(xié)議:看個新聞原來這么麻煩
HTTP 是基于 TCP 協(xié)議的,要先建立 TCP 連接
建立了連接以后,瀏覽器就要發(fā)送 HTTP 的請求。
HTTP 請求的創(chuàng)建
第一部分:請求行
GET POST PUT DELETE
POST 往往是用來創(chuàng)建一個資源的,而 PUT 往往是用來修改一個資源的。
第二部分:首部字段
例如,Accept-Charset,表示客戶端可以接受的字符集。防止傳過來的是另外的字符集,從而導(dǎo)致出現(xiàn)亂碼。
再如,Content-Type是指正文的格式。例如,我們進行 POST 的請求,如果正文是 JSON,那么我們就應(yīng)該將這個值設(shè)置為 JSON。
在 HTTP 協(xié)議中,Cache-Control
和 If-Modified-Since
是用于控制緩存行為和條件請求的頭字段。讓我們通俗易懂地解釋它們的作用和工作方式。
Cache-Control 頭字段用于指定緩存機制的指令,這些指令告訴瀏覽器和中間緩存服務(wù)器如何緩存 HTTP 響應(yīng)。它可以幫助提高網(wǎng)站性能和減少帶寬消耗。
常見指令
-
public:響應(yīng)可以被任何緩存(包括瀏覽器、代理服務(wù)器等)緩存。
- 例子:
Cache-Control: public
- 例子:
-
private:響應(yīng)只能被用戶的瀏覽器緩存,不能被共享緩存(如代理服務(wù)器)緩存。
- 例子:
Cache-Control: private
- 例子:
-
no-cache:緩存可以存儲響應(yīng),但在使用前必須先驗證其有效性(向服務(wù)器發(fā)送請求確認)。
- 例子:
Cache-Control: no-cache
- 例子:
-
no-store:不允許緩存響應(yīng),所有內(nèi)容每次都必須從服務(wù)器獲取。
- 例子:
Cache-Control: no-store
- 例子:
-
max-age:指定響應(yīng)在緩存中可以保存的最大時間(以秒為單位),在此時間內(nèi)緩存內(nèi)容被認為是新鮮的。
- 例子:
Cache-Control: max-age=3600
(緩存內(nèi)容在1小時內(nèi)有效)
- 例子:
場景:用戶訪問網(wǎng)頁
-
第一次訪問:
- 用戶瀏覽器向服務(wù)器請求網(wǎng)頁。
- 服務(wù)器返回網(wǎng)頁內(nèi)容,并在響應(yīng)頭中包含
Cache-Control: max-age=3600
和Last-Modified
頭字段。 - 瀏覽器將網(wǎng)頁緩存1小時。
-
在1小時內(nèi)再次訪問:
- 瀏覽器檢查緩存,發(fā)現(xiàn)緩存仍然有效(未超過
max-age
)。 - 瀏覽器直接從緩存中加載網(wǎng)頁,無需向服務(wù)器發(fā)送請求。
- 瀏覽器檢查緩存,發(fā)現(xiàn)緩存仍然有效(未超過
-
超過1小時再次訪問:
- 瀏覽器向服務(wù)器發(fā)送請求,包含If-Modified-Since頭字段,指示上次接收到的
Last-Modified
時間。 - 服務(wù)器檢查資源是否自該時間以來有修改:
- 如果沒有修改,返回
304 Not Modified
,瀏覽器使用緩存內(nèi)容。 - 如果有修改,返回新的網(wǎng)頁內(nèi)容和新的
Last-Modified
時間,瀏覽器更新緩存。
- 如果沒有修改,返回
- 瀏覽器向服務(wù)器發(fā)送請求,包含If-Modified-Since頭字段,指示上次接收到的
HTTP 請求的發(fā)送
就是 TCP 傳輸
HTTP 2.0
HTTP/2 和 HTTP/1.1 是兩個版本的超文本傳輸協(xié)議,它們有許多不同之處,主要目的是提高性能和效率。以下是 HTTP/2 和 HTTP/1.1 的主要區(qū)別,通俗易懂地解釋這些技術(shù)細節(jié):
1. 多路復(fù)用
- HTTP/1.1:每個請求-響應(yīng)對都需要一個獨立的 TCP 連接。這意味著如果一個網(wǎng)頁上有多個資源(如圖片、CSS 文件、JavaScript 文件等),每個資源的請求通常需要單獨的連接,導(dǎo)致了“隊頭阻塞”(Head-of-Line Blocking)問題:一個請求阻塞了,后續(xù)請求也無法進行。
- HTTP/2:引入了多路復(fù)用技術(shù),多個請求和響應(yīng)可以在一個單一的 TCP 連接中同時進行。這樣可以有效地利用網(wǎng)絡(luò)資源,減少延遲。
2. 二進制分幀
- HTTP/1.1:使用純文本格式來傳輸數(shù)據(jù),包括請求和響應(yīng)頭部。這種格式在解析時效率較低。
- HTTP/2:使用二進制分幀層(Binary Framing Layer),將所有傳輸?shù)男畔?#xff08;頭部和數(shù)據(jù))編碼為二進制格式。這種方式更高效、解析更快,并且更容易實現(xiàn)多路復(fù)用。
3. 頭部壓縮
- HTTP/1.1:每次請求都會攜帶完整的頭部信息,頭部信息往往很大且包含重復(fù)的內(nèi)容,浪費了帶寬。
- HTTP/2:使用 HPACK 壓縮算法對頭部信息進行壓縮,大大減少了頭部的大小和冗余信息,提高了傳輸效率。
4. 服務(wù)器推送
- HTTP/1.1:只有客戶端可以主動請求資源,服務(wù)器只能被動響應(yīng)。
- HTTP/2:引入了服務(wù)器推送功能,服務(wù)器可以在客戶端請求某個資源時,主動推送其他相關(guān)資源到客戶端,這樣客戶端就不需要再單獨請求這些資源了。例如,當(dāng)客戶端請求一個 HTML 頁面時,服務(wù)器可以提前推送相關(guān)的 CSS 和 JavaScript 文件。
5. 流量控制
- HTTP/1.1:沒有針對流量控制的機制,所有請求-響應(yīng)對共享帶寬,可能導(dǎo)致性能不穩(wěn)定。
- HTTP/2:引入了流量控制機制,可以更好地管理和分配帶寬,確保各個請求的傳輸速度和效率。
6. 優(yōu)先級和依賴關(guān)系
- HTTP/1.1:沒有內(nèi)置的請求優(yōu)先級機制,所有請求的處理順序主要取決于到達服務(wù)器的順序。
- HTTP/2:允許客戶端為每個請求分配優(yōu)先級,并建立依賴關(guān)系,使得重要的資源可以優(yōu)先傳輸,優(yōu)化了頁面加載順序和速度。
QUIC
盡管 HTTP/2 引入了多路復(fù)用技術(shù),使得多個流可以在一個 TCP 連接上并行傳輸,但由于底層使用的是 TCP 協(xié)議,TCP 必須保證數(shù)據(jù)包按順序和完整性傳輸。如果某個數(shù)據(jù)包出現(xiàn)問題,整個連接上的所有數(shù)據(jù)傳輸都會被阻塞,直到問題數(shù)據(jù)包被正確重傳和接收。這就意味著,即使在 HTTP/2 中,某個流的數(shù)據(jù)包出現(xiàn)問題,其他流的數(shù)據(jù)傳輸也會受到影響,無法完全避免隊頭阻塞的問題
于是,就又到了從 TCP 切換到 UDP。這就是 Google 的 QUIC 協(xié)議
機制一:自定義連接機制
我們都知道,一條 TCP 連接是由四元組標(biāo)識的,分別是源 IP、源端口、目的 IP、目的端口。一旦一個元素發(fā)生變化時,就需要斷開重連,重新連接。在移動互聯(lián)情況下,當(dāng)手機信號不穩(wěn)定或者在 WIFI 和 移動網(wǎng)絡(luò)切換時,都會導(dǎo)致重連,從而進行再次的三次握手,導(dǎo)致一定的時延。
這在 TCP 是沒有辦法的,但是基于 UDP,就可以在 QUIC 自己的邏輯里面維護連接的機制,不再以四元組標(biāo)識,而是以一個 64 位的隨機數(shù)作為 ID 來標(biāo)識,而且 UDP 是無連接的,所以當(dāng) IP 或者端口變化的時候,只要 ID 不變,就不需要重新建立連接。