成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

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

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

[[428508]]

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

你好,我是yes。

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

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

不過在此之前,我先來說說什么叫 I/O。

I/O到底是什么?

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

那輸入輸出啥呢?

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

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

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

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

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

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

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

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

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

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

創(chuàng)建 socket

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

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

這個 protocol 通常可以設(shè)為 0 ,因為由前面兩個參數(shù)可以推斷出所要使用的協(xié)議。

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

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

bind

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

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

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

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

listen

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

int listen(int sockfd, int backlog);

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

這個 backlog 我查閱資料的時候,看到了三種解釋:

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

解釋下什么叫半連接

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

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

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

而 Linux 則使用兩個隊列分別存儲已完成連接和半連接,且 backlog 僅為已完成連接的隊列大小

accept

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

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

這時候,我們就需要從已完成連接隊列中拿到連接進行處理,這個拿取動作就由 accpet 來完成。

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

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

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

至此服務(wù)端的通信流程暫告一段落,我們再看看客戶端的操作。

connect

客戶端也需要創(chuàng)建一個 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),此時就代表三次握手完畢。

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

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

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

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

可以看到這里的兩個阻塞點:

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

read、write

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

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

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

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

因為我們用的是 TCP 協(xié)議,TCP 協(xié)議需要保證數(shù)據(jù)可靠地、有序地傳輸,并且給予端與端之間的流量控制。

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

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

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

最后

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

因為建連和通信涉及到的 accept、connect、read、write 這幾個方法都可能會發(fā)生阻塞。

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

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

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

優(yōu)化唄!

所以后來就弄了個非阻塞套接字,然后 I/O多路復(fù)用、信號驅(qū)動I/O、異步I/O。

下篇我們就來好好盤盤,這幾種 I/O 模型!

 

責(zé)任編輯:武曉燕 來源: yes的練級攻略
相關(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ù)庫

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++編程語言

2024-06-19 10:26:36

非阻塞IO客戶端

2025-05-15 04:00:55

2013-05-28 10:08:41

IO輸出
點贊
收藏

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

主站蜘蛛池模板: 91免费版在线观看 | 日韩欧美成人一区二区三区 | 国产精品1区2区3区 国产在线观看一区 | 精品欧美一区二区精品久久久 | 日本精品一区二区三区视频 | 毛片电影| 国产不卡在线 | 久久婷婷麻豆国产91天堂 | 99久久久久久99国产精品免 | 国产三级电影网站 | 国产一区二区三区在线视频 | 青青久久久 | 成人福利在线 | 国产成人网 | 欧美亚洲视频在线观看 | 国产精品亚洲精品 | 国产免费色| 在线观看日本高清二区 | 亚洲一区视频在线 | 91精品国产综合久久久久 | 亚洲3p | 日韩在线不卡 | 天堂综合网 | 国产精品久久久久久妇女 | 天天操夜夜拍 | 欧美一区二区在线 | 日本精品一区二区三区在线观看视频 | 日本精品视频在线 | 亚洲 欧美 日韩 在线 | jlzzjlzz欧美大全 | 超碰婷婷| 欧美一区二区三区四区视频 | 精品无码三级在线观看视频 | 成人小视频在线免费观看 | 久久久久国产一区二区三区 | 伊人久久国产 | 欧美日韩在线播放 | 亚洲精品久久久久久久久久久久久 | 成人av免费在线观看 | 在线视频日韩 | 国产精品久久久久久久午夜片 |