偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

為什么網(wǎng)絡(luò) I/O 會(huì)被阻塞?

存儲(chǔ) 存儲(chǔ)軟件
最近打算輸出 Netty 相關(guān)的文章,但要深入學(xué)習(xí) Netty 這個(gè)底層通信框架,網(wǎng)絡(luò)相關(guān)知識(shí)點(diǎn)不可或缺。所以我打算先寫一些前置知識(shí)點(diǎn),對(duì)齊一下認(rèn)識(shí),便于之后對(duì) Netty 的理解。

[[428508]]

本文轉(zhuǎn)載自微信公眾號(hào)「yes的練級(jí)攻略」,作者是Yes呀。轉(zhuǎn)載本文請(qǐng)聯(lián)系yes的練級(jí)攻略公眾號(hào)。

你好,我是yes。

最近打算輸出 Netty 相關(guān)的文章,但要深入學(xué)習(xí) Netty 這個(gè)底層通信框架,網(wǎng)絡(luò)相關(guān)知識(shí)點(diǎn)不可或缺。所以我打算先寫一些前置知識(shí)點(diǎn),對(duì)齊一下認(rèn)識(shí),便于之后對(duì) Netty 的理解。

我們應(yīng)該都知道 socket(套接字),你可以認(rèn)為我們的通信都要基于這個(gè)玩意,而常說(shuō)的網(wǎng)絡(luò)通信又分為 TCP 與 UDP 兩種,下面我會(huì)以 TCP 通信為例來(lái)闡述下 socket 的通信流程。

不過(guò)在此之前,我先來(lái)說(shuō)說(shuō)什么叫 I/O。

I/O到底是什么?

I/O 其實(shí)就是 input 和 output 的縮寫,即輸入/輸出。

那輸入輸出啥呢?

比如我們用鍵盤來(lái)敲代碼其實(shí)就是輸入,那顯示器顯示圖案就是輸出,這其實(shí)就是 I/O。

而我們時(shí)常關(guān)心的磁盤 I/O 指的是硬盤和內(nèi)存之間的輸入輸出。

讀取本地文件的時(shí)候,要將磁盤的數(shù)據(jù)拷貝到內(nèi)存中,修改本地文件的時(shí)候,需要把修改后的數(shù)據(jù)拷貝到磁盤中。

網(wǎng)絡(luò) I/O 指的是網(wǎng)卡與內(nèi)存之間的輸入輸出。

當(dāng)網(wǎng)絡(luò)上的數(shù)據(jù)到來(lái)時(shí),網(wǎng)卡需要將數(shù)據(jù)拷貝到內(nèi)存中。當(dāng)要發(fā)送數(shù)據(jù)給網(wǎng)絡(luò)上的其他人時(shí),需要將數(shù)據(jù)從內(nèi)存拷貝到網(wǎng)卡里。

那為什么都要跟內(nèi)存交互呢?

我們的指令最終是由 CPU 執(zhí)行的,究其原因是 CPU 與內(nèi)存交互的速度遠(yuǎn)高于 CPU 和這些外部設(shè)備直接交互的速度。

因此都是和內(nèi)存交互,當(dāng)然假設(shè)沒(méi)有內(nèi)存,讓 CPU 直接和外部設(shè)備交互,那也算 I/O。

總結(jié)下:I/O 就是指內(nèi)存與外部設(shè)備之間的交互(數(shù)據(jù)拷貝)。

好了,明確什么是 I/O 之后,讓我們來(lái)揭一揭 socket 通信內(nèi)幕~

創(chuàng)建 socket

首先服務(wù)端需要先創(chuàng)建一個(gè) socket。在 Linux 中一切都是文件,那么創(chuàng)建的 socket 也是文件,每個(gè)文件都有一個(gè)整型的文件描述符(fd)來(lái)指代這個(gè)文件。

  1. int socket(int domain, int type, int protocol); 
  • domain:這個(gè)參數(shù)用于選擇通信的協(xié)議族,比如選擇 IPv4 通信,還是 IPv6 通信等等
  • type:選擇套接字類型,可選字節(jié)流套接字、數(shù)據(jù)報(bào)套接字等等。
  • protocol:指定使用的協(xié)議。

這個(gè) protocol 通??梢栽O(shè)為 0 ,因?yàn)橛汕懊鎯蓚€(gè)參數(shù)可以推斷出所要使用的協(xié)議。

比如socket(AF_INET, SOCK_STREAM, 0);,表明使用 IPv4 ,且使用字節(jié)流套接字,可以判斷使用的協(xié)議為 TCP 協(xié)議。

這個(gè)方法的返回值為 int ,其實(shí)就是創(chuàng)建的 socket 的 fd。

bind

現(xiàn)在我們已經(jīng)創(chuàng)建了一個(gè) socket,但現(xiàn)在還沒(méi)有地址指向這個(gè) socket。

眾所周知,服務(wù)器應(yīng)用需要指明 IP 和端口,這樣客戶端才好找上門來(lái)要服務(wù),所以此時(shí)我們需要指定一個(gè)地址和端口來(lái)與這個(gè) socket 綁定一下。

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

參數(shù)里的 sockfd 就是我們創(chuàng)建的 socket 的文件描述符,執(zhí)行了 bind 參數(shù)之后我們的 socket 距離可以被訪問(wèn)又更近了一步。

listen

執(zhí)行了 socket、bind 之后,此時(shí)的 socket 還處于 closed 的狀態(tài),也就是不對(duì)外監(jiān)聽的,然后我們需要調(diào)用 listen 方法,讓 socket 進(jìn)入被動(dòng)監(jiān)聽狀態(tài),這樣的 socket 才能夠監(jiān)聽到客戶端的連接請(qǐng)求。

int listen(int sockfd, int backlog);

傳入創(chuàng)建的 socket 的 fd,并且指明一下 backlog 的大小。

這個(gè) backlog 我查閱資料的時(shí)候,看到了三種解釋:

  1. socket 有一個(gè)隊(duì)列,同時(shí)存放已完成的連接和半連接,backlog為這個(gè)隊(duì)列的大小。
  2. socket 有兩個(gè)隊(duì)列,分別為已完成的連接隊(duì)列和半連接隊(duì)列,backlog為這個(gè)兩個(gè)隊(duì)列的大小之和。
  3. socket 有兩個(gè)隊(duì)列,分別為已完成的連接隊(duì)列和半連接隊(duì)列,backlog僅為已完成的連接隊(duì)列大小。

解釋下什么叫半連接

我們都知道 TCP 建立連接需要三次握手,當(dāng)接收方收到請(qǐng)求方的建連請(qǐng)求后會(huì)返回 ack,此時(shí)這個(gè)連接在接收方就處于半連接狀態(tài),當(dāng)接收方再收到請(qǐng)求方的 ack 時(shí),這個(gè)連接就處于已完成狀態(tài):

所以上面討論的就是這兩種狀態(tài)的連接的存放問(wèn)題。

我查閱資料看到,基于 BSD 派生的系統(tǒng)的實(shí)現(xiàn)是使用的一個(gè)隊(duì)列來(lái)同時(shí)存放這兩種狀態(tài)的連接, backlog 參數(shù)即為這個(gè)隊(duì)列的大小。

而 Linux 則使用兩個(gè)隊(duì)列分別存儲(chǔ)已完成連接和半連接,且 backlog 僅為已完成連接的隊(duì)列大小

accept

現(xiàn)在我們已經(jīng)初始化好監(jiān)聽套接字了,此時(shí)會(huì)有客戶端連上來(lái),然后我們需要處理這些已經(jīng)完成建連的連接。

從上面的分析我們可以得知,三次握手完成后的連接會(huì)被加入到已完成連接隊(duì)列中去。

這時(shí)候,我們就需要從已完成連接隊(duì)列中拿到連接進(jìn)行處理,這個(gè)拿取動(dòng)作就由 accpet 來(lái)完成。

  1. int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 

這個(gè)方法返回的 int 值就是拿到的已完成連接的 socket 的文件描述符,之后操作這個(gè) socket 就可以進(jìn)行通信了。

如果已完成連接隊(duì)列沒(méi)有連接可以取,那么調(diào)用 accept 的線程會(huì)阻塞等待。

至此服務(wù)端的通信流程暫告一段落,我們?cè)倏纯纯蛻舳说牟僮鳌?/p>

connect

客戶端也需要?jiǎng)?chuàng)建一個(gè) socket,也就是調(diào)用 socket(),這里就不贅述了,我們直接開始建連操作。

客戶端需要與服務(wù)端建立連接,在 TCP 協(xié)議下開始經(jīng)典的三次握手操作,再看一下上面畫的圖:

客戶端創(chuàng)建完 socket 并調(diào)用 connect 之后,連接就處于 SYN_SEND 狀態(tài),當(dāng)收到服務(wù)端的 SYN+ACK 之后,連接就變?yōu)? ESTABLISHED 狀態(tài),此時(shí)就代表三次握手完畢。

  1. int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 

調(diào)用connect需要指定遠(yuǎn)程的地址和端口進(jìn)行建連,三次握手完畢之后就可以開始通信了。

客戶端這邊不需要調(diào)用 bind 操作,默認(rèn)會(huì)選擇源 IP 和隨機(jī)端口。

用一幅圖來(lái)小結(jié)一下建連的操作:

可以看到這里的兩個(gè)阻塞點(diǎn):

  • connect:需要阻塞等待三次握手的完成。
  • accept:需要等待可用的已完成的連接,如果已完成連接隊(duì)列為空,則被阻塞。

read、write

連接建立成功之后,就能開始發(fā)送和接收消息了,我們來(lái)看一下

read 為讀數(shù)據(jù),從服務(wù)端來(lái)看就是等待客戶端的請(qǐng)求,如果客戶端不發(fā)請(qǐng)求,那么調(diào)用 read 會(huì)處于阻塞等待狀態(tài),沒(méi)有數(shù)據(jù)可以讀,這個(gè)應(yīng)該很好理解。

write 為寫數(shù)據(jù),一般而言服務(wù)端接受客戶端的請(qǐng)求之后,會(huì)進(jìn)行一些邏輯處理,然后再把結(jié)果返回給客戶端,這個(gè)寫入也可能會(huì)被阻塞。

這里可能有人就會(huì)問(wèn) read 讀不到數(shù)據(jù)阻塞等待可以理解,write 為什么還要阻塞,有數(shù)據(jù)不就直接發(fā)了嗎?

因?yàn)槲覀冇玫氖?TCP 協(xié)議,TCP 協(xié)議需要保證數(shù)據(jù)可靠地、有序地傳輸,并且給予端與端之間的流量控制。

所以說(shuō)發(fā)送不是直接發(fā)出去,它有個(gè)發(fā)送緩沖區(qū),我們需要把數(shù)據(jù)先拷貝到 TCP 的發(fā)送緩沖區(qū),由 TCP 自行控制發(fā)送的時(shí)間和邏輯,有可能還有重傳什么的。

如果我們發(fā)的過(guò)快,導(dǎo)致接收方處理不過(guò)來(lái),那么接收方就會(huì)通過(guò) TCP 協(xié)議告知:別發(fā)了!忙不過(guò)來(lái)了。發(fā)送緩存區(qū)是有大小限制的,由于無(wú)法發(fā)送,還不斷調(diào)用 write 那么緩存區(qū)就滿了,滿了就不然你 write 了,所以 write 也會(huì)發(fā)生阻塞。

綜上,read 和 write 都會(huì)發(fā)生阻塞。

最后

為什么網(wǎng)絡(luò) I/O 會(huì)被阻塞?

因?yàn)榻ㄟB和通信涉及到的 accept、connect、read、write 這幾個(gè)方法都可能會(huì)發(fā)生阻塞。

阻塞會(huì)占用當(dāng)前執(zhí)行的線程,使之不能進(jìn)行其他操作,并且頻繁阻塞喚醒切換上下文也會(huì)導(dǎo)致性能的下降。

由于阻塞的緣故,起初的解決的方案就是建立多個(gè)線程,但是隨著互聯(lián)網(wǎng)的發(fā)展,用戶激增,連接數(shù)也隨著激增,需要建立的線程數(shù)也隨著一起增加,到后來(lái)就產(chǎn)生了 C10K 問(wèn)題。

服務(wù)端頂不住了呀,咋辦?

優(yōu)化唄!

所以后來(lái)就弄了個(gè)非阻塞套接字,然后 I/O多路復(fù)用、信號(hào)驅(qū)動(dòng)I/O、異步I/O。

下篇我們就來(lái)好好盤盤,這幾種 I/O 模型!

 

責(zé)任編輯:武曉燕 來(lái)源: yes的練級(jí)攻略
相關(guān)推薦

2012-02-22 21:15:41

unixIO阻塞

2018-03-28 08:52:53

阻塞非阻塞I

2024-11-26 10:37:19

2018-03-13 09:34:36

Kubernetes容器系統(tǒng)

2020-01-15 08:42:16

TCP三次握手弱網(wǎng)絡(luò)

2020-04-01 15:30:19

TCPUDP服務(wù)器

2020-01-13 10:16:53

TCPUDP協(xié)議

2012-05-22 00:25:41

.NET

2023-07-31 08:55:01

Java NIO非阻塞阻塞

2020-11-10 22:53:54

oracle數(shù)據(jù)庫(kù)

2010-11-09 10:36:39

求職

2021-02-10 08:09:48

Netty網(wǎng)絡(luò)多路復(fù)用

2023-11-08 09:22:14

I/ORedis阻塞

2014-07-28 16:47:41

linux性能

2020-12-01 07:08:23

Linux網(wǎng)絡(luò)I

2020-07-06 14:16:22

Fastjson漏洞開源

2019-09-11 09:09:56

++ii++編程語(yǔ)言

2024-06-19 10:26:36

非阻塞IO客戶端

2013-05-28 10:08:41

IO輸出

2018-11-05 11:20:54

緩沖IO
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)