中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁(yè) > news >正文

網(wǎng)站建設(shè)圖片競(jìng)價(jià)

網(wǎng)站建設(shè)圖片,競(jìng)價(jià),通過(guò)社交網(wǎng)站來(lái)做招聘決定,網(wǎng)站建設(shè)目的背景怎么寫目錄 前言前置知識(shí)一、計(jì)算機(jī)網(wǎng)絡(luò)體系結(jié)構(gòu)二、TCP/IP協(xié)議族2.1 簡(jiǎn)介*2.2 TCP/IP網(wǎng)絡(luò)傳輸中的數(shù)據(jù)2.3 地址和端口號(hào)2.4 小總結(jié) 三、TCP/UDP特性3.1 TCP特性TCP 3次握手TCP 4次揮手TCP頭部結(jié)構(gòu)體 3.2 UDP特性 四、總結(jié) 課程內(nèi)容一、網(wǎng)絡(luò)通信編程基礎(chǔ)知識(shí)1.1 什么是Socket1.2 長(zhǎng)連…

目錄

  • 前言
  • 前置知識(shí)
    • 一、計(jì)算機(jī)網(wǎng)絡(luò)體系結(jié)構(gòu)
    • 二、TCP/IP協(xié)議族
      • 2.1 簡(jiǎn)介
      • *2.2 TCP/IP網(wǎng)絡(luò)傳輸中的數(shù)據(jù)
      • 2.3 地址和端口號(hào)
      • 2.4 小總結(jié)
    • 三、TCP/UDP特性
      • 3.1 TCP特性
        • TCP 3次握手
        • TCP 4次揮手
        • TCP頭部結(jié)構(gòu)體
      • 3.2 UDP特性
    • 四、總結(jié)
  • 課程內(nèi)容
    • 一、網(wǎng)絡(luò)通信編程基礎(chǔ)知識(shí)
      • 1.1 什么是Socket
      • 1.2 長(zhǎng)連接、短連接
      • 1.3 網(wǎng)絡(luò)編程與生活常識(shí)類比
    • 二、BIO
      • 2.1 BIO簡(jiǎn)介
      • 2.2 BIO結(jié)合多線程1:普通線程
      • 2.3 BIO結(jié)合多線程2:線程池
      • *2.4 小結(jié)
    • 三、NIO
      • 3.1 NIO簡(jiǎn)介
      • 3.2 與BIO的主要區(qū)別
      • 3.3 Java NIO沒(méi)引入多路復(fù)用器之前
      • 3.4 Java NIO + 多路復(fù)用
    • *四、拓展:epoll簡(jiǎn)介
  • 學(xué)習(xí)總結(jié)
  • 感謝

前言

我是有點(diǎn)怕網(wǎng)絡(luò)編程的,總有點(diǎn)【談網(wǎng)色變】的感覺(jué)。為了讓自己不再【談網(wǎng)色變】,所以我想過(guò)系統(tǒng)學(xué)習(xí)一下,然后再做個(gè)筆記這樣,加深一下理解。但是真要系統(tǒng)學(xué)習(xí),其實(shí)還是要花費(fèi)不少時(shí)間的,所以這里也只是簡(jiǎn)單的,盡可能地覆蓋一下,梳理一些我認(rèn)為比較迫切需要了解的網(wǎng)絡(luò)編程相關(guān)的知識(shí)。
其實(shí)單單網(wǎng)絡(luò)編程跟TCP/IP協(xié)議族就可以拿來(lái)寫一個(gè)獨(dú)立的筆記了,但是為了讓知識(shí)點(diǎn)更耦合一點(diǎn),我還是決定寫在一起。當(dāng)然這樣也有壞處,那就是知識(shí)點(diǎn)比較密集,然后導(dǎo)致筆記篇幅偏長(zhǎng),所以閱讀的朋友給點(diǎn)耐心。
另外,為了讓知識(shí)分層更加清晰一點(diǎn),基礎(chǔ)網(wǎng)絡(luò)編程跟TCP/IP那一塊我還是放在【前置知識(shí)】里面。

前置知識(shí)

一、計(jì)算機(jī)網(wǎng)絡(luò)體系結(jié)構(gòu)

OSI七層網(wǎng)絡(luò)模型:
我相信大家對(duì)OSI七層模型都不陌生,OSI全稱:開(kāi)放系統(tǒng)互連參考模型,是國(guó)際標(biāo)準(zhǔn)化組織(ISO)和國(guó)際電報(bào)電話咨詢委員會(huì)(CCITT)聯(lián)合制定的開(kāi)放系統(tǒng)互連參考模型,為開(kāi)放式互連信息系統(tǒng)提供了一種功能結(jié)構(gòu)的框架。其目的是為異種計(jì)算機(jī)互連提供一個(gè)共同的基礎(chǔ)和標(biāo)準(zhǔn)框架,并為保持相關(guān)標(biāo)準(zhǔn)的一致性和兼容性提供共同的參考。這里所說(shuō)的開(kāi)放系統(tǒng),實(shí)質(zhì)上指的是遵循 OSI 參考模型和相關(guān)協(xié)議能夠?qū)崿F(xiàn)互連的具有各種應(yīng)用目的的計(jì)算機(jī)系統(tǒng)。(PS:這是一個(gè)標(biāo)準(zhǔn)的,參考模型
它的整體模型圖如下:
在這里插入圖片描述

面試中可能比較??歼@一點(diǎn),建議死記硬背。個(gè)人背誦方式是:記每一層首字。即:應(yīng)表會(huì)傳網(wǎng)數(shù)物、應(yīng)表會(huì)傳網(wǎng)數(shù)物、應(yīng)表會(huì)傳網(wǎng)數(shù)物。剛開(kāi)始有點(diǎn)拗口,多念幾遍就記住了。

TCP/IP模型:
嚴(yán)格來(lái)說(shuō),OSI模型是一種比較復(fù)雜且學(xué)術(shù)化的【參考】模型,實(shí)際上我們說(shuō)的互聯(lián)網(wǎng)中實(shí)際使用的是TCP/IP模型(這是由工程師定義的。這個(gè)也是我們后面要著重講的內(nèi)容)。
TCP/IP模型在網(wǎng)上又叫五層模型。它跟7層網(wǎng)絡(luò)模型其實(shí)差不多,就是把7層的前3層合在一起表示了。模型圖如下:
在這里插入圖片描述

PS:網(wǎng)上也有一些人把最后2層【數(shù)據(jù)鏈路層】和【物理層】合在一起叫做【網(wǎng)絡(luò)接口層】,最后結(jié)論為:TCP/IP為4層網(wǎng)絡(luò)模型。大家知道有這個(gè)東西就好

小總結(jié):
其實(shí)無(wú)論什么模型,每一層的定義都是建立在低一層提供的服務(wù)上的,并且為高一層提供服務(wù)。大致來(lái)說(shuō),我們可以這么映射這個(gè)網(wǎng)絡(luò)模型:

物理層 <—對(duì)應(yīng)—> 網(wǎng)卡、路由器、交換機(jī)
數(shù)據(jù)鏈路層 <—對(duì)應(yīng)—> 網(wǎng)卡驅(qū)動(dòng)
網(wǎng)絡(luò)層、傳輸層 <—對(duì)應(yīng)—> 這一部分已經(jīng)被操作系統(tǒng)封裝了
應(yīng)用層 <—對(duì)應(yīng)—> 一些通用的網(wǎng)絡(luò)應(yīng)用程序,或者自己編寫的應(yīng)用程序

另外,我相信通過(guò)對(duì)比大家也看到了,OSI七層模型跟TCP/IP五層模型沒(méi)啥特別大區(qū)別的,記住其中一個(gè)模型就差不多了。而且,在編程過(guò)程中,其實(shí)我們通常只需要關(guān)注【應(yīng)用層】就好了,下面幾層的封裝,其實(shí)操作系統(tǒng)早就幫我們封裝好了。它就是:Socket。

二、TCP/IP協(xié)議族

2.1 簡(jiǎn)介

TCP/IP協(xié)議族,我希望大家將他拆分成三個(gè)部分來(lái)理解。

  • TCP/IP:Transmission Control Protocol/Internet Protocol 的簡(jiǎn)寫,中譯名為【傳輸控制協(xié)議/因特網(wǎng)互聯(lián)協(xié)議】,是 Internet 最基本的協(xié)議、Internet 國(guó)際互聯(lián)網(wǎng)絡(luò)的基礎(chǔ)。由網(wǎng)絡(luò)層的 IP 協(xié)議和傳輸層的 TCP 協(xié)議組成。協(xié)議采用了 5 層的層級(jí)結(jié)構(gòu)。然而在很多情況下,它是利用 IP 進(jìn)行通信時(shí)所必須用到的協(xié)議群的統(tǒng)稱也就是說(shuō),它其實(shí)是個(gè)協(xié)議家族,由很多個(gè)協(xié)議組成,并且是在不同的層,是互聯(lián)網(wǎng)的基礎(chǔ)通信架構(gòu)
  • 協(xié)議:協(xié)議是經(jīng)過(guò)談判、協(xié)商而制定的共同承認(rèn)、共同遵守的文件。就可以認(rèn)為是一種規(guī)范、標(biāo)準(zhǔn)
  • 族:說(shuō)明每一層都含有多個(gè)的意思

下圖是TCP/IP模型,以及對(duì)應(yīng)的常見(jiàn)協(xié)議族:
在這里插入圖片描述
橫向?qū)Ρ葓D:

OSI參考模型TCP/IP模型TCP/IP協(xié)議族
應(yīng)用層應(yīng)用層DNS、HTTP、FTP、Mysql、WebSocket
表示層
會(huì)話層
傳輸層傳輸層TCP、UDP
網(wǎng)絡(luò)層網(wǎng)絡(luò)層IP、ICMP、RIP、IGMP
數(shù)據(jù)鏈路層數(shù)據(jù)鏈路層ARP、RARP、IEEE802.3、CSMA/CD
物理層物理層FE自協(xié)商、Manchester、MLT-3、4A、PAM5

*2.2 TCP/IP網(wǎng)絡(luò)傳輸中的數(shù)據(jù)

每個(gè)分層中,都會(huì)對(duì)所發(fā)送的數(shù)據(jù)附加一個(gè)首部,在這個(gè)首部中包含了該層必要的信息,如發(fā)送的目標(biāo)地址以及協(xié)議相關(guān)信息(或者我們可以理解為,數(shù)據(jù)再每個(gè)分層流轉(zhuǎn)的時(shí)候都會(huì)包裹一層,根據(jù)協(xié)議標(biāo)準(zhǔn)附帶上當(dāng)前層的一些關(guān)鍵數(shù)據(jù)),這就是編程語(yǔ)言角度【協(xié)議】的本質(zhì)。通常,為協(xié)議提供的信息為包首部,所要發(fā)送的內(nèi)容為數(shù)據(jù)。在下一層的角度看,從上一層收到的包全部都被認(rèn)為是本層的數(shù)據(jù)。

網(wǎng)絡(luò)中傳輸?shù)臄?shù)據(jù)包由兩部分組成:【協(xié)議首部】+【上一層傳過(guò)來(lái)的數(shù)據(jù)】。協(xié)議首部結(jié)構(gòu)由協(xié)議的具體規(guī)范詳細(xì)定義。在數(shù)據(jù)包的首部,明確標(biāo)明了協(xié)議應(yīng)該如何讀取數(shù)據(jù)。反過(guò)來(lái)說(shuō),看到首部,也就能夠了解該協(xié)議必要的信息以及所要處理的數(shù)據(jù)。

舉個(gè)簡(jiǎn)單的例子。我們用用戶 A 發(fā)送,用戶 B 接受來(lái)說(shuō)說(shuō)明:
1)用戶A使用某應(yīng)用程序在【應(yīng)用層】處理。首先,應(yīng)用程序會(huì)根據(jù)自定義編碼處理產(chǎn)生的消息(比如最簡(jiǎn)單的json序列化),然后交給下面的【傳輸層】TCP協(xié)議處理
2)用戶A的【傳輸層】TCP處理。TCP根據(jù)應(yīng)用的指示,負(fù)責(zé)建立連接、發(fā)送數(shù)據(jù)以及斷開(kāi)連接。TCP 提供將應(yīng)用層發(fā)來(lái)的數(shù)據(jù)順利發(fā)送至對(duì)端的可靠傳輸。為了實(shí)現(xiàn)這一功能,需要將【應(yīng)用層數(shù)據(jù)】封裝為【報(bào)文段(segment)】并附加一個(gè) TCP 首部然后交給下面的【網(wǎng)絡(luò)層】IP協(xié)議處理
3)用戶A的【網(wǎng)絡(luò)層】IP模塊的處理。IP 將 TCP 傳過(guò)來(lái)的 TCP 首部和 TCP 數(shù)據(jù)合起來(lái)當(dāng)做自己的數(shù)據(jù),并在 TCP 首部的前端加上自己的 IP 首部生成 IP 數(shù)據(jù)報(bào)(datagram)然后交給下面的【數(shù)據(jù)鏈路層】
4)用戶A【數(shù)據(jù)鏈路層】處理。從 IP 傳過(guò)來(lái)的 IP 包對(duì)于數(shù)據(jù)鏈路層來(lái)說(shuō)就是數(shù)據(jù)。給這些數(shù)據(jù)附加上數(shù)據(jù)鏈路層首部封裝為鏈路層幀(frame),生成的鏈路層幀(frame)將通過(guò)物理層傳輸給接收端
5)用戶B【數(shù)據(jù)鏈路層】的處理。用戶 B 主機(jī)收到鏈路層幀(frame)后,首先從鏈路層幀(frame)首部找到 MAC 地址判斷
是否為發(fā)送給自己的包,若不是則丟棄數(shù)據(jù);如果是發(fā)送給自己的包,則從以太網(wǎng)包首部中的類型確定數(shù)據(jù)類型,再傳給相應(yīng)的模塊,如IP、ARP 等。這里的例子則是 IP
6)用戶B【網(wǎng)絡(luò)層】IP 模塊的處理。IP 模塊接收到 數(shù)據(jù)后也做類似的處理。從包首部中判斷此 IP 地址是否與自己的 IP 地址匹配,如果匹配則根據(jù)首部的協(xié)議類型將數(shù)據(jù)發(fā)送給對(duì)應(yīng)的模塊,如 TCP、UDP。這里的例子則是 TCP
7)用戶B【傳輸層】TCP 模塊的處理。在 TCP 模塊中,首先會(huì)計(jì)算一下校驗(yàn)和,判斷數(shù)據(jù)是否被破壞。然后檢查是否在按照序號(hào)接收數(shù)據(jù)(請(qǐng)記住這個(gè)步驟,后面推斷三次握手要用到)。最后檢查端口號(hào),確定具體的應(yīng)用程序。數(shù)據(jù)被完整地接收以后,會(huì)傳給由端口號(hào)識(shí)別的應(yīng)用程序
8)用戶B【應(yīng)用層】應(yīng)用程序的處理。接收端應(yīng)用程序會(huì)直接接收發(fā)送端發(fā)送的數(shù)據(jù)。通過(guò)解析數(shù)據(jù),展示相應(yīng)的內(nèi)容。
在這里插入圖片描述

2.3 地址和端口號(hào)

在我們網(wǎng)絡(luò)傳輸中,有兩個(gè)概念我們會(huì)經(jīng)常接觸,就是所謂的地址跟端口號(hào)。地址地址,我估計(jì)大部分人想到的是IP地址,卻忽略了Mac地址??墒怯腥酥?#xff0c;為什么計(jì)算機(jī)里面要設(shè)計(jì)【Mac地址 + IP地址 + 端口號(hào)】嗎?
我們知道,IP地址通常是用來(lái)唯一標(biāo)識(shí)一臺(tái)機(jī)器的,但我們同樣應(yīng)該知道,不同內(nèi)網(wǎng)中IP地址是可以重復(fù)的,那我怎么知道這個(gè)IP地址就是這臺(tái)機(jī)器呢?IP地址又是如何而來(lái)的呢?對(duì)的,就是Mac地址。

Mac地址
MAC 地址全稱叫做媒體訪問(wèn)控制地址,也稱為局域網(wǎng)地址(LAN Address),MAC 位址,以太網(wǎng)地址(Ethernet Address)或物理地址(Physical Address),它是由網(wǎng)絡(luò)設(shè)備制造商在生產(chǎn)時(shí)就寫在硬件內(nèi)部的(但是這不意味著就不能更改,只是比較麻煩,一般人也不會(huì)去改)。它作用于【數(shù)據(jù)鏈路層】上
MAC 地址共 48 位(6 個(gè)字節(jié))。前 24 位由 IEEE(電氣和電子工程師協(xié)會(huì))決定如何分配,后 24 位由實(shí)際生產(chǎn)該網(wǎng)絡(luò)設(shè)備的廠商自行制定。例如:FF:FF:FF:FF:FF:FF 或 FF-FF-FF-FF-FF-FF
在這里插入圖片描述
比如,下面是我的電腦Mac地址信息:打開(kāi)cmd,輸入:ipconfig
在這里插入圖片描述
在這里插入圖片描述

IP地址
IP地址是啥不用過(guò)多介紹了。IP 地址分為:IPv4 和 IPv6,我們通常說(shuō)的是IPv4。IP 地址是由 32 位的二進(jìn)制數(shù)組成,它們通常被分為 4 個(gè)【8 位二進(jìn)制數(shù)】,我們可以把它理解為 4 個(gè)字節(jié),格式表示為:A.B.C.D。其中,A,B,C,D 這四個(gè)英文字母表示為 0-255(2^8 - 1)的十進(jìn)制的整數(shù)。例如:192.168.1.1。它作用于【網(wǎng)絡(luò)層】上
那IP地址跟Mac地址有什么區(qū)別和聯(lián)系呢?
1)對(duì)于網(wǎng)絡(luò)中的一些設(shè)備,路由器或者是 PC 及而言, IP 地址的設(shè)計(jì)是出于拓?fù)湓O(shè)計(jì)出來(lái)的,只要在不重復(fù) IP 地址的情況下,它是可以隨意更改的;而 MAC 地址是根據(jù)生產(chǎn)廠商燒錄好的,它一般不能改動(dòng)的,一般來(lái)說(shuō),當(dāng)一臺(tái) PC 機(jī)的網(wǎng)卡壞了之后,更換了網(wǎng)卡之后 MAC 地址就會(huì)變了
2)在前面的介紹里面,它們最明顯的區(qū)別就是長(zhǎng)度不同, IP 地址的長(zhǎng)度為 32 位,而Mac地址為 48 位
3)它們的尋址協(xié)議層不同。IP 地址應(yīng)用于 OSI 模型的【網(wǎng)絡(luò)層】,而Mac地址應(yīng)用在OSI的【數(shù)據(jù)鏈路層】。 數(shù)據(jù)鏈路層協(xié)議可以使數(shù)據(jù)從一個(gè)節(jié)點(diǎn)傳遞到相同鏈路的另一個(gè)節(jié)點(diǎn)上(通過(guò) MAC 地址),而網(wǎng)絡(luò)層協(xié)議使數(shù)據(jù)可以從一個(gè)網(wǎng)絡(luò)傳遞到另一個(gè)網(wǎng)絡(luò)上( ARP 根據(jù)目的 IP 地址,找到中間節(jié)點(diǎn)的 MAC 地址,通過(guò)中間節(jié)點(diǎn)傳送,從而最終到達(dá)目的網(wǎng)絡(luò))
4)分配依據(jù)不同。 IP 地址的分配是基于我們自身定義的網(wǎng)絡(luò)拓?fù)?#xff0c; MAC 地址的分配是基于制造商

端口號(hào)
端口號(hào)的存在不難理解,畢竟無(wú)論是Mac地址還是IP地址都只能標(biāo)識(shí)一臺(tái)機(jī)器,那怎么標(biāo)識(shí)機(jī)器上特定應(yīng)用程序呢?就是端口號(hào),所以端口號(hào)又可以稱為:程序地址。它作用于【傳輸層】上
一臺(tái)計(jì)算機(jī)上同時(shí)可以運(yùn)行多個(gè)程序。傳輸層協(xié)議正是利用這些端口號(hào)識(shí)別本機(jī)中正在進(jìn)行通信的應(yīng)用程序,并準(zhǔn)確地將數(shù)據(jù)傳輸。
在這里插入圖片描述

2.4 小總結(jié)

1)TCP/IP每一層都有自己的協(xié)議,甚至有多種
2)協(xié)議是一種規(guī)范、約束。用Java WebMVC來(lái)說(shuō),這種規(guī)范就是:前后端約定了,每一次交互必傳手機(jī)號(hào)一樣
3)TCP/IP每個(gè)分層中,都會(huì)對(duì)所發(fā)送的數(shù)據(jù)附加一個(gè)首部,在這個(gè)首部中包含了該層必要的信息(其實(shí)就是【協(xié)議】的體現(xiàn))
4)在下一層的角度看,從上一層收到的包全部都被認(rèn)為是本層的數(shù)據(jù)

Q1:為什么端口號(hào)只有65535個(gè)?
答:因?yàn)樵赥CP、UDP協(xié)議報(bào)文的定義中,會(huì)分別有16位二進(jìn)制(合計(jì)32位4字節(jié))長(zhǎng)度來(lái)存儲(chǔ)元端口號(hào)和目的端口號(hào),所以端口個(gè)數(shù)只能是2^16=65536個(gè)。通常0號(hào)端口用來(lái)表示所有端口,所以實(shí)際可以用的端口號(hào)有65535個(gè)。(詳細(xì)的TCP、UPD頭部結(jié)構(gòu)體定義,見(jiàn)下面的筆記內(nèi)容【TCP頭部結(jié)構(gòu)體】)

Q2:客戶端的端口號(hào)是多少,如何確定?
答:是的,我們通過(guò)客戶端連接服務(wù)端的時(shí)候,也需要端口號(hào)的,這一點(diǎn)很容易被大家忽略。然后,客戶端的端口號(hào)雖然可以指定,但是通常不指定,因?yàn)闆](méi)什么必要。所以,都是由操作系統(tǒng)給自動(dòng)分配的。并且,分配的范圍通常都是大于10000(0-1023通常被知名服務(wù)占用了,比如FTP端口是21,HTTP是80)

三、TCP/UDP特性

在講述TCP/UDP特性之前,我們先給大家聲明一個(gè)概念。那就是網(wǎng)絡(luò)通信中的客戶端和服務(wù)端。聲明如下:

  • 客戶端:主動(dòng)發(fā)起連接的一方為客戶端
  • 服務(wù)端:接收連接請(qǐng)求的一方為服務(wù)端

【你】向【我】發(fā)起連接,肯定是因?yàn)橄霃摹疚摇窟@里獲得什么,所以【你】是客戶端,【我】是服務(wù)端(服務(wù)提供方),這很合理。其實(shí)這才是廣義上的客戶端、服務(wù)端定義。請(qǐng)大家不要狹隘地跟Web開(kāi)發(fā)中的客戶端、服務(wù)端混淆。

3.1 TCP特性

TCP(Transmission Control Protocol)是面向連接的【全雙工】通信協(xié)議,通過(guò)三次握手建立連接,然后才能開(kāi)始數(shù)據(jù)的讀寫,通訊完成時(shí)要拆除連接,由于 TCP 是面向連接的所以只能用于端到端的通訊。
我們知道,TCP相對(duì)于UDP最大的特點(diǎn),就是【可靠性】。那么為了實(shí)現(xiàn)【可靠性】,TCP做了什么呢?整體來(lái)說(shuō)大概是2點(diǎn):【超時(shí)重傳】和【消息應(yīng)答確認(rèn)機(jī)制】。那大家有思考過(guò),這兩點(diǎn)是如何被實(shí)現(xiàn)的嗎?其實(shí)這個(gè)實(shí)現(xiàn)方式也并不是特別重要,不過(guò)說(shuō)到這個(gè)東西,我突然間意識(shí)到一些東西。言歸正傳。

全雙工:即允許數(shù)據(jù)在兩個(gè)方向上傳輸
半雙工:雖然數(shù)據(jù)也能在兩個(gè)方向上傳輸,但是一次只能一個(gè)方向流動(dòng)
單工:只允許數(shù)據(jù)從一個(gè)方向上傳輸

超時(shí)重傳
超時(shí)重傳機(jī)制中最最重要的就是重傳超時(shí)(RTO,Retransmission TimeOut)的時(shí)間選擇(如何確定),由于網(wǎng)絡(luò)存在波動(dòng),所以,用一個(gè)固定的時(shí)間來(lái)測(cè)算是一種不經(jīng)濟(jì)的方式,TCP通過(guò)引入一個(gè)叫做RTT(round-trip time)的定義,根據(jù)最近的發(fā)包、收包時(shí)間動(dòng)態(tài)測(cè)算、修改重試時(shí)間

消息應(yīng)答機(jī)制
顧名思義,就是當(dāng)收到消息后,給消息一個(gè)應(yīng)答,告訴對(duì)方:我收到了。而對(duì)于沒(méi)收到消息應(yīng)答的一方來(lái)說(shuō),一段時(shí)間后沒(méi)收到應(yīng)答就【超時(shí)重傳】。那在復(fù)雜的網(wǎng)絡(luò)通信中,我可能每秒鐘都有好多條消息交互,我怎么知道這一條答復(fù),是對(duì)應(yīng)哪一條請(qǐng)求呢?對(duì)的,序號(hào)!這個(gè)序號(hào)也將在TCP三次握手中體現(xiàn)出來(lái)(現(xiàn)在回過(guò)頭來(lái)看,RocketMQ的事務(wù)可能就是參照這個(gè)設(shè)計(jì)的。當(dāng)然可能也不是,畢竟這個(gè)設(shè)計(jì)思路很多地方都有用過(guò))

TCP 3次握手

TCP 提供面向有連接的通信傳輸。【面向有連接】是指在數(shù)據(jù)通信開(kāi)始之前先做好兩端之間的準(zhǔn)備工作。
所謂三次握手是指建立一個(gè) TCP 連接時(shí)需要客戶端和服務(wù)器端總共發(fā)送三個(gè)包以確認(rèn)連接的建立。在 socket 編程中,這一過(guò)程由客戶端執(zhí)行 connect 來(lái)觸發(fā),所以網(wǎng)絡(luò)通信中,發(fā)起連接的一方我們稱為客戶端,接收連接的一方我們稱之為服務(wù)端。
下面是三次握手的流程圖:
在這里插入圖片描述
流程解讀:
1)第一次握手:客戶端將請(qǐng)求報(bào)文標(biāo)志位 SYN 置為 1(告訴服務(wù)端,此時(shí)是在同步),請(qǐng)求報(bào)文的 seq(Sequence Number)字段中填入一個(gè)隨機(jī)值 J(表示此時(shí)是同步到第幾條消息,這個(gè)很重要很重要很重要),并將該數(shù)據(jù)包發(fā)送給服務(wù)器端,等待服務(wù)端的應(yīng)答確認(rèn),緊接著客戶端進(jìn)入SYN_SENT狀態(tài)(SYN即同步的意思)
2)第二次握手:服務(wù)器端收到數(shù)據(jù)包后由請(qǐng)求報(bào)文標(biāo)志位SYN=1知道客戶端請(qǐng)求建立連接,服務(wù)器端將應(yīng)答報(bào)文標(biāo)志位 SYN 和 ACK 都置為1(ACK=1表示此條應(yīng)答是針對(duì)客戶端同步的應(yīng)答;SYN=1則表示我也想跟你請(qǐng)求同步了【畢竟全雙工啊同學(xué)們】,雙方都交換seq),應(yīng)答報(bào)文的 ack(Acknowledgment Number)字段中填入 ack=J+1,應(yīng)答報(bào)文的 seq 中填入一個(gè)隨機(jī)值 K(同上面的J一樣道理),并將該數(shù)據(jù)包發(fā)送給客戶端以確認(rèn)連接請(qǐng)求,服務(wù)器端進(jìn)入 SYN_RCVD 狀態(tài)
3)第三次握手:客戶端收到應(yīng)答報(bào)文后,檢查 ack 是否為 J+1,ACK 是否為 1,如果正確則將第三個(gè)報(bào)文標(biāo)志位 ACK 置為 1,ack=K+1,并將該數(shù)據(jù)包發(fā)送給服務(wù)器端,服務(wù)器端檢查 ack 是否為 K+1,ACK 是否為 1,如果正確則連接建立成功,客戶端和服務(wù)器端進(jìn)入ESTABLISHED 狀態(tài),完成三次握手,隨后客戶端與服務(wù)器端之間可以開(kāi)始傳輸數(shù)據(jù)了

TCP三次握手大家都聽(tīng)過(guò),但是根據(jù)我的經(jīng)驗(yàn),大部分人都【談網(wǎng)色變】,甚至搞不清楚三次握手傳的參數(shù)是什么意思。所以這邊再來(lái)個(gè)【小總結(jié)】,希望大家別看了流程就算了。如下:

  1. 三次握手中的SYN標(biāo)志,是同步的意思。SYN=1表示我要跟你同步了

Q1:為什么要同步?同步什么?
答:主要是同步序號(hào)seq,為什么要這個(gè)同步這個(gè)?你看看【消息應(yīng)答機(jī)制】提到的,在復(fù)雜的網(wǎng)絡(luò)通信中,如果沒(méi)有序號(hào),如何判斷,我這條應(yīng)答對(duì)應(yīng)的是你這條消息??沒(méi)有序號(hào),我怎么知道,哪條消息已經(jīng)接收到了,哪些沒(méi)有收到,哪些消息又沒(méi)有重復(fù)發(fā)送?


Q2:為什么三次握手而不是四次握手,甚至更多次?
答:因?yàn)槿尉蛪蛄?#xff0c;已經(jīng)完成原始數(shù)據(jù)同步了,后面再來(lái)就沒(méi)意義了。

  1. 三次握手的本質(zhì):即是通信雙方相互告知序列號(hào)起始值,并確認(rèn)對(duì)方已經(jīng)收到了序列號(hào)起始值
  2. 因?yàn)槭荰CP是全雙工,所以,雙方都要建立請(qǐng)求連接。這個(gè)在Java中的體現(xiàn)就是:兩邊都有一個(gè)socket對(duì)象
  3. 如果你跟我一樣是死記硬背的選手,請(qǐng)記住出現(xiàn)在圖中的每一個(gè)字段的意思,包括客戶端、服務(wù)端在握手過(guò)程中狀態(tài)的演變
TCP 4次揮手

其實(shí)這個(gè)相對(duì)來(lái)說(shuō)沒(méi)那么重要,就算是面試也很少考這個(gè),但終歸還是要了解一下的。
四次揮手即終止 TCP 連接,就是指斷開(kāi)一個(gè) TCP 連接時(shí),需要客戶端和服務(wù)端總共發(fā)送 4 個(gè)包以確認(rèn)連接的斷開(kāi)。在 socket 編程中,這一過(guò)程由客戶端或服務(wù)端任一方執(zhí)行 close來(lái)觸發(fā)。
跟握手一樣,由于TCP是全雙工的,因此,每個(gè)方向都必須要單獨(dú)進(jìn)行關(guān)閉。首先進(jìn)行關(guān)閉的一方將執(zhí)行主動(dòng)關(guān)閉,而另一方則執(zhí)行被動(dòng)關(guān)閉。(連接都是雙方都請(qǐng)求連接,關(guān)閉當(dāng)然是兩邊都要關(guān)閉)
流程圖如下:
在這里插入圖片描述
我相信你們?nèi)绻暾催^(guò)了【TCP3次握手】的話,這邊的流程圖難不倒你。FINfinish嘛。老樣子,注意狀態(tài)變化哦。這里可能比較男的地方就是最后一次【揮手】的時(shí)候,為什么要進(jìn)入TIME-WAIT狀態(tài)了。哎呀,都是因?yàn)椤救p工】啊,等待一下,確保那邊也關(guān)了比較好,另外也確保遲到的消息報(bào)文,能被當(dāng)前應(yīng)用程序正確的識(shí)別并且丟棄。

Q1:【被當(dāng)前應(yīng)用程序正確的識(shí)別并且丟棄】怎么理解?
答:我們前面介紹端口的時(shí)候說(shuō)過(guò),客戶端端口號(hào)可以不指定,由系統(tǒng)自動(dòng)分配的,所以很可能你剛關(guān)閉,就被新的其他應(yīng)用程序客戶端占取了這個(gè)端口,結(jié)果就是:如果沒(méi)有這個(gè)TIME-WAIT狀態(tài),就恰好再關(guān)閉之前有來(lái)自其他端的發(fā)送給上一個(gè)占用該端口的應(yīng)用程序。啊,現(xiàn)在這個(gè)應(yīng)用程序就懵了,咋回事啊這條消息?完全“看不懂”啥意思啊!!

TCP頭部結(jié)構(gòu)體

為什么要介紹這個(gè)結(jié)構(gòu)體?
首先,經(jīng)過(guò)了【2.2 TCP/IP網(wǎng)絡(luò)傳輸中的數(shù)據(jù)】的描述,你應(yīng)該知道,在網(wǎng)絡(luò)通信中,每個(gè)分層都會(huì)裹上一層屬于自己的數(shù)據(jù)(各種協(xié)議規(guī)定的),這就是編程語(yǔ)言角度【網(wǎng)絡(luò)協(xié)議】的本質(zhì)。
所以,TCP在傳輸層中給出的【協(xié)議】即如下所示:
在這里插入圖片描述
這個(gè)圖是不是很難看懂?給大家一段C語(yǔ)言偽代碼,一般人都看得懂啥意思。

// TCP頭(首)部信息
struct TCPHead
{int32 port;		// 端口號(hào),4字節(jié)int32 seq;		// 序號(hào),4字節(jié)int32 ack;		// 確認(rèn)號(hào),4字節(jié)int32 offset;	// 偏移數(shù)據(jù),4字節(jié)int32 crc;		// 校驗(yàn),4字節(jié)byte[] data;	// 協(xié)議數(shù)據(jù),邊長(zhǎng)
}

這里給出這該結(jié)構(gòu)體的定義跟模型圖,也是為了方便大家去放飛思想,想象一下其他各層的結(jié)構(gòu)體,以及,又會(huì)裹上什么信息呢。

再然后,通過(guò)一個(gè)簡(jiǎn)單的面試問(wèn)題,把之前的知識(shí)整合起來(lái)

Q1:一臺(tái)主機(jī)上只能保持最多 65535 個(gè) TCP 連接,對(duì)嗎?
答:肯定是不對(duì)的。首先,出于【傳輸層】的TCP協(xié)議,肯定是包含了【網(wǎng)絡(luò)層】IP層數(shù)據(jù)的(如果你網(wǎng)絡(luò),趕緊回去看看【2.2】)。的雖然我沒(méi)有貼出來(lái)【網(wǎng)絡(luò)層】IP協(xié)議的頭部數(shù)據(jù),但是你多多少少會(huì)知道,肯定需要【源IP地址】跟【目標(biāo)IP】地址才對(duì)。
所以,定義一個(gè)【唯一TCP連接】的要素如下:【源 IP 地址】+【目標(biāo) IP 地址】+【協(xié)議號(hào)(協(xié)議類型)】+【源端口號(hào)】+【目標(biāo)端口號(hào)】
在這里插入圖片描述
大伙看看上圖,不難看出,當(dāng)【源IP地址】跟【源端口號(hào)】變化時(shí),哪怕【目標(biāo)IP地址】跟【目標(biāo)端口號(hào)】,【協(xié)議類型(TCP固定為TCP,UDP固定為UPD)】固定,TCP連接數(shù)量也會(huì)由于前兩者的變化而變化。

3.2 UDP特性

UDP(User Datagram Protocol),中文名是用戶數(shù)據(jù)報(bào)協(xié)議。是把數(shù)據(jù)直接發(fā)出去,而不管對(duì)方是不是在接收,也不管對(duì)方是否能接收,也不需要接收方確認(rèn),屬于不可靠的傳輸,可能會(huì)出現(xiàn)丟包現(xiàn)象,實(shí)際應(yīng)用中要求程序員自己選擇、比較使用。
UDP還有2個(gè)特性,稱為:單播和廣播。

單播和廣播
單播的傳輸模式,定義為發(fā)送消息給一個(gè)由唯一的地址所標(biāo)識(shí)的單一的網(wǎng)絡(luò)目的地。面向連接的協(xié)議和無(wú)連接協(xié)議都支持這種模式。由于通訊不需要連接,所以可以實(shí)現(xiàn)廣播發(fā)送,所謂廣播——傳輸?shù)骄W(wǎng)絡(luò)(或者子網(wǎng))上的所有主機(jī)。

我相信大部分初次接觸UDP的朋友可能會(huì)覺(jué)得,UDP這么不可靠,使用應(yīng)該不多吧?哈,相反,UDP因?yàn)闆](méi)那么多復(fù)雜的機(jī)制,反而深受大佬們喜愛(ài),甚至可以說(shuō),很多大佬們會(huì)優(yōu)先選擇UDP,除非迫不得已,真的需要【可靠性】的支持,才使用TCP。

四、總結(jié)

OK,【前置知識(shí)】中關(guān)于網(wǎng)絡(luò)編程的一些基礎(chǔ)知識(shí)終于是捋完了。怎么說(shuō)呢,因?yàn)槲乙矝](méi)看過(guò)更專業(yè)、更全的【網(wǎng)絡(luò)編程基礎(chǔ)】相關(guān)資料書(shū)記,所以,我也不好說(shuō)我說(shuō)的是否都是對(duì)的,更遑論是否全面。我只是根據(jù),我一直以來(lái)學(xué)習(xí)網(wǎng)絡(luò)編程以來(lái)遇到的問(wèn)題。不過(guò)話說(shuō)回來(lái),我遇到的這些問(wèn)題有時(shí)候會(huì)受限于個(gè)人的眼界與技術(shù)功底。
共勉

課程內(nèi)容

正文開(kāi)始

一、網(wǎng)絡(luò)通信編程基礎(chǔ)知識(shí)

1.1 什么是Socket

我在前置知識(shí)中稍微提過(guò)一嘴,由于Socket的存在,直接幫我們封裝了TCP/IP模型中的后4層,我們只需要關(guān)注【應(yīng)用層】就好了。甚至【應(yīng)用層】也有很多成熟的產(chǎn)品了,我們往往只需要關(guān)注如何寫業(yè)務(wù),以及如何把它們應(yīng)用到我們的代碼中。言歸正傳。
Socket是應(yīng)用層與 TCP/IP 協(xié)議族通信的中間軟件抽象層,它是一組接口,一般由操作系統(tǒng)提供。從設(shè)計(jì)模式的角度看,Socket其實(shí)就是一個(gè)門面模式,它把復(fù)雜的 TCP/IP 協(xié)議處理和通信緩存管理等等都隱藏在 Socket 接口后面,對(duì)用戶來(lái)說(shuō),使用一組簡(jiǎn)單的接口就能進(jìn)行網(wǎng)絡(luò)應(yīng)用編程,讓Socket 去組織數(shù)據(jù),以符合指定的協(xié)議。主機(jī) A 的應(yīng)用程序要能和主機(jī) B 的應(yīng)用程序通信,必須通過(guò) Socket 建立連接。

給大家一個(gè)門面模式渲染圖感受一下Socket:
在這里插入圖片描述
改成門面模式后:(不要懷疑下面這個(gè)是否合理,如果你去過(guò)高端大氣上檔次的男科醫(yī)院,就知道什么是VIP服務(wù)了)
在這里插入圖片描述

客戶端連接上一個(gè)服務(wù)端(connect),就會(huì)在客戶端中產(chǎn)生一個(gè) socket 接口實(shí)例,服務(wù)端每接受一個(gè)客戶端連接,就會(huì)產(chǎn)生一個(gè) socket 接口實(shí)例和客戶端的 socket 進(jìn)行通信,有多個(gè)客戶端連接自然就有多個(gè) socket 接口實(shí)例。它們的通信模型大概如下:
在這里插入圖片描述

1.2 長(zhǎng)連接、短連接

短連接
短連接的大概流程是這樣的:連接->傳輸數(shù)據(jù)->關(guān)閉連接。典型的如:HTTP。
傳統(tǒng) HTTP 是無(wú)狀態(tài)的,瀏覽器和服務(wù)器每進(jìn)行一次 HTTP 操作,就建立一次連接,但任務(wù)結(jié)束就斷開(kāi)。也可以這樣說(shuō):短連接是指 SOCKET 連接發(fā)送數(shù)據(jù)接收完數(shù)據(jù)后馬上斷開(kāi)連接的一種連接。

長(zhǎng)連接
長(zhǎng)連接的大概流程是這樣的:連接->傳輸數(shù)據(jù)->保持連接 -> 傳輸數(shù)據(jù)-> 。。。 ->關(guān)閉連接。典型的如:Mysql連接協(xié)議。也可以這樣說(shuō):長(zhǎng)連接指建立 SOCKET 連接后不管是否使用都保持連接的一種連接。

Q:什么時(shí)候用長(zhǎng)連接,短連接?
答:長(zhǎng)連接多用于操作頻繁,點(diǎn)對(duì)點(diǎn)的通訊,畢竟TCP連接需要三次握手,反復(fù)握手是一個(gè)很重型的操作。而且從代碼角度來(lái)說(shuō),反復(fù)創(chuàng)建Socket對(duì)象也不是一個(gè)經(jīng)濟(jì)的方式。而像 WEB 網(wǎng)站的 HTTP服務(wù)按照 HTTP協(xié)議規(guī)范早期一般都用短鏈接,畢竟想WEB應(yīng)用通常都不會(huì)頻繁交互,或者說(shuō)每次交互都是短暫的操作。不過(guò)值得注意的是,現(xiàn)在的 HTTP協(xié)議,Http1.1,尤其是 Http2、Http3 已經(jīng)開(kāi)始向長(zhǎng)連接演化。

1.3 網(wǎng)絡(luò)編程與生活常識(shí)類比

我們首先來(lái)看一個(gè)生活中的場(chǎng)景。網(wǎng)絡(luò)編程可以類比成生活中:心理咨詢中心場(chǎng)景。

  1. 周瑜老師準(zhǔn)備開(kāi)一個(gè)心理咨詢中心,嘴上光喊沒(méi)用,只有到工商局注冊(cè)“東吳心理診所”并且在圖靈大街 888 號(hào)掛牌了,才算正式開(kāi)張。為了開(kāi)展電話業(yè)務(wù),申請(qǐng)了一個(gè)電話號(hào)碼 88888888。

這里就類比于:網(wǎng)絡(luò)編程中,我們要先在服務(wù)端聲明一個(gè)ServerSocket,綁定指定IP地址跟端口

  1. 由于疫情經(jīng)濟(jì)下滑等原因,諸葛老師為了養(yǎng)家糊口沒(méi)日沒(méi)夜的工作,久疾成病,并且有了心理問(wèn)題。于是打電話過(guò)來(lái),周瑜老師接了電話,但是周瑜老師只是一個(gè)接待,

這就類比于:網(wǎng)絡(luò)編程中,ServerSocket只關(guān)注于網(wǎng)絡(luò)連接事件

  1. 所以周瑜老師不懂心理咨詢,于是通過(guò)內(nèi)部分機(jī)把電話轉(zhuǎn)給請(qǐng)來(lái)的心理醫(yī)生 A 負(fù)責(zé)接待諸葛老師,心理醫(yī)生 A 和諸葛老師通過(guò)電話進(jìn)行溝通,模式一般就是一個(gè)人說(shuō)另個(gè)一人聽(tīng),兩者進(jìn)行溝通交流。
  2. Fox 老師也來(lái)了,周瑜老師接了電話,又把電話轉(zhuǎn)給請(qǐng)來(lái)的心理醫(yī)生 B 負(fù)責(zé)接待 Fox 老師,心理醫(yī)生 B 和 Fox 老師也通過(guò)電話進(jìn)行溝通。

4、5就類比于:網(wǎng)絡(luò)編程中,ServerSocket每接收一個(gè)連接事件后,就新建一個(gè)Socket與它們交互

在這里插入圖片描述
還算簡(jiǎn)單吧。這里有一個(gè)小結(jié)論要跟大家說(shuō)下:
整體來(lái)說(shuō),在網(wǎng)絡(luò)通信編程中,我們只需要關(guān)注三件事:
PS:網(wǎng)絡(luò)編程范式
PS:網(wǎng)絡(luò)編程范式
PS:網(wǎng)絡(luò)編程范式
1)連接事件??蛻舳诉B接服務(wù)器,服務(wù)器等待和連接
2)讀網(wǎng)絡(luò)數(shù)據(jù)
3)寫網(wǎng)絡(luò)數(shù)據(jù)
無(wú)論是后面學(xué)的BIO也好,還是NIO,又或者什么Netty網(wǎng)絡(luò)編程,所有模式的通信編程都是圍繞著這三件事情進(jìn)行的。服務(wù)端提供 IP 和監(jiān)聽(tīng)端口,客戶端通過(guò)連接操作向服務(wù)端監(jiān)聽(tīng)的地址發(fā)起連接請(qǐng)求,通過(guò)三次握手連接,如果連接成功建立,雙方就可以通過(guò)套接字進(jìn)行通信。

二、BIO

2.1 BIO簡(jiǎn)介

BIO,意為 Blocking I/O,即:阻塞的 I/O。
BIO 基本上就是我們上面所說(shuō)的生活場(chǎng)景的直接實(shí)現(xiàn)。在 BIO 中類 ServerSocket 負(fù)責(zé)綁定 IP 地址,啟動(dòng)監(jiān)聽(tīng)端口,等待客戶連接;客戶端 Socket 類的實(shí)例發(fā)起連接操作,ServerSocket接受連接后產(chǎn)生一個(gè)新的服務(wù)端 socket 實(shí)例負(fù)責(zé)和客戶端 socket 實(shí)例通過(guò)輸入和輸出流進(jìn)行通信。
在這里插入圖片描述
那么,BIO的阻塞體現(xiàn)在哪里呢?我們來(lái)看一段簡(jiǎn)單的BIO代碼。

/*** 服務(wù)端BIO代碼* * @author shen·buffet* @date 2023-10-17* @slogan 聽(tīng)天命,盡人事*/
public class TestServer {final static int PORT = 8585;public static void main(String[] args) throws IOException {// 第一步:創(chuàng)建一個(gè)ServerSocketServerSocket serverSocket = createServerSocket();System.out.println("第一步完成。 服務(wù)已啟動(dòng)");// 第二步:監(jiān)聽(tīng)連接事件handleConnect(serverSocket);}/*** 創(chuàng)建一個(gè)ServerSocket*/public static ServerSocket createServerSocket() throws IOException {ServerSocket serverSocket = new ServerSocket();serverSocket.bind(new InetSocketAddress(PORT));return serverSocket;}/*** ServerSocket上的事件處理*/public static void handleConnect(ServerSocket serverSocket) throws IOException {int connectCount = 0;try {while (true) {Socket clientSocket = serverSocket.accept();System.out.println("連接事件來(lái)了。 當(dāng)前連接數(shù)目=" + (++connectCount));// 第三步:讀寫網(wǎng)絡(luò)事件handleClientReadWrite(clientSocket);}} finally {serverSocket.close();}}/*** Client上的讀寫事件*/public static void handleClientReadWrite(Socket clientSocket) throws IOException {// 實(shí)例化與客戶端通信的輸入輸出流ObjectInputStream objectInputStream = new ObjectInputStream(clientSocket.getInputStream());ObjectOutputStream objectOutputStream = new ObjectOutputStream(clientSocket.getOutputStream());// 接收客戶端的輸出,也就是服務(wù)器的輸入String userName = objectInputStream.readUTF();System.out.println("客戶端讀事件來(lái)了,消息=" + userName);// 服務(wù)器的輸出,也就是客戶端的輸入objectOutputStream.writeUTF("hello," + userName);objectOutputStream.flush();}
}/*** 客戶端BIO代碼** @author shen·buffet* @date 2023-10-17* @slogan 聽(tīng)天命,盡人事*/
public class TestClient {public static void main(String[] args) throws IOException {//客戶端啟動(dòng)必備Socket socket = null;// 實(shí)例化與服務(wù)端通信的輸入輸出流ObjectOutputStream output = null;ObjectInputStream input = null;// 服務(wù)器的通信地址InetSocketAddress addr = new InetSocketAddress("127.0.0.1", 8585);try {socket = new Socket();socket.connect(addr);System.out.println("連接服務(wù)器成功!!");output = new ObjectOutputStream(socket.getOutputStream());input = new ObjectInputStream(socket.getInputStream());System.out.println("客戶端準(zhǔn)備發(fā)送數(shù)據(jù)....");// 向服務(wù)器輸出請(qǐng)求output.writeUTF("ZhangShen");output.flush();//接收服務(wù)器的輸出System.out.println("服務(wù)端答復(fù)數(shù)據(jù)來(lái)了....");System.out.println(input.readUTF());} finally {if (socket != null) {socket.close();}if (output != null) {output.close();}if (input != null) {input.close();}}}
}

其實(shí)這個(gè)阻塞,就體現(xiàn)在上面代碼中的2點(diǎn):TestServer#handleConnect下的serverSocket.accept()服務(wù)端等待客戶端連接,以及TestServer#handleClientReadWrite下的objectInputStream.readUTF()等待客戶端socket發(fā)送消息。(大家伙可以從方法點(diǎn)進(jìn)去,看看方法頭上的注釋就知道了。通常這種優(yōu)秀的代碼,會(huì)在方法頭上寫清楚注釋:這個(gè)方法會(huì)阻塞)
除了阻塞以外,大家發(fā)現(xiàn)沒(méi)有,這個(gè)代碼一條線程只能處理一個(gè)TCP連接請(qǐng)求,這顯然是無(wú)法忍受的。所以,BIO在演進(jìn)的過(guò)程中,為了,結(jié)合了多線程來(lái)實(shí)現(xiàn)同時(shí)處理多個(gè)TCP連接的目的。

如果想要使用我上面的代碼實(shí)現(xiàn)模擬多條TCP連接失敗的場(chǎng)景,需要DEBUG住客戶端發(fā)送消息那一塊,反正就是不要讓它進(jìn)入finally塊關(guān)閉socket,不然連接都關(guān)了,服務(wù)端也要申請(qǐng)關(guān)閉(TCP全雙工特性,還記得吧)。
然后,IDEA用戶的話,此時(shí)再新增一個(gè)Application啟動(dòng),如下圖所示:(整體操作步驟應(yīng)該不用我手把手再教學(xué)了吧…)
在這里插入圖片描述

2.2 BIO結(jié)合多線程1:普通線程

話不多說(shuō),先上個(gè)代碼,這里主要修改了TestServer類的handleConnect方法:

    /*** ServerSocket上的事件處理*/public static void handleConnect(ServerSocket serverSocket) throws IOException {int connectCount = 0;try {while (true) {Socket clientSocket = serverSocket.accept();System.out.println("連接事件來(lái)了。 當(dāng)前連接數(shù)目=" + (++connectCount));// 第三步:讀寫網(wǎng)絡(luò)事件new Thread(()->{try {handleClientReadWrite(clientSocket);} catch (IOException e) {throw new RuntimeException(e);}}).start();}} finally {serverSocket.close();}}

就是在調(diào)用handleClientReadWrite的時(shí)候,新增一個(gè)線程。
好了,如果看過(guò)我前面【并發(fā)專題】筆記的朋友,或者本身就了解多線程的朋友肯定知道,這么干是有性能瓶頸的。瓶頸造成的原因如下:

  1. Java線程,目前JDK1.8采用的是【內(nèi)核線程】方案(其實(shí)JDK17也是),所以Java線程與內(nèi)核線程(內(nèi)核線程,指的是PC操作系統(tǒng)的線程)是1:1關(guān)系
  2. 因?yàn)橐粋€(gè)PC最多也就開(kāi)幾千條線程,更何況,難道這臺(tái)PC也不可能只有你一個(gè)Java程序在用啊

所以這個(gè)方案肯定不行的。先上個(gè)該方案的模型圖讓大家理解一下:
在這里插入圖片描述
那不行,還有高級(jí)一點(diǎn)的方案嗎?有的,用線程池咯,試試看

2.3 BIO結(jié)合多線程2:線程池

先上代碼。這里主要修改了TestServer類的handleConnect方法,并且新增了一個(gè)線程池:

    final static ExecutorService executorService;static {int cores = Runtime.getRuntime().availableProcessors();executorService = Executors.newFixedThreadPool(cores);}/*** ServerSocket上的事件處理*/public static void handleConnect(ServerSocket serverSocket) throws IOException {int connectCount = 0;try {while (true) {Socket clientSocket = serverSocket.accept();System.out.println("連接事件來(lái)了。 當(dāng)前連接數(shù)目=" + (++connectCount));// 第三步:讀寫網(wǎng)絡(luò)事件executorService.submit(() -> {try {handleClientReadWrite(clientSocket);} catch (IOException e) {throw new RuntimeException(e);}});}} finally {serverSocket.close();}}

雖然我這里的線程池用的是newFixedThreadPool,不過(guò)你們也可以根據(jù)需要選擇其他API。但是,無(wú)論你選擇何種線程池API,這始終是一個(gè)BIO,并且它的性能受限于線程池。先來(lái)個(gè)模型圖幫助大家理解:
在這里插入圖片描述

*2.4 小結(jié)

一定要看看
一定要看看
一定要看看

BIO小結(jié)一:
大家也看到了,BIO使用還是有多種限制的,哪怕是上了多線程也一樣。事實(shí)上,我相信大家也看得出來(lái),真正讓BIO受限的,其實(shí)是BIO本身。而且我們開(kāi)過(guò)天眼的啊,知道有NIO這個(gè)東西,所以,關(guān)于BIO性能瓶頸的解決方案,我們也能預(yù)知道。

BIO小結(jié)二:
嚴(yán)格來(lái)說(shuō)這里不是BIO小結(jié),而是:Java Socket網(wǎng)絡(luò)編程小結(jié)。
經(jīng)過(guò)了【前置知識(shí)】的系統(tǒng)整合,我終于把OSI,TCP/IP網(wǎng)絡(luò)模型與Java Socket網(wǎng)絡(luò)編程結(jié)合起來(lái)理解了。以前我看BIO、NIO、甚至Netty等網(wǎng)絡(luò)編程的時(shí)候,個(gè)人遇到的最大的兩個(gè)問(wèn)題是:

  • 為什么服務(wù)端這么多個(gè)Socket,什么ServerSocket,然后每次客戶端連接進(jìn)來(lái)的時(shí)候,還要重新新建一個(gè)Socket保存?Socket是什么?我只能死記硬背,真讓我回答什么是Socket我也只能含糊其辭說(shuō)是Java對(duì)網(wǎng)絡(luò)編程的封裝了
  • 各種流,把我整蒙了?!菊劸W(wǎng)色變】一定程度上是因?yàn)椤菊劻魃儭?/li>

當(dāng)然【談流色變】這個(gè)病我還沒(méi)去治,現(xiàn)階段也只是解決了部分疑問(wèn)。
第一個(gè)問(wèn)題,多個(gè)Socket是怎么回事。我在一開(kāi)始介紹Socket的時(shí)候就說(shuō)過(guò),它是對(duì)網(wǎng)絡(luò)模型后面4層的封裝,使得我們無(wú)需關(guān)心IP、端口是如何被封裝的。另外,還記得一條TCP是如何標(biāo)識(shí)唯一的嗎?(我在【前置知識(shí) 3.1中講了】)也正是由于這個(gè)原因,所以為了維護(hù)、標(biāo)識(shí)每個(gè)客戶端的唯一TCP,使用一個(gè)Socket去保存。

BIO小結(jié)三:
雖然我們一直說(shuō)BIO有性能瓶頸,話語(yǔ)中看似有不喜的意思,但并不代表它就沒(méi)用。因?yàn)樗褂煤?jiǎn)單的特性,像Zookeeper等分布式中間件在集群通信方案中,都使采用了BIO。

三、NIO

3.1 NIO簡(jiǎn)介

NIO 庫(kù)是在 JDK 1.4 中引入的。NIO 彌補(bǔ)了原來(lái)的 BIO 的不足,它在標(biāo)準(zhǔn) Java 代碼中提供了高速的、面向塊的 I/O。NIO 被稱為 no-blocking io(非阻塞IO),也有人叫new io(新IO)。這無(wú)關(guān)緊要,只是一個(gè)稱呼。

3.2 與BIO的主要區(qū)別

NIO跟BIO相比,有2個(gè)比較顯著的區(qū)別:

  1. 前者非阻塞,后者阻塞

BIO是如何阻塞的,在哪里阻塞,我前面已經(jīng)提過(guò)了。NIO則是因?yàn)榘堰@些阻塞操作變成了非阻塞,在沒(méi)有連接或者讀取事件過(guò)來(lái)的時(shí)候,去做其他事情。比如:接收新的客戶端的連接,或者讀取事件。就是這樣,達(dá)到了【一條線程管理多個(gè)客戶端的連接、輸入輸出】

  1. 前者面向緩沖,后者面向流

這算是NIO與BIO最大的區(qū)別。
BIO是面向流的,怎么理解呢?BIO面向流意味著每次從流中讀一個(gè)或多個(gè)字節(jié),直至讀取所有字節(jié),它們沒(méi)有被緩存在任何地方。此外,它不能前后移動(dòng)流中的數(shù)據(jù)。如果需要前后移動(dòng)從流中讀取的數(shù)據(jù),需要先將它緩存到一個(gè)緩沖區(qū);
NIO是面向緩沖區(qū)的,NIO把數(shù)據(jù)讀取到一個(gè)它稍后處理的緩沖區(qū),需要時(shí)可在緩沖區(qū)中前后移動(dòng)。這就增加了處理過(guò)程中的靈活性。但是,還需要檢查是否該緩沖區(qū)中包含所有需要處理的數(shù)據(jù)。而且,需確保當(dāng)更多的數(shù)據(jù)讀入緩沖區(qū)時(shí),不要覆蓋緩沖區(qū)里尚未處理的數(shù)據(jù)。

3.3 Java NIO沒(méi)引入多路復(fù)用器之前

Java的NIO并非本來(lái)就這么強(qiáng)大的,在沒(méi)有引入多路復(fù)用之前,他也僅僅只是非阻塞版本的BIO而已。代碼示例如下:

public class NioServer {// 保存客戶端連接static List<SocketChannel> channelList = new ArrayList<>();public static void main(String[] args) throws IOException, InterruptedException {// 創(chuàng)建NIO ServerSocketChannel,與BIO的serverSocket類似ServerSocketChannel serverSocket = ServerSocketChannel.open();serverSocket.socket().bind(new InetSocketAddress(9000));// 設(shè)置ServerSocketChannel為非阻塞serverSocket.configureBlocking(false);System.out.println("服務(wù)啟動(dòng)成功");while (true) {// 非阻塞模式accept方法不會(huì)阻塞,否則會(huì)阻塞// NIO的非阻塞是由操作系統(tǒng)內(nèi)部實(shí)現(xiàn)的,底層調(diào)用了linux內(nèi)核的accept函數(shù)SocketChannel socketChannel = serverSocket.accept();if (socketChannel != null) { // 如果有客戶端進(jìn)行連接System.out.println("連接成功");// 設(shè)置SocketChannel為非阻塞socketChannel.configureBlocking(false);// 保存客戶端連接在List中channelList.add(socketChannel);}// 遍歷連接進(jìn)行數(shù)據(jù)讀取Iterator<SocketChannel> iterator = channelList.iterator();while (iterator.hasNext()) {SocketChannel sc = iterator.next();ByteBuffer byteBuffer = ByteBuffer.allocate(128);// 非阻塞模式read方法不會(huì)阻塞,否則會(huì)阻塞int len = sc.read(byteBuffer);// 如果有數(shù)據(jù),把數(shù)據(jù)打印出來(lái)if (len > 0) {System.out.println("接收到消息:" + new String(byteBuffer.array()));} else if (len == -1) { // 如果客戶端斷開(kāi),把socket從集合中去掉iterator.remove();System.out.println("客戶端斷開(kāi)連接");}}}}
}

模型圖就不畫了。相比于BIO,僅僅只是非阻塞而已,然后呢,相比于BIO的【一條線程管理一條連接】,它實(shí)現(xiàn)了【一條線程管理多個(gè)連接】。不過(guò),我想細(xì)心的朋友已經(jīng)看見(jiàn)了,非阻塞 + 【while(true)】的組合,那這樣,CPU使用率不是非常高?對(duì)的!這種方案,CPU使用率很高,而且每次都輪詢所有的【與客戶端連接的socket】,就導(dǎo)致了大量無(wú)效的遍歷。

3.4 Java NIO + 多路復(fù)用

不過(guò)好在啊,JAVA的NIO實(shí)現(xiàn)引入了多路復(fù)用器這個(gè)玩意。

Q:什么是多路復(fù)用?
答:多路復(fù)用,即:【多路】+【復(fù)用】?!径嗦贰恐傅氖?#xff1a;多個(gè)TCP連接;【復(fù)用】指的是:復(fù)用一個(gè)或者多個(gè)線程。體現(xiàn)在IO中(完整的概念:IO多路復(fù)用)的意思就是,一條線程管理多個(gè)TCP連接的狀態(tài)。只有當(dāng) socket 真正有讀寫事件時(shí),才真正調(diào)用實(shí)際的 IO 讀寫操作。因?yàn)樵诙嗦窂?fù)用 IO 模型中,只需要使用一個(gè)線程就可以管理多個(gè)socket,系統(tǒng)不需要建立新的進(jìn)程或者線程,也不必維護(hù)這些線程和進(jìn)程,并且只有在真正有socket 讀寫事件進(jìn)行時(shí),才會(huì)使用 IO 資源,所以它大大減少了資源占用。(至于怎么實(shí)現(xiàn)的IO多路復(fù)用,這個(gè)是Linux內(nèi)核的行為,感興趣自己去研究)

先看看JavaNIO引入多路復(fù)用器之后的代碼:

public class NioSelectorServer {public static void main(String[] args) throws IOException, InterruptedException {// 創(chuàng)建NIO ServerSocketChannelServerSocketChannel serverSocket = ServerSocketChannel.open();serverSocket.socket().bind(new InetSocketAddress(9000));// 設(shè)置ServerSocketChannel為非阻塞serverSocket.configureBlocking(false);// 打開(kāi)Selector處理Channel,即創(chuàng)建epollSelector selector = Selector.open();// 把ServerSocketChannel注冊(cè)到selector上,并且selector對(duì)客戶端accept連接操作感興趣serverSocket.register(selector, SelectionKey.OP_ACCEPT);System.out.println("服務(wù)啟動(dòng)成功");while (true) {// 阻塞等待需要處理的事件發(fā)生selector.select();// 獲取selector中注冊(cè)的全部事件的 SelectionKey 實(shí)例Set<SelectionKey> selectionKeys = selector.selectedKeys();Iterator<SelectionKey> iterator = selectionKeys.iterator();// 遍歷SelectionKey對(duì)事件進(jìn)行處理while (iterator.hasNext()) {SelectionKey key = iterator.next();// 如果是OP_ACCEPT事件,則進(jìn)行連接獲取和事件注冊(cè)if (key.isAcceptable()) {ServerSocketChannel server = (ServerSocketChannel) key.channel();SocketChannel socketChannel = server.accept();socketChannel.configureBlocking(false);// 這里只注冊(cè)了讀事件,如果需要給客戶端發(fā)送數(shù)據(jù)可以注冊(cè)寫事件socketChannel.register(selector, SelectionKey.OP_READ);System.out.println("客戶端連接成功");} else if (key.isReadable()) {  // 如果是OP_READ事件,則進(jìn)行讀取和打印SocketChannel socketChannel = (SocketChannel) key.channel();ByteBuffer byteBuffer = ByteBuffer.allocate(128);int len = socketChannel.read(byteBuffer);// 如果有數(shù)據(jù),把數(shù)據(jù)打印出來(lái)if (len > 0) {System.out.println("接收到消息:" + new String(byteBuffer.array()));} else if (len == -1) { // 如果客戶端斷開(kāi)連接,關(guān)閉SocketSystem.out.println("客戶端斷開(kāi)連接");socketChannel.close();}}//從事件集合里刪除本次處理的key,防止下次select重復(fù)處理iterator.remove();}}}
}

在這個(gè)代碼中,其實(shí)有三個(gè)比較核心的東西,也是與上一版的NIO區(qū)別所在,他們就是NIO的三大核心組件:

  • Channel(通道):通道,是一個(gè)【應(yīng)用程序和操作系統(tǒng)交互事件、傳遞內(nèi)容的渠道(注意是連接到操
    作系統(tǒng))】。那么既然是和操作系統(tǒng)進(jìn)行內(nèi)容的傳遞,那么說(shuō)明應(yīng)用程序可以通過(guò)通道讀取數(shù)據(jù),也可以通過(guò)通道向操作系統(tǒng)寫數(shù)據(jù),而且可以同時(shí)進(jìn)行讀寫
  • Selector(多路復(fù)用器):選擇器,它允許一個(gè)單獨(dú)的線程來(lái)監(jiān)視多個(gè)輸入通道,你可以注冊(cè)多個(gè)通道(就是上面的Channel)使用一個(gè)選擇器(Selectors),然后使用一個(gè)單獨(dú)的線程來(lái)操作這個(gè)選擇器,進(jìn)而“選擇”通道:這些通道里已經(jīng)有可以處理的輸入,或者選擇已準(zhǔn)備寫入的通道。這種選擇機(jī)制,使得一個(gè)單獨(dú)的線程很容易來(lái)管理多個(gè)通道(應(yīng)用程序?qū)⑾?Selector 對(duì)象注冊(cè)需要它關(guān)注的 Channel,以及具體的某一個(gè) Channel 會(huì)對(duì)哪些 IO 事件感興趣。Selector 中也會(huì)維護(hù)一個(gè)“已經(jīng)注冊(cè)的 Channel”的容器)
  • Buffter(緩沖區(qū)):我們前面說(shuō)過(guò) JDK NIO 是面向緩沖的,Buffer 就是這個(gè)緩沖,用于和 NIO 通道進(jìn)行交互。數(shù)據(jù)是從通道讀入緩沖區(qū),從緩沖區(qū)寫入到通道中的。緩沖區(qū)本質(zhì)上是一塊可以寫入數(shù)據(jù),然后可以從中讀取數(shù)據(jù)的內(nèi)存(其實(shí)就是數(shù)組)。這塊內(nèi)存被包裝成 NIO Buffer 對(duì)象,并提供了一組方法,用來(lái)方便的訪問(wèn)該塊內(nèi)存。一個(gè)通道對(duì)應(yīng)一個(gè)緩沖區(qū)


    引入多路復(fù)用器之后的Java NIO模型如下:
    在這里插入圖片描述
    (PS:我估計(jì)比較較真的同學(xué)對(duì)上面的代碼中的selectorchannel、selectionKey會(huì)有點(diǎn)疑惑,這個(gè)寫法上確實(shí)比較抽象。大家點(diǎn)開(kāi)SelectorSelectionKey就知道它們的關(guān)系了。我對(duì)這個(gè)設(shè)計(jì)思想也不是很熟悉,就不說(shuō)了。簡(jiǎn)單來(lái)說(shuō)就是:你中有我,我中有你)

NIO多路復(fù)用的代碼如上,流程我就不多說(shuō)了,基本上都寫了注釋。但還是要提一嘴,這個(gè)方案比較核心的代碼在于如下:

// 創(chuàng)建多路復(fù)用器 
Selector.open() // 將channel注冊(cè)到多路復(fù)用器上,并設(shè)置感興趣的事件
socketChannel.register(selector, SelectionKey.OP_READ)  // 阻塞等待需要處理的事件發(fā)生
selector.select()

所以我是希望大伙可以稍微去看一下這個(gè)源碼的,也幫助大家理解selectorchannel、selectionKey之間的關(guān)系。代碼流程圖如下:(下圖是基于Linux操作系統(tǒng)的實(shí)現(xiàn),Windows實(shí)現(xiàn)大體相似,但是實(shí)現(xiàn)類多少有點(diǎn)區(qū)別。因?yàn)閃indows下沒(méi)有epoll函數(shù),只有select函數(shù))
在這里插入圖片描述
整體來(lái)說(shuō):JavaNIO整個(gè)調(diào)用流程就是調(diào)用了【操作系統(tǒng)的內(nèi)核函數(shù)】來(lái)創(chuàng)建Socket,獲取到Socket的文件描述符,再創(chuàng)建一個(gè)Selector對(duì)象,對(duì)應(yīng)操作系統(tǒng)的【Epoll描述符】,將獲取到的Socket連接的文件描述符的事件綁定到Selector對(duì)應(yīng)的Epoll文件描述符上,進(jìn)行事件的異步通知,這樣就實(shí)現(xiàn)了使用一條線程,并且不需要太多的無(wú)效的遍歷,將事件處理交給了操作系統(tǒng)內(nèi)核(操作系統(tǒng)中斷程序DMA來(lái)實(shí)現(xiàn)的),大大提高了效率。

順帶提一嘴。上面說(shuō)的Linux內(nèi)核函數(shù),其實(shí)就是大名鼎鼎的epoll函數(shù)(可曾聽(tīng)聞epoll模型?)。

*四、拓展:epoll簡(jiǎn)介

關(guān)于epoll,雖然這個(gè)屬于拓展,但是因?yàn)橹跎峡吹搅艘粋€(gè)硬核大佬寫的文章,從硬件層面開(kāi)始給大家推演epoll的演進(jìn)變化,實(shí)在是太牛逼了,而且講得通俗易懂。如果你又恰好完整看了我的【前置知識(shí)】,個(gè)人感覺(jué)閱讀起來(lái)不會(huì)很困難(當(dāng)然硬件知識(shí)直接跳過(guò)),所以我還是比較建議大伙可以學(xué)習(xí)學(xué)習(xí)的。文章傳送門:《如果這篇文章說(shuō)不清e(cuò)poll的本質(zhì),那就過(guò)來(lái)掐死我吧》

學(xué)習(xí)總結(jié)

  1. 從OSI、TCP/IP模型開(kāi)始學(xué)習(xí)網(wǎng)絡(luò)編程
  2. 學(xué)習(xí)了BIO、NIO的一些基礎(chǔ)知識(shí)
  3. 認(rèn)識(shí)了NIO+多路復(fù)用,以及多路復(fù)用的三個(gè)核心組件
  4. 個(gè)人在這兩天學(xué)習(xí)得非常爽,很滿足,因?yàn)檫@確實(shí)解決了不少個(gè)人在網(wǎng)絡(luò)編程方面的疑問(wèn)

感謝

感謝知乎大佬【作者:羅培羽】的文章《如果這篇文章說(shuō)不清e(cuò)poll的本質(zhì),那就過(guò)來(lái)掐死我吧》

http://www.risenshineclean.com/news/11697.html

相關(guān)文章:

  • 西安高端網(wǎng)站建設(shè)公司seo案例分析100例
  • 純靜態(tài)網(wǎng)站制作seo整站優(yōu)化報(bào)價(jià)
  • 一個(gè)網(wǎng)站的后臺(tái)怎么做太原網(wǎng)站建設(shè)
  • 百度怎樣做網(wǎng)站并宣傳網(wǎng)站長(zhǎng)春網(wǎng)站建設(shè)公司哪家好
  • 鄭州婦科醫(yī)院正規(guī)有哪些廣州seo營(yíng)銷培訓(xùn)
  • 煤礦黨風(fēng)廉政建設(shè)網(wǎng)站如何注冊(cè)域名
  • 淘客做的領(lǐng)券網(wǎng)站黑帽seo優(yōu)化推廣
  • 為什么用wp做網(wǎng)站沈陽(yáng)網(wǎng)站制作優(yōu)化推廣
  • 對(duì)視頻播放網(wǎng)站做性能測(cè)試查排名的軟件有哪些
  • c2c電子商務(wù)網(wǎng)站策劃深圳市龍華區(qū)
  • 做的好看的網(wǎng)站免費(fèi)網(wǎng)站優(yōu)化排名
  • 做網(wǎng)站便宜新聞源
  • wordpress隱藏服務(wù)器ip網(wǎng)站seo優(yōu)化分析
  • 廣州市做民宿什么網(wǎng)站比較好seo搜論壇
  • 什么網(wǎng)站可以免費(fèi)做找客戶谷歌seo快速排名優(yōu)化方法
  • vs做網(wǎng)站教程長(zhǎng)春網(wǎng)站關(guān)鍵詞排名
  • 東莞模板建站軟件seo專員
  • 視頻網(wǎng)站如何建設(shè)專業(yè)代寫軟文
  • 武漢招聘一般用什么網(wǎng)站沙洋縣seo優(yōu)化排名價(jià)格
  • 對(duì)酒店網(wǎng)站建設(shè)的意見(jiàn)互聯(lián)網(wǎng)廣告行業(yè)
  • 喀什做網(wǎng)站seo快速排名源碼
  • wordpress 菜單 標(biāo)簽科學(xué)新概念seo外鏈平臺(tái)
  • 照片做視頻ppt模板下載網(wǎng)站知識(shí)營(yíng)銷成功案例介紹
  • 淡水做網(wǎng)站網(wǎng)頁(yè)設(shè)計(jì)主題參考
  • word做招聘網(wǎng)站長(zhǎng)尾詞挖掘
  • 電影日記網(wǎng)站怎么做界首網(wǎng)站優(yōu)化公司
  • 有免費(fèi)的網(wǎng)址嗎南寧seo專員
  • 做網(wǎng)站造假域名推薦
  • 做論壇網(wǎng)站前段用什么框架好點(diǎn)網(wǎng)絡(luò)廣告的概念
  • 公司網(wǎng)站建設(shè)會(huì)計(jì)上怎么處理百度產(chǎn)品大全入口