国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

【Java 網(wǎng)絡(luò)編程】網(wǎng)絡(luò)通信原理、TCP、UDP 回顯服務(wù)

這篇具有很好參考價(jià)值的文章主要介紹了【Java 網(wǎng)絡(luò)編程】網(wǎng)絡(luò)通信原理、TCP、UDP 回顯服務(wù)。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

一、網(wǎng)絡(luò)發(fā)展歷史

互聯(lián)網(wǎng)從何而來?

這要追溯到上個(gè)世紀(jì) 50 - 60 年代,當(dāng)時(shí)正逢美蘇爭霸冷戰(zhàn),核武器給戰(zhàn)爭雙方提供了足夠的威懾力,想要保全自己,就要保證自己的反制手段是有效的。

如何保證能夠反擊:

  1. 保存指揮機(jī)構(gòu)
  2. 保存核彈頭和發(fā)射井
  3. 指揮機(jī)構(gòu)和核彈頭之間的通信鏈路
    需要保證通信鏈路在核彈洗地的情況下仍然能正常運(yùn)作
    最終方案,以力破巧!讓指揮機(jī)構(gòu)和核彈頭之間,有無數(shù)條可以通信的鏈路,哪怕其中一部分被打掉了,剩余的仍然能夠正常工作,從而衍生出了今天的互聯(lián)網(wǎng)。

中國互聯(lián)網(wǎng)的發(fā)展是非常滯后的,90年代左右,國內(nèi)的計(jì)算機(jī)才逐漸多了起來,隨著計(jì)算機(jī)和網(wǎng)絡(luò)的普及,中國這個(gè)十億級(jí)別的市場開始爆發(fā)整個(gè)互聯(lián)網(wǎng)行業(yè)出現(xiàn)井噴式發(fā)展。

2007年,國外出現(xiàn)了一件驚天動(dòng)地的大事,喬布斯發(fā)布了第一代蘋果手機(jī) lPhone,手機(jī)從功能機(jī)向智能機(jī)轉(zhuǎn)變。

智能手機(jī)對(duì)于國內(nèi)的影響,其實(shí)大概是2012年左右才開始,這個(gè)時(shí)候國內(nèi)的智能手機(jī)才逐漸普及。
2012年以后,互聯(lián)網(wǎng)行業(yè)迎來了第二波發(fā)展高峰:移動(dòng)互聯(lián)網(wǎng)。


二、網(wǎng)絡(luò)通信基礎(chǔ)

1、局域網(wǎng) / 廣域網(wǎng)

Local Area Network,簡稱LAN。
Local 即標(biāo)識(shí)了局域網(wǎng)是本地,局部組建的一種私有網(wǎng)絡(luò)

局域網(wǎng)內(nèi)的主機(jī)之間能方便的進(jìn)行網(wǎng)絡(luò)通信,又稱為內(nèi)網(wǎng);局域網(wǎng)和局域網(wǎng)之間在沒有連接的情況下,是無法通信的。

兩根線把三個(gè)主機(jī)給連起來,這三個(gè)主機(jī)就構(gòu)成了一個(gè)局域網(wǎng)。

局域網(wǎng)組建網(wǎng)絡(luò)的方式有很多種,咱們?nèi)粘J褂玫碾娔X一般都是一個(gè)網(wǎng)口,但是也有的主機(jī)是帶有多個(gè)網(wǎng)口的,這種組網(wǎng)方式是非常少見 (非常費(fèi)網(wǎng)線,也非常費(fèi)網(wǎng)口)
一般組件局域網(wǎng),都會(huì)使用一些轉(zhuǎn)發(fā)設(shè)備:交換機(jī),路由器

  • 交換機(jī)

    • 借助交換機(jī),就組成了一個(gè)局域網(wǎng),交換機(jī)上面的網(wǎng)口之間都是對(duì)等 (都是─樣的口)
      效果就是把插在上面的設(shè)備給組建成一個(gè)局域網(wǎng),這個(gè)局域網(wǎng)內(nèi)部的主機(jī)之間就可以相互進(jìn)行訪問
    • 交換機(jī)是把若干個(gè)設(shè)備給組建到一個(gè)局域網(wǎng)中
  • 路由器

    • 這個(gè)是咱們?nèi)粘V凶畛R姷那闆r。路由器這里其實(shí)有兩類端口,
      WAN 口
      LAN 口
      其中插在 LAN 口上的設(shè)備,在一個(gè)局域網(wǎng)里,通過 wan 口連接到另外一個(gè)局域網(wǎng)
    • 路由器則是連接了兩個(gè)局域網(wǎng) (LAN口是一個(gè),WAN又連了一個(gè))
  • 集線器

    • 實(shí)際上基本沒有使用集線器組網(wǎng)的,集線器相當(dāng)于把一根網(wǎng)線給分叉
    • 分出來的兩個(gè)叉不能一起用,用一個(gè)的時(shí)候另一個(gè)就不好使

上述討論的區(qū)別,局限于 "傳統(tǒng)”,的交換機(jī)和路由器。

實(shí)際上,真實(shí)的交換機(jī)和路由器之間的界限,已經(jīng)越來越模糊了,路由器的很多功能,交換機(jī)也有,交換機(jī)的很多功能,路由器也有
通過路由器 / 交換機(jī),組建起來的這些都叫做局域網(wǎng)
廣域網(wǎng)其實(shí)和局域網(wǎng)之間,沒有明確界限認(rèn)為比較大的局域網(wǎng),就可以稱為 “廣域網(wǎng)”。

全世界最大的廣域網(wǎng),叫做 Internet (因特網(wǎng))


2、IP地址 & 端口號(hào)

IP 地址:描述了網(wǎng)絡(luò)上的一個(gè)主機(jī)的位置 (收貨地址)

IP地址本質(zhì)上是一個(gè) 32 位的整數(shù),但是由于32位的整數(shù),不方便人來讀和記憶,一般常見的操作都是把這個(gè) 32 位的整數(shù),按照每個(gè)字節(jié),分成四個(gè)部分,中間用 . 分割,稱為 點(diǎn)分十進(jìn)制 。
例如:123.139.170.225,范圍是 0-255。
127.0.0.1 (一個(gè)特殊的IP地址,環(huán)回IP,表示自己這個(gè)主機(jī))

端口號(hào):描述了一個(gè)主機(jī)上的某個(gè)應(yīng)用程序 (收件人的電話)
端口號(hào)本質(zhì)上是一個(gè) 2 個(gè)字節(jié) (16位) 的無符號(hào)整數(shù),范圍 0-65535
例如:3306,MySQL 默認(rèn)的端口號(hào)
服務(wù)器程序在啟動(dòng)的時(shí)候,就需要綁定上一個(gè)端口號(hào),以便客戶端程序來訪問


3、協(xié)議

3.1、協(xié)議的概念

進(jìn)行有效的通信,前提就是能夠明確通信協(xié)議。本質(zhì)上就是約定,發(fā)出來的數(shù)據(jù)是什么的格式,接收方按照對(duì)應(yīng)的格式來進(jìn)行解析

網(wǎng)絡(luò)通信的時(shí)候,本質(zhì)上,傳輸?shù)氖枪庑盘?hào)和電信號(hào)

  • 通過光信號(hào)的頻率 (高頻率 / 低頻率),電信號(hào)的電平 (高電平 / 低電平),來表示 0 和 1。

關(guān)于協(xié)議分層

網(wǎng)絡(luò)通信這個(gè)過程,其實(shí)很復(fù)雜,里面有很多很多的細(xì)節(jié),
如果就只通過一個(gè)協(xié)議,來約定所有的細(xì)節(jié),這個(gè)協(xié)議就會(huì)非常龐大,復(fù)雜,
更好的辦法,就是把一個(gè)大的復(fù)雜的協(xié)議,拆成多個(gè)小的,更簡單的協(xié)議,每個(gè)協(xié)議,負(fù)責(zé)一部分工作
(就和寫代碼一樣,寫一個(gè)復(fù)雜的程序,不能指望說,一個(gè)文件把所有的代碼都裝進(jìn)去,把這個(gè)代碼拆分成多個(gè)更小的,更簡單的文件,每個(gè)文件負(fù)責(zé)一部分工作)

  • 好處1:每層協(xié)議不需要理解其他層協(xié)議的細(xì)節(jié) (更好的做到了封裝)
    打電話的人,不需要理解電話的工作原理,就能完成打電話的操作,制造電話的人,也不需要稱為語言大師
  • 好處2:對(duì)應(yīng)層的協(xié)議替換成其他協(xié)議 (更好的解耦合)
    打電話的人,可以不使用有線電話,可以使用無線電話
    打電話的人,也可以使用英語,不使用漢語

互聯(lián)網(wǎng)中的分層具體怎么分:

OSI 七層網(wǎng)絡(luò)模型

  • 這種模型只是存在于教科書中,真實(shí)的情況是 OSI 的簡化版本:

TCP / IP 五層 (四層) 網(wǎng)絡(luò)模型

  • 站在一個(gè)全局的角度,五層模型
    站在純程序猿的角度,最下面的物理層描述的是硬件設(shè)備 (和軟件沒啥關(guān)系,和程序猿距離比較遠(yuǎn)) 這個(gè)時(shí)候就認(rèn)為是四層

  • 下面四層都是一樣的,這四層,和咱們程序猿的關(guān)系都不是很大,這里的代碼邏輯都是由操作系統(tǒng)和驅(qū)動(dòng)以及硬件已經(jīng)實(shí)現(xiàn)好的

    程序猿打交道最多的,是這個(gè)應(yīng)用層的協(xié)議


3.2、TCP五層網(wǎng)絡(luò)模型

1、物理層: 網(wǎng)絡(luò)通信中的硬件設(shè)備

通信需要網(wǎng)線 / 網(wǎng)卡… 針對(duì)硬件設(shè)備的約定,就是物理層協(xié)議所負(fù)責(zé)的范疇,需要保證所有的主機(jī)和網(wǎng)絡(luò)設(shè)備之間,都是相互匹配的,隨便買一個(gè)路由器都可以插我的網(wǎng)線

2、數(shù)據(jù)鏈路層: 負(fù)責(zé)完成相鄰 (一根網(wǎng)線相連的兩個(gè)設(shè)備) 的兩個(gè)設(shè)備之間的通信的 [局部]
如果一個(gè)路由器連接了兩個(gè)主機(jī),路由器 和 主機(jī) 1 是相鄰的,路由器和主機(jī) 2 是相鄰的,主機(jī) 1 和主機(jī) 2 不是相鄰的

3、網(wǎng)絡(luò)層: 負(fù)責(zé)點(diǎn)到點(diǎn)之間的通信 [全局]
網(wǎng)絡(luò)中的任意節(jié)點(diǎn),到任意節(jié)點(diǎn)之間的通信 (不一定是相鄰了,更多的是指不相鄰的),網(wǎng)絡(luò)層就負(fù)責(zé)在這兩個(gè)點(diǎn)之間,規(guī)劃出一條合適的路線
實(shí)際的網(wǎng)絡(luò)環(huán)境結(jié)構(gòu)非常復(fù)雜,兩個(gè)點(diǎn)之間的路線不只一條,就需要規(guī)劃處最合適的一條 [高德地圖為你導(dǎo)航]

舉個(gè)例子: 從西安到吉林省白城市安廣鎮(zhèn),首先,規(guī)劃路線
1.西安 -> 北京 -> 白城 -> 安廣
2.西安 -> 長春 -> 白城 -> 安廣
3.西安 -> 沈陽 -> 白城 -> 安廣

我就需要規(guī)劃哪一條路線最優(yōu) (最優(yōu)可能是指,時(shí)間最短,也可能是指成本最低,還可能是少換乘)

網(wǎng)絡(luò)層負(fù)責(zé)這個(gè)事情,網(wǎng)絡(luò)層允許用戶根據(jù)情況來決定哪種是 “最優(yōu)”。

假設(shè)我路線規(guī)劃好了
西安 -> 長春 -> 白城 -> 安廣
接下來就考慮具體如何實(shí)施,先考慮西安到長春,決定坐飛機(jī),到了長春了,再考慮如何到白城,決定坐火車,到白城了,考慮如何去安廣,決定坐大巴車,到了安廣,考慮如何到家里,決定坐毛驢車

這個(gè)過程是數(shù)據(jù)鏈路層負(fù)責(zé)的工作

4、傳輸層: 負(fù)責(zé)端到端(起點(diǎn)和終點(diǎn)) 之間的通信
只是關(guān)注結(jié)果 (數(shù)據(jù)到?jīng)]到),不關(guān)注過程 (不關(guān)注數(shù)據(jù)是走哪條路,轉(zhuǎn)發(fā)的)

例如我網(wǎng)上購物,我就需要填寫自己的收件人地址和收件人姓名,商家就要根據(jù)這個(gè)地址把快遞發(fā)給我
我和商家,都是只關(guān)注結(jié)果,不關(guān)注過程
快遞公司,要關(guān)注中間的過程

5、應(yīng)用層: 和應(yīng)用程序密切相關(guān)的,你傳輸?shù)倪@個(gè)數(shù)據(jù),是跟什么用的
不同的應(yīng)用程序就有不同的用途

舉個(gè)例子:有一天我在網(wǎng)上買一個(gè)床刷子
商家,站在傳輸層,考慮這個(gè)東西是能不能發(fā)到我手上??爝f公司,站在網(wǎng)絡(luò)層規(guī)劃路線??爝f小哥,站在數(shù)據(jù)鏈路層,騎著電動(dòng)車把貨拉到集散中心。電動(dòng)車 / 集裝箱卡車 / 公路,站在物理層,提供傳輸?shù)幕A(chǔ)。
他們都是只在考慮包裹如何傳輸,不考慮這個(gè)包裹里面是什么,更不關(guān)心包裹里的東西的作用。但是我,作為買床刷子的人,就是抱著一定的用途 / 目的,來買的,這個(gè)是程序猿最最需要打交道的事情

網(wǎng)絡(luò)設(shè)備所在分層 (傳統(tǒng)意義上的路由器和交換機(jī))

  • 一臺(tái)主機(jī),其實(shí)就對(duì)應(yīng)了物理層到應(yīng)用層五層 (把這五層都給實(shí)現(xiàn)了)
  • 一臺(tái)路由器,主要就是物理層到網(wǎng)絡(luò)層(主要是實(shí)現(xiàn)了物理層,數(shù)據(jù)鏈路層,網(wǎng)絡(luò)層)
  • 一臺(tái)交換機(jī),主要就是物理層到數(shù)據(jù)鏈路層 (主要是實(shí)現(xiàn)了物理層,數(shù)據(jù)鏈路層)

4、封裝,分用

4.1、封裝

網(wǎng)絡(luò)分層中的一組重要概念,封裝分用,(此處的 “封裝”,和 Java 面向?qū)ο螅胺庋b繼承多態(tài)” 的封裝,沒什么關(guān)系)
不同的分層的協(xié)議之間,是如何相互配合的

例如,使用 QQ 給一個(gè)同學(xué)發(fā)送消息,用戶 A 在鍵盤上輸入了一個(gè)"hello",按下發(fā)送鍵

應(yīng)用層 (QQ應(yīng)用程序)

  • 根據(jù)用戶輸入的內(nèi)容,把數(shù)據(jù)構(gòu)造成一個(gè)應(yīng)用層的協(xié)議報(bào)文協(xié)議是一種約定,報(bào)文遵守了這個(gè)約定的一組數(shù)據(jù)
  • QQ 的代碼中就會(huì)根據(jù)程序猿所設(shè)計(jì)的應(yīng)用層協(xié)議,來構(gòu)造出一個(gè) 應(yīng)用層的數(shù)據(jù)報(bào)文
    • 這個(gè)協(xié)議長啥樣?都是程序猿自己約定的。QQ使用的應(yīng)用層協(xié)議,是開發(fā)QQ的程序猿約定的;LOL使用的應(yīng)用層協(xié)議,是開發(fā)LOL的程序猿;約定的淘寶使用的應(yīng)用層協(xié)議,是開發(fā)淘寶的程序猿約定的。顯然這些不同程序中使用的應(yīng)用層協(xié)議大概率是不相同的,QQ之外的人,是不知道 QQ 使用的應(yīng)用層協(xié)議是什么的。
    • (其他的傳輸層、網(wǎng)絡(luò)層… 的協(xié)議都是現(xiàn)成,操作系統(tǒng) / 硬件 / 驅(qū)動(dòng)已經(jīng)實(shí)現(xiàn)好的),應(yīng)用層的協(xié)議大概率是程序猿自己設(shè)定的。
  • 應(yīng)用層協(xié)議就調(diào)用操作系統(tǒng)提供的API (稱為socket API),把應(yīng)用層的數(shù)據(jù),交給傳輸層 (就已經(jīng)進(jìn)入操作系統(tǒng)內(nèi)核了)、

【Java 網(wǎng)絡(luò)編程】網(wǎng)絡(luò)通信原理、TCP、UDP 回顯服務(wù)

傳輸層 (操作系統(tǒng)內(nèi)核)

根據(jù)剛才傳過來的數(shù)據(jù),基于當(dāng)前使用的傳輸層協(xié)議,來構(gòu)造出一個(gè)傳輸層的協(xié)議報(bào)文
傳輸層最典型的協(xié)議:UDP,TCP。以 TCP 為例:

  • 在應(yīng)用層數(shù)據(jù)的基礎(chǔ)上加上一個(gè) TCP 的協(xié)議報(bào)頭
    也就是說 TCP 的數(shù)據(jù)報(bào) = TCP報(bào)頭+數(shù)據(jù)載荷 (Payload,也就是一個(gè)完整的應(yīng)用層數(shù)據(jù))
    • 可以簡單的把這個(gè)構(gòu)造 TCP 報(bào)文的過程視為是一個(gè)字符串拼接 (這里拼的是二進(jìn)制數(shù)據(jù))
  • TCP的報(bào)頭中有很多信息
    其中最重要的,就是 “源端口” 和 “目的端口”,也就是發(fā)件人電話和收件人電話
  • 應(yīng)用層和傳輸層的過程就是封裝,類似于快遞打包。
    打包的目的,一方面是為了保護(hù)衣服,不被弄壞弄臟;另一方面,是為了往上面貼標(biāo)簽,標(biāo)簽上就有轉(zhuǎn)發(fā)數(shù)據(jù)的重要輔助信息。
    網(wǎng)絡(luò)中的封裝,不需要考慮 “數(shù)據(jù)弄壞弄臟的問題”。這里主要的目的就是為了“貼標(biāo)簽",貼上輔助轉(zhuǎn)發(fā)的信息。
  • 接下來就會(huì)把這個(gè)傳輸層的數(shù)據(jù)報(bào),交給網(wǎng)絡(luò)層

【Java 網(wǎng)絡(luò)編程】網(wǎng)絡(luò)通信原理、TCP、UDP 回顯服務(wù)

網(wǎng)絡(luò)層 (操作系統(tǒng)內(nèi)核)

  • 拿到了完整的傳輸層數(shù)據(jù)報(bào),就會(huì)再根據(jù)當(dāng)前使用的網(wǎng)絡(luò)層協(xié)議 (例如IP),再次進(jìn)行封裝,把 TCP 數(shù)據(jù)報(bào)構(gòu)造成 IP 數(shù)據(jù)報(bào),還是添加上一個(gè) IP 協(xié)議報(bào)頭
    IР 數(shù)據(jù)報(bào) = IP 協(xié)議報(bào)頭+載荷 (完整的 TCP / UDP 的數(shù)據(jù)報(bào))
  • 這個(gè)報(bào)頭中也有很多重要的信息
    其中最重要的就是 源IP目的IP,相當(dāng)于發(fā)件人的地址,和收件人的地址
    緊接著,當(dāng)前的網(wǎng)絡(luò)層協(xié)議,就會(huì)把這個(gè) IР數(shù)據(jù)報(bào),交給數(shù)據(jù)鏈路層

【Java 網(wǎng)絡(luò)編程】網(wǎng)絡(luò)通信原理、TCP、UDP 回顯服務(wù)

數(shù)據(jù)鏈路層 (驅(qū)動(dòng)程序)

  • 在剛才的 IP數(shù)據(jù)報(bào)基礎(chǔ)上,根據(jù)當(dāng)前使用的數(shù)據(jù)鏈路層的協(xié)議,給構(gòu)造成一個(gè) 數(shù)據(jù)鏈路層的數(shù)據(jù)報(bào) ,就是加上幀頭和幀尾。
    典型的數(shù)據(jù)鏈路層的協(xié)議,叫做 “以太網(wǎng)”,就會(huì)構(gòu)造成一個(gè) “以太網(wǎng)數(shù)據(jù)幀”。
    以太網(wǎng)數(shù)據(jù)幀 = 幀頭+I(xiàn)P數(shù)據(jù)報(bào)+幀尾

  • 幀頭里也有很都重要的信息
    最重要的信息,接下來要傳給的設(shè)備的地址是什么

  • IР協(xié)議 里面寫的地址,是起點(diǎn)和終點(diǎn) (西安和安廣鎮(zhèn))
    以太網(wǎng)數(shù)據(jù)幀,幀頭里,寫的地址,是接下來一個(gè)相鄰節(jié)點(diǎn)的地址 (西安和長春),隨著數(shù)據(jù)往下一個(gè)設(shè)備轉(zhuǎn)發(fā),幀頭中的地址,一直在時(shí)刻發(fā)生改變。
    我人在西安,這里的地址,寫的是西安 / 長春
    我人在長春,這里的地址,寫的是長春 / 白城
    我人在白城,這里的地址,寫的是白城 / 安廣

  • 數(shù)據(jù)鏈路層,又會(huì)把這個(gè)數(shù)據(jù)交個(gè)物理層

【Java 網(wǎng)絡(luò)編程】網(wǎng)絡(luò)通信原理、TCP、UDP 回顯服務(wù)

物理層 (硬件設(shè)備)

  • 做的工作就是,根據(jù)剛才的以太網(wǎng)數(shù)據(jù)幀 (其實(shí)就是一組 0 1 ),把這里的 0 1 變成高低電平,通過網(wǎng)線傳輸出去?;蛘甙堰@里的 0 1 變成高頻 / 低頻的電磁波,通過光纖 / 無線的方式傳播出去。

以上都是封裝,從上往下,就是數(shù)據(jù)從上層協(xié)議,交給下層協(xié)議,由下層協(xié)議進(jìn)行封裝 (構(gòu)造成該層協(xié)議的報(bào)文


4.2、分用

到了剛才這一步,此時(shí)數(shù)據(jù)就已經(jīng)離開了當(dāng)前主機(jī),前往了下一個(gè)設(shè)備,下一個(gè)設(shè)備可能是路由器 / 交換機(jī) / 其他設(shè)備

A 和 B 之間,大概率不是網(wǎng)線直連的,中間就有很多個(gè)路由器和交換機(jī)來負(fù)責(zé)數(shù)據(jù)的轉(zhuǎn)發(fā)
中間的過程暫且不表,主要先看,數(shù)據(jù)到達(dá) B 之后的表現(xiàn)

物理層 (硬件設(shè)備,網(wǎng)卡)

  • 主機(jī) B 的網(wǎng)卡感知到了一組高低電平,然后就會(huì)把這些電平翻譯成 0 1 的一串?dāng)?shù)據(jù),然后這一串 0 1 就是一個(gè)完整的以太網(wǎng)數(shù)據(jù)幀
    物理層就把這個(gè)數(shù)據(jù)往上交給了數(shù)據(jù)鏈路層

數(shù)據(jù)鏈路層 (驅(qū)動(dòng)程序)

  • 數(shù)據(jù)鏈路層負(fù)責(zé)對(duì)這個(gè)數(shù)據(jù)進(jìn)行解析,去掉幀頭和幀尾,
    取出里面的 IP數(shù)據(jù)報(bào),然后交給網(wǎng)絡(luò)層協(xié)議

【Java 網(wǎng)絡(luò)編程】網(wǎng)絡(luò)通信原理、TCP、UDP 回顯服務(wù)

網(wǎng)絡(luò)層 (操作系統(tǒng)內(nèi)核)

  • 網(wǎng)絡(luò)層協(xié)議 (IP 協(xié)議) 又會(huì)對(duì)這個(gè)數(shù)據(jù)進(jìn)行解析,去掉 IP 協(xié)議報(bào)頭
    取出里面的 TCP 數(shù)據(jù)報(bào)再交給傳輸層

【Java 網(wǎng)絡(luò)編程】網(wǎng)絡(luò)通信原理、TCP、UDP 回顯服務(wù)

傳輸層 (操作系統(tǒng)內(nèi)核)

  • 傳輸層協(xié)議 (TCP 協(xié)議) 又會(huì)對(duì)這個(gè)數(shù)據(jù)進(jìn)行解析,去掉 TCP 報(bào)頭,
    取出里面的 TCP 數(shù)據(jù)報(bào),交給應(yīng)用層(QQ)

【Java 網(wǎng)絡(luò)編程】網(wǎng)絡(luò)通信原理、TCP、UDP 回顯服務(wù)

應(yīng)用層 (應(yīng)用程序,QQ)

  • 應(yīng)用層就會(huì)調(diào)用 socket API,從內(nèi)核中讀取到這個(gè)應(yīng)用層數(shù)據(jù)報(bào),再按照應(yīng)用層協(xié)議進(jìn)行解析
    根據(jù)解析結(jié)果給顯示到窗口中心

【Java 網(wǎng)絡(luò)編程】網(wǎng)絡(luò)通信原理、TCP、UDP 回顯服務(wù)

以上是分用,分用就是封裝的逆過程
封裝是從上往下,數(shù)據(jù)依次被加上了協(xié)議報(bào)頭 (包快遞)
分用是從下往上,數(shù)據(jù)一次被去掉了協(xié)議報(bào)頭 (拆快遞)

上述討論的只是起點(diǎn)和終點(diǎn)的情況,A 和 B 中間還有很多路由器和交換機(jī)

交換機(jī)先分用數(shù)據(jù)解析到數(shù)據(jù)鏈路層,更新以太網(wǎng)數(shù)據(jù)幀的幀頭里的地址,然后再重新封裝,并進(jìn)行轉(zhuǎn)發(fā)

【Java 網(wǎng)絡(luò)編程】網(wǎng)絡(luò)通信原理、TCP、UDP 回顯服務(wù)

路由器先分用數(shù)據(jù)到網(wǎng)絡(luò)層,拿到 IP 地址之后,進(jìn)行下一階段的路徑規(guī)劃,然后重新往下封裝,并進(jìn)行轉(zhuǎn)發(fā)

【Java 網(wǎng)絡(luò)編程】網(wǎng)絡(luò)通信原理、TCP、UDP 回顯服務(wù)

A 和 B 之間有多少個(gè)路由器或交換機(jī),無論網(wǎng)絡(luò)多么復(fù)雜,這里整體的傳輸過程都是類似的,只是在不停地重復(fù)封裝和分用的過程

【Java 網(wǎng)絡(luò)編程】網(wǎng)絡(luò)通信原理、TCP、UDP 回顯服務(wù)


三、網(wǎng)絡(luò)編程基本概念

為什么需要網(wǎng)絡(luò)編程

用戶在瀏覽器中,打開在線視頻網(wǎng)站,如優(yōu)酷看視頻,實(shí)質(zhì)是通過網(wǎng)絡(luò),獲取到網(wǎng)絡(luò)上的一個(gè)視頻資源

與本地打開視頻文件類似,只是視頻文件這個(gè)資源的來源是網(wǎng)絡(luò)。
相比本地資源來說,網(wǎng)絡(luò)提供了更為豐富的網(wǎng)絡(luò)資源

網(wǎng)絡(luò)資源:所謂的網(wǎng)絡(luò)資源,其實(shí)就是在網(wǎng)絡(luò)中可以獲取的各種數(shù)據(jù)資源。
而所有的網(wǎng)絡(luò)資源,都是通過網(wǎng)絡(luò)編程來進(jìn)行數(shù)據(jù)傳輸?shù)摹?/p>

網(wǎng)絡(luò)編程,指網(wǎng)絡(luò)上的主機(jī),通過不同的進(jìn)程,以編程的方式實(shí)現(xiàn)網(wǎng)絡(luò)通信(或稱為網(wǎng)絡(luò)數(shù)據(jù)傳輸)

當(dāng)然,我們只要滿足進(jìn)程不同就行;所以即便是同一個(gè)主機(jī),只要是不同進(jìn)程,基于網(wǎng)絡(luò)來傳輸數(shù)據(jù),也屬于網(wǎng)絡(luò)編程。
特殊的,對(duì)于開發(fā)來說,在條件有限的情況下,一般也都是在一個(gè)主機(jī)中運(yùn)行多個(gè)進(jìn)程來完成網(wǎng)絡(luò)編程。
但是,我們一定要明確,我們的目的是提供網(wǎng)絡(luò)上不同主機(jī),基于網(wǎng)絡(luò)來傳輸數(shù)據(jù)資源:

  • 進(jìn)程A:編程來獲取網(wǎng)絡(luò)資源
  • 進(jìn)程B:編程來提供網(wǎng)絡(luò)資源

發(fā)送端和接收端:
在一次網(wǎng)絡(luò)數(shù)據(jù)傳輸時(shí):

  • 發(fā)送端:數(shù)據(jù)的發(fā)送方進(jìn)程,稱為發(fā)送端。發(fā)送端主機(jī)即網(wǎng)絡(luò)通信中的源主機(jī)。

  • 接收端:數(shù)據(jù)的接收方進(jìn)程,稱為接收端。接收端主機(jī)即網(wǎng)絡(luò)通信中的目的主機(jī)。

  • **收發(fā)端:**發(fā)送端和接收端兩端,也簡稱為收發(fā)端。

注意:發(fā)送端和接收端只是相對(duì)的,只是一次網(wǎng)絡(luò)數(shù)據(jù)傳輸產(chǎn)生數(shù)據(jù)流向后的概念

請(qǐng)求和響應(yīng):
一般來說,獲取一個(gè)網(wǎng)絡(luò)資源,涉及到兩次網(wǎng)絡(luò)數(shù)據(jù)傳輸:

  • 第一次:請(qǐng)求數(shù)據(jù)的發(fā)送

  • 第二次:響應(yīng)數(shù)據(jù)的發(fā)送

好比在快餐店點(diǎn)一份炒飯:
先要發(fā)起請(qǐng)求:點(diǎn)一份炒飯,再有快餐店提供的對(duì)應(yīng)響應(yīng):提供一份炒飯

客戶端和服務(wù)端:

  • 服務(wù)端:在常見的網(wǎng)絡(luò)數(shù)據(jù)傳輸場景下,把提供服務(wù)的一方進(jìn)程,稱為服務(wù)端,可以提供對(duì)外服務(wù)。

  • 客戶端獲取服務(wù)的一方進(jìn)程,稱為客戶端。

對(duì)于服務(wù)來說,一般是提供:

  • 客戶端獲取服務(wù)資源
  • 客戶端保存資源在服務(wù)端

好比在銀行辦事:
銀行提供存款服務(wù):用戶(客戶端)保存資源(現(xiàn)金)在銀行(服務(wù)端)
銀行提供取款服務(wù):用戶(客戶端)獲取服務(wù)端資源(銀行替用戶保管的現(xiàn)金)

常見的客戶端服務(wù)端模型:
最常見的場景,客戶端是指給用戶使用的程序,服務(wù)端是提供用戶服務(wù)的程序:

  1. 客戶端先發(fā)送請(qǐng)求到服務(wù)端

  2. 服務(wù)端根據(jù)請(qǐng)求數(shù)據(jù),執(zhí)行相應(yīng)的業(yè)務(wù)處理

  3. 服務(wù)端返回響應(yīng):發(fā)送業(yè)務(wù)處理結(jié)果

  4. 客戶端根據(jù)響應(yīng)數(shù)據(jù),展示處理結(jié)果(展示獲取的資源,或提示保存資源的處理結(jié)果)


四、網(wǎng)絡(luò)編程套接字

Socket套接字,是由系統(tǒng)提供用于網(wǎng)絡(luò)通信的技術(shù),是基于 TCP / IP協(xié)議的網(wǎng)絡(luò)通信的基本操作單元?;?Socket套接字的網(wǎng)絡(luò)程序開發(fā)就是網(wǎng)絡(luò)編程。

Socket 套接字主要針對(duì)傳輸層協(xié)議劃分為如下三類:

流套接字:使用傳輸層 TCP 協(xié)議,TCP,即Transmission Control Protocol(傳輸控制協(xié)議),傳輸層協(xié)議

數(shù)據(jù)報(bào)套接字:使用傳輸層 UDP 協(xié)議
UDP,即User Datagram Protocol(用戶數(shù)據(jù)報(bào)協(xié)議),傳輸層協(xié)議。

原始套接字:原始套接字用于自定義傳輸層協(xié)議,用于讀寫內(nèi)核沒有處理的IP協(xié)議數(shù)據(jù)。
我們不學(xué)習(xí)原始套接字,簡單了解即可。


1、TCP / UDP

網(wǎng)絡(luò)編程套接字,是操作系統(tǒng)給應(yīng)用程序提供的一組 API (叫做 socket API) socket 原義插座

socket 可以視為是應(yīng)用層和傳輸層之間的通信橋梁,
傳輸層 的核心協(xié)議有兩種:TCP,UDP
socket API 也有對(duì)應(yīng)的兩組,由于 TCP 和 UDP 協(xié)議差別很大,因此,這兩組 API 差別也很大

  • TCP:有連接,可靠傳輸,面向字節(jié)流,全雙工
  • UDP:無連接,不可靠傳輸,面向數(shù)據(jù)報(bào),全雙工
  • 有連接:像打電話,得先接通,才能交互數(shù)據(jù)
  • 無連接:像發(fā)微信,不需要接通,直接就能發(fā)數(shù)據(jù)
  • 可靠傳輸:傳輸過程中,發(fā)送方知道接收方有沒有收到數(shù)據(jù)
    打電話,就是可靠傳輸
    阿里旺旺 / 釘釘 / 飛書已讀功能
  • 不可靠傳輸:傳輸過程中,發(fā)送方不知道接收方有沒有收到數(shù)據(jù)
    發(fā)微信,就是不可靠傳輸

錯(cuò)誤的理解
可靠傳輸,就是數(shù)據(jù)發(fā)過去后 100% 能被對(duì)方收到 —— err
可靠傳輸,就是 “安全傳輸” —— err

  • 面向字節(jié)流:以字節(jié)為單位進(jìn)行傳輸 (非常類似于文件操作中的字節(jié)流)
  • 面向數(shù)據(jù)報(bào):以數(shù)據(jù)報(bào)為單位進(jìn)行傳輸 (一個(gè)數(shù)據(jù)報(bào)都會(huì)明確大小),一次發(fā)送 / 接收必須是一個(gè)完整的數(shù)據(jù)報(bào),不能是半個(gè),也不能是一個(gè)半

在代碼中體現(xiàn)地非常明顯

  • 全雙工:一條鏈路,雙向通信
  • 半雙工:一條鏈路,單向通信

TCP,UDP 都是全雙工

以上,是 TCP 和UDP 直觀上的區(qū)別,細(xì)節(jié)上還有很多很多的東西,后面再詳細(xì)介紹


五、UDP socket

UDP socket 中,主要涉及到兩個(gè)類

  • DatagramSocket (Datagram:數(shù)據(jù)報(bào))

    這一個(gè) DatagramSocket 對(duì)象,就對(duì)應(yīng)到操作系統(tǒng)中的一個(gè) socket 文件

    • 操作系統(tǒng)中的 “文件” 是一個(gè)廣義的概念。平時(shí)說的文件。只是指普通文件 (硬盤上的數(shù)據(jù))
      實(shí)際上,操作系統(tǒng)中的文件還可能表示了一些硬件設(shè)備 / 軟件資源
    • socket 文件,就對(duì)應(yīng)這 "網(wǎng)卡” 這種硬件設(shè)備,從 socket 文件讀數(shù)據(jù),本質(zhì)上就是讀網(wǎng)卡,往 socket 文件寫數(shù)據(jù),本質(zhì)上就是寫網(wǎng)卡 。
      你可以想象:socket 文件,就是一個(gè)遙控器,通過遙控器來操作網(wǎng)卡,這種行為非常常見,甚至早在三國時(shí)期,就有了董卓曹操,挾天子以令諸侯,天子就是這個(gè)天下的遙控器
  • DatagramPacket

    代表了一個(gè) UDP 數(shù)據(jù)報(bào),使用 UDP 傳輸數(shù)據(jù)的基本單位。每次發(fā)送 / 接收數(shù)據(jù),都是在傳輸一個(gè) DatagramPacket 對(duì)象。

方法簽名 方法說明
void receive(DatagramPacket p) 從此套接字接收數(shù)據(jù)報(bào)(如果沒有接收到數(shù)據(jù)報(bào),該方法會(huì)阻 塞等待)
void send(DatagramPacket p) 從此套接字發(fā)送數(shù)據(jù)報(bào)包(不會(huì)阻塞等待,直接發(fā)送)
void close() 關(guān)閉此數(shù)據(jù)報(bào)套接字

1、UDP 回顯服務(wù)

1.1、服務(wù)器

寫一個(gè)最簡單的客戶端服務(wù)器程序,回顯服務(wù) EchoServer,這樣的程序?qū)儆谧詈唵蔚木W(wǎng)絡(luò)編程中的程序,不涉及到任何的業(yè)務(wù)邏輯,就只是通過 socket api 單純的轉(zhuǎn)發(fā)

public UdpEchoServer(int port) throws SocketException {
	socket = new DatagramSocket(port);
}

端口號(hào):

  • 此處在構(gòu)造服務(wù)器這邊的 socket 對(duì)象的時(shí)候就需要顯式的綁定一個(gè)端口號(hào)
    端口號(hào)是用來區(qū)分一個(gè)應(yīng)用程序的,主機(jī)收到網(wǎng)卡上的數(shù)據(jù)的時(shí)候這個(gè)數(shù)據(jù)該給哪個(gè)程序?
    port 在運(yùn)行程序的時(shí)候來指定即可

  • 端口號(hào)可以是自己定,也可以讓系統(tǒng)分配
    當(dāng)前這個(gè)寫法,是自己定的,一會(huì)還能看到系統(tǒng)分配的

SocketException:

構(gòu)造socket對(duì)象有很多失敗的可能

  • 端口號(hào)已經(jīng)被占用了,兩個(gè)人不能有相同的電話號(hào)碼,同一個(gè)主機(jī)的兩個(gè)程序也不能有相同的端口號(hào)

  • 多個(gè)進(jìn)程不能綁定同一個(gè)端口
    一個(gè)進(jìn)程能不能綁定多個(gè)端口呢?可以的,一個(gè)人可以有多個(gè)手機(jī)號(hào)碼
    一個(gè)進(jìn)程可以創(chuàng)建多個(gè) socket 對(duì)象,每個(gè) socket 對(duì)象都綁定自己的端口
    如果一個(gè)程序需要使用網(wǎng)路通信,你至少得有一個(gè)端口。如果一個(gè)人需要網(wǎng)購,也得至少有一個(gè)電話號(hào)碼。

  • 每個(gè)進(jìn)程能夠打開的文件個(gè)數(shù),是有上限的。如果進(jìn)程之前已經(jīng)打開了很多很多的文件,就可能導(dǎo)致此處的 socket 文件就不能順利打開

為什么服務(wù)器第一步就是接收客戶端發(fā)來的請(qǐng)求,而不是發(fā)送呢?
因?yàn)椋?wù)器的定義,就是 “被動(dòng)接收請(qǐng)求” 的這一方。主動(dòng)發(fā)送請(qǐng)求的這一方面,叫做客戶端。

receive 方法是可能會(huì)阻塞的! 客戶端什么時(shí)候給服務(wù)器發(fā)請(qǐng)求?不確定的!

package network;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class UdpEchoServer {
    // 1、網(wǎng)絡(luò)編程,第一步就要準(zhǔn)備好 socket 實(shí)例,這是進(jìn)行網(wǎng)絡(luò)編程的大前提
    private DatagramSocket socket = null;

    public UdpEchoServer(int port) throws SocketException {
        // 此處在構(gòu)造服務(wù)器這邊的 socket 對(duì)象的時(shí)候就需要顯式的綁定一個(gè)端口號(hào)
        // port 在運(yùn)行程序的時(shí)候來指定即可
        socket = new DatagramSocket(port);
    }

    // 啟動(dòng)服務(wù)器
    public void start() throws IOException {
        System.out.println("啟動(dòng)服務(wù)器!");
        // UDP 不需要建立連接,直接接收從客戶端來的數(shù)據(jù)即可
        while (true) {
            // 1、讀取客戶端發(fā)來的請(qǐng)求
            //    為了接收數(shù)據(jù),需要先準(zhǔn)備好一個(gè)空的 DatagramPacket 對(duì)象,由 receive 進(jìn)行填充數(shù)據(jù)
            DatagramPacket requestPacket = new DatagramPacket(new byte[1024], 1024); //  把一個(gè)字節(jié)數(shù)組包裝了
            //    參數(shù)為 "輸出型參數(shù)"
            socket.receive(requestPacket);
            //    把 DatagramPacket 解析成一個(gè) String
            //    假設(shè)此處的 UDP 數(shù)據(jù)報(bào)最長是 1024,這個(gè)長度不一定是 1024,實(shí)際的數(shù)據(jù)可能不夠 1024
            String request = new String(requestPacket.getData(), 0, requestPacket.getLength(), "UTF-8");

            // 2、根據(jù)請(qǐng)求計(jì)算響應(yīng)(由于咱們這是一個(gè)回顯服務(wù),2省略)
            String response = process(request);

            // 3、把響應(yīng)寫回到客戶端
            //    send 方法的參數(shù),也是 DatagramPacket,需要把響應(yīng)數(shù)據(jù)先構(gòu)造成一個(gè) DatagramPacket 再進(jìn)行發(fā)送,這里就不是構(gòu)造一個(gè)空的數(shù)據(jù)報(bào)
            //    這里的參數(shù)不再是一個(gè)空的字節(jié)數(shù)組了,response 是剛才根據(jù)請(qǐng)求計(jì)算得到的響應(yīng),非空的 DatagramPacket 里面的數(shù)據(jù)就是String response的數(shù)據(jù)
            //    寫成 response.length() 表示(字符的個(gè)數(shù))。  這里拿到的是字節(jié)數(shù)組的長度(字節(jié)的個(gè)數(shù))

            /*如果代碼光是這么寫,還是不太行,此時(shí)就無法區(qū)分出,這個(gè)數(shù)據(jù)要交給誰了
            在發(fā)送數(shù)據(jù)的時(shí)候,必須要指定,這個(gè)數(shù)據(jù)報(bào)發(fā)給誰?地址 + 電話
            lP + port
            在當(dāng)前的場景中,哪個(gè)客戶端發(fā)來的請(qǐng)求,就把數(shù)據(jù)返回給哪個(gè)客戶端
            進(jìn)之后的版本,在 DatagramPacket構(gòu)造方法中,指定了第三個(gè)參數(shù),表示要把數(shù)據(jù)發(fā)給哪個(gè)地址 + 端口
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length);*/
            // 改進(jìn):
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,
                    requestPacket.getSocketAddress()); // SocketAddress 就可以視為是一個(gè)類,里面包含了 IP 和端口
            socket.send(responsePacket);
            System.out.printf("[%s : %d] req: %s, req: %s\n",
                    requestPacket.getAddress().toString(), requestPacket.getPort(), request, response);
        }
    }

    // 由于是回顯服務(wù),響應(yīng)和請(qǐng)求一樣
    // 實(shí)際上對(duì)于一個(gè)真實(shí)的服務(wù)器來說,這個(gè)過程是最復(fù)雜的,為了實(shí)現(xiàn)這個(gè)過程,可能需要幾萬,幾十萬代碼
    private String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        UdpEchoServer server = new UdpEchoServer(9090);
        server.start();
    }
}

1.2、客戶端

指定端口號(hào)?

public UdpEchoServer(int port) throws SocketException {
    socket = new DatagramSocket(port); // 自己指定
}

public UdpEchoClient() throws SocketException {
    socket = new DatagramSocket(); // 系統(tǒng)隨機(jī)分配
}

第一個(gè)就是你去營業(yè)廳辦理電話卡,自己手動(dòng)挑一個(gè)喜歡的號(hào)碼

在客戶端構(gòu)造 socket 對(duì)象的時(shí)候,就不再手動(dòng)指定端口號(hào),使用無參版本的構(gòu)造方法
不指定端口號(hào),意思是,讓操作系統(tǒng)自己分配一個(gè)空閑的端口號(hào)
這個(gè)操作就是辦電話卡,對(duì)于號(hào)碼無感,人家給你隨機(jī)指定一個(gè)號(hào)碼

通常寫代碼的時(shí)候,服務(wù)器都是手動(dòng)指定的,客戶端都是由系統(tǒng)自動(dòng)指定的 (系統(tǒng)隨機(jī)分配一個(gè))

  • 對(duì)于服務(wù)器來說,必須要手動(dòng)指定,后續(xù)客戶端要根據(jù)這個(gè)端口來訪問到服務(wù)器
    如果讓系統(tǒng)隨機(jī)分配,客戶端就不知道服務(wù)器的端口是啥,不能訪問,

  • 對(duì)于客戶端來說,如果手動(dòng)指定,也行,但是系統(tǒng)隨機(jī)分配更好
    一個(gè)機(jī)器上的兩個(gè)進(jìn)程,不能綁定同一個(gè)端口
    客戶端就是普通用戶的電腦,天知道用戶電腦上都裝了什么程序,天知道用戶的電腦上已經(jīng)被占用了哪些端口,如果你手動(dòng)指定一個(gè)端口,萬一這個(gè)端口被別的程序占用,咱們的程序不就不能正常工作了嘛?
    而且由于客戶端是主動(dòng)發(fā)起請(qǐng)求的一方,客戶端需要在發(fā)送請(qǐng)求之前,先知道服務(wù)器的地址 + 端口,但是反過來在請(qǐng)求發(fā)出去之前,服務(wù)器是不需要事先知道客戶端的地址 + 端口

構(gòu)造方法:

// 法是只構(gòu)造了保存數(shù)據(jù)的空間,沒有數(shù)據(jù)內(nèi)容,也沒有地址~[用于接收]
DatagramPacket requestPacket = new DatagramPacket(new byte[1024], 1024);

// 這種寫法,也是,既構(gòu)造了數(shù)據(jù),有能構(gòu)造目標(biāo)地址.這個(gè)目標(biāo)地址, IP和端口是合在一起的寫法. (InetSocketAddress)[用于發(fā)送]
DatagramPacket responsePacket = new DatagramPacket(response.getBytes(), response.getBytes().length,
                    requestPacket.getSocketAddress());

// 又使用到了一種DatagramPacket構(gòu)造方法.既能構(gòu)造數(shù)據(jù),又能構(gòu)造目標(biāo)地址.這個(gè)目標(biāo)地址是IP和端口分開的寫法~~[用于發(fā)送)
DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
                    InetAddress.getByName("127.0.0.1"), 9090);

五元組:

寫代碼的時(shí)候,就會(huì)涉及到一系列的 ip 和 端口。
一次通信,是由五個(gè)核心信息,描述出來的。源 IP,源端口,目的IP,目的端口,協(xié)議類型。

站在服務(wù)器的角度:

  1. 源IP:服務(wù)器程序本機(jī)的 IP
  2. 源端口:服務(wù)器綁定的端口 (此處手動(dòng)指定了 9090)
  3. 目的 IP:包含在收到的數(shù)據(jù)報(bào)中 (客戶端的 IP)
  4. 目的端口:包含在收到的數(shù)據(jù)報(bào)中 (客戶端的端口)
  5. 協(xié)議類型:UDP

站在客戶端的角度:

  1. 源IP:本機(jī) IP
  2. 源端口:系統(tǒng)分配的端口
  3. 目的IP:服務(wù)器的 IP
  4. 目的端口:服務(wù)器的端口
  5. 協(xié)議類型:UDP
package network;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Scanner;

public class UdpEchoClient {
    private DatagramSocket socket = null;

    private String serverIP;
    private int serverPort;

    public UdpEchoClient(String ip, int port) throws SocketException {
        socket = new DatagramSocket();
        this.serverIP = ip;
        this.serverPort = port;
        // 此處的 port 是服務(wù)器的端口,客戶端啟動(dòng)的時(shí)候,不需要給 socket 指定端口,客戶端自己的端口是系統(tǒng)隨機(jī)分配的
    }

    // 在客戶端構(gòu)造 socket 對(duì)象的時(shí)候,就不再手動(dòng)指定端口號(hào),使用無參版本的構(gòu)造方法
    /*public UdpEchoClient() throws SocketException {
        socket = new DatagramSocket();
    }*/

    public void start() throws IOException {
        Scanner scanner = new Scanner(System.in);
        while (true) {
            // 1、先從控制臺(tái)讀取用戶輸入的字符串
            System.out.print("-> ");
            String request = scanner.next();

            // 2、把這個(gè)用戶輸入的內(nèi)容,構(gòu)造成一個(gè) UDP 請(qǐng)求,并發(fā)送
            //    構(gòu)造的請(qǐng)求里包含兩部分信息
            //    1) 數(shù)據(jù)的內(nèi)容:request 字符串
            //    2) 數(shù)據(jù)要發(fā)給誰:服務(wù)器的 IP + 端口號(hào)
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(), request.getBytes().length,
                    InetAddress.getByName(serverIP), serverPort);
            socket.send(requestPacket);

            // 3、從服務(wù)器讀取響應(yīng)數(shù)據(jù),并解析
            DatagramPacket responsePacket = new DatagramPacket(new byte[1024], 1024);
            socket.receive(responsePacket);
            String response = new String(responsePacket.getData(), 0, responsePacket.getLength(), "UTF-8");

            // 4、把響應(yīng)結(jié)果顯示到控制臺(tái)上
            System.out.printf("request: %s, response: %s\n", request, response);
        }
    }

    public static void main(String[] args) throws IOException {
        // 由于客戶端和服務(wù)器在同一個(gè)機(jī)器上,使用的 IP 仍是 127.0.0.1,如果是不同的機(jī)器,就要修改這里的 IP
        UdpEchoClient client = new UdpEchoClient("127.0.0.1", 9090);
        client.start();
    }
}

早就已經(jīng)把服務(wù)器啟動(dòng)起來了,啟動(dòng)了服務(wù)器之后,才開始寫客戶端代碼的,在寫客戶端代碼的這個(gè)過程中,顯然,沒人訪問服務(wù)器的,
服務(wù)器其實(shí)就卡在 receive 這里,阻塞等待了。

啟動(dòng)服務(wù)器!
[/127.0.0.1 : 63140] request: hello, response: hello

63140:這個(gè)就是系統(tǒng)自動(dòng)給客戶端分配的端口

客戶端是可以有很多的
一個(gè)服務(wù)器可以給很多很多客戶端提供服務(wù),一個(gè)餐館,可以給很多很多的客人提供就餐服務(wù)的

取決于服務(wù)器的能力,同一時(shí)刻服務(wù)器能夠處理的客戶端的數(shù)目存在上限的,
服務(wù)器處理每個(gè)請(qǐng)求,都需要消耗一定的硬件資源 (包括不限于,CPU,內(nèi)存,磁盤,帶寬…)
能處理多少客戶端,取決于:

  1. 處理一個(gè)請(qǐng)求,消耗多少資源

  2. 機(jī)器一共有多少資源能用

(在 Java 中并不容易精確的計(jì)算消耗多少資源,,JVM 里面有很多輔助性的功能,也要消耗額外的資源)

實(shí)際開發(fā)中,通過性能測試的方式,就知道了能有多少個(gè)客戶端

問題:

當(dāng)我們像再啟動(dòng)一個(gè)客戶端的時(shí)候,遇到了點(diǎn)小困難,idea 提示咱們要把上一個(gè)客戶端給干掉

‘UdpEchoClient’ is not allowed to run in parallel.
Would you like to stop the running one?

IDEA 中默認(rèn)情況下,一個(gè)程序只能啟動(dòng)一個(gè)實(shí)例.再次啟動(dòng)就會(huì)干掉之前的實(shí)例,此處勾選上這個(gè)選項(xiàng),就可以啟動(dòng)多個(gè)實(shí)例了
【Java 網(wǎng)絡(luò)編程】網(wǎng)絡(luò)通信原理、TCP、UDP 回顯服務(wù)

[/127.0.0.1y.S0368yreq: hello, resp: hello
[/127.0.e.1: 598o2]req: java,resp: java

每個(gè)客戶端,都被系統(tǒng)分配了不同的端口:通常情況下,一個(gè)服務(wù)器,是要同時(shí)給多個(gè)客戶端提供服務(wù)的
但是也有情況,就是一個(gè)服務(wù)器只給一個(gè)客戶端提供服務(wù) (典型就是在分布式系統(tǒng)中,兩個(gè)節(jié)點(diǎn)之間的交互)

上述寫的代碼雖然只是針對(duì)一個(gè)簡單的回顯服務(wù),但是對(duì)于一個(gè)復(fù)雜的服務(wù)器來說,做的工作的基本流程,也是類似的


2、UDP 翻譯

再來寫一個(gè)簡單程序,帶上點(diǎn)業(yè)務(wù)邏輯,寫一個(gè)翻譯程序 (英譯漢)
請(qǐng)求是一些簡單的英文單詞,響應(yīng)也就是英文單詞對(duì)應(yīng)的翻譯
客戶端不變,把服務(wù)器代碼進(jìn)行調(diào)整
主要是調(diào)整 process 方法
讀取請(qǐng)求并解析,把響應(yīng)寫回給客戶端,這倆步驟都一樣,關(guān)鍵的邏輯就是 “根據(jù)請(qǐng)求處理響應(yīng)”

package network;

import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;

public class UdpDictServer extends UdpEchoServer {
    public HashMap<String, String> dict = new HashMap<>();

    public UdpDictServer(int port) throws SocketException {
        super(port);
        // 簡單構(gòu)造幾個(gè)詞
        dict.put("cat", "貓");
        dict.put("dog", "狗");
        dict.put("pig", "豬");
    }

    @Override
    public String process(String request) {
        // UdpEchoServer 中的 process 改成 public
        return dict.getOrDefault(request, "該詞無法被翻譯!");
    }

    public static void main(String[] args) throws IOException {
        UdpDictServer server = new UdpDictServer(9090);
        server.start();
    }
}

六、TCP

1、TCP 回顯服務(wù)

1.1、服務(wù)器

TCP api 中,也是涉及到兩個(gè)核心的類

  • ServerSocket (專門給 TCP 服務(wù)器用的)
  • Socket (既需要給服務(wù)器用,又需要給客戶端用)

主要通過這樣的類,來描述一個(gè) socket 文件即可,而不需要專門的類表示 “傳輸?shù)陌?,面向字?jié)流,以字節(jié)為單位傳輸?shù)?/p>

package network;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class TcpEchoServer {
    // listen 英文原意:監(jiān)聽。但是 Java socket 中體現(xiàn)出 "監(jiān)聽" 的含義,
    // 這樣叫是因?yàn)?,操作系統(tǒng)原生的 API 中,有一個(gè)操作叫做 listen
    // private ServerSocket listenSocket = null;
    private ServerSocket serverSocket = null;

    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服務(wù)器啟動(dòng)了!");
        while (true) {
            // 1、建立連接
            // 由于 TCP 是有連接的,不能一上來就讀數(shù)據(jù),需要先建立連接 (接電話)
            // accept 就是在接電話,接電話的前提是,有人給你打,如果當(dāng)前客戶端嘗試建立連接,此處的 accept 就會(huì)阻塞
            // accept 返回了一個(gè) socket 對(duì)象,稱為 clientSocket,后續(xù)和客戶端之間的溝通,都是都過 clientSocket 來完成的
            Socket clientSocket  = serverSocket.accept();

            // 2、處理連接
            // 這里之所分成了兩步 就是因?yàn)橐⑦B接 一個(gè)專門負(fù)責(zé)建立連接 一個(gè)專門負(fù)責(zé)數(shù)據(jù)通信
            processConnection(clientSocket);
        }
    }

    // 處理連接
    private void processConnection(Socket clientSocket) {
        System.out.printf("[%s : %d] 客戶端建立連接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
        // 接下來處理請(qǐng)求和響應(yīng)
        try (InputStream inputStream = clientSocket.getInputStream()) {
            try (OutputStream outputStream = clientSocket.getOutputStream()) {
                // 把 inputStream 中的數(shù)據(jù)讀出來,寫入到 outputStream 中
                // 循環(huán)地處理每個(gè)請(qǐng)求,分別返回響應(yīng)
                Scanner scanner = new Scanner(inputStream);
                while (true) {
                    // 1、讀取請(qǐng)求
                    if (!scanner.hasNext()) {
                        System.out.printf("[%s : %d] 客戶端斷開連接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
                        break;
                    }
                    String request = scanner.next(); // 此處用 Scanner 更方便,如果用 InputStream 的 read 也可以

                    // 2、根據(jù)請(qǐng)求,計(jì)算響應(yīng)
                    String response = process(request);

                    // 3、將這個(gè)響應(yīng)返回客戶端
                    //    方便起見,用 PrintWriter 把 OutputStream 包裹一下
                    PrintWriter printWriter = new PrintWriter(outputStream);
                    printWriter.println(response);
                    //  刷新,如果沒有這個(gè)刷新,可能客戶端就不能第一時(shí)間看到響應(yīng)結(jié)果
                    printWriter.flush();

                    System.out.printf("[%s : %d] req : %s, resp: %s!\n", clientSocket.getInetAddress().toString(),
                            clientSocket.getPort(), request, response);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 記得關(guān)閉!
            try {
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        TcpEchoServer server = new TcpEchoServer(9090);
        server.start();
    }
}

在上述代碼中,針對(duì)這里的 clientSocket 特意關(guān)閉了一下,但是對(duì)于 ServerSocket 就沒有關(guān)閉,同理 UDP 版本的代碼里,也沒有針對(duì) socket的關(guān)閉,為什么?
關(guān)閉的目的是為了 “釋放資源” ,釋放資源的前提,是已經(jīng)不再使用這個(gè)資源了,

對(duì)于 UDP 的程序和 serversocket 來說,這些 socket 都是貫穿程序始終的,
這些資源最遲最遲,也就是跟隨進(jìn)程的退出一起釋放了 (進(jìn)程才是系統(tǒng)分配資源的基本單位)

clientSocket 這個(gè)是每個(gè)連接有一個(gè)的一,數(shù)目很多,連接斷開,也就不再需要了
每次都得保證處理完的連接都給進(jìn)行釋放


1.2、客戶端

對(duì)于 UDP 的 DatagramSocket 來說,構(gòu)造方法指定的端口,表示自己綁定哪個(gè)端口
對(duì)于 TCP 的ServerSocket 來說,構(gòu)造方法指定的端口,也是表示自己綁定哪個(gè)端口
對(duì)于 TCP 的 Socket 來說,構(gòu)造方法指定的端口,表示要連接的服務(wù)器的端口,要和哪一個(gè)服務(wù)器上的哪一個(gè)端口建立連接

package network;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

public class TcpEchoClient {
    // 用普通的 socket 即可,不用 ServerSocket 了
    private Socket socket = null;

    public TcpEchoClient(String serverIP, int serverPort) throws IOException {
        // 這里可以給端口號(hào),但還這里給了之后,含義是不同的
        // 傳入的 IP 和 端口號(hào) 的含義表示的不是自己綁定,而是服務(wù)器的,表示和這個(gè) IP 端口 建立連接!
        // 調(diào)用這個(gè)構(gòu)造方法,就是和服務(wù)器建立連接 (打電話撥號(hào)了)
        socket = new Socket(serverIP, serverPort);
    }

    public void start() {
        System.out.println("和服務(wù)器連接成功");
        Scanner scanner = new Scanner(System.in);
        try (InputStream inputStream = socket.getInputStream()) {
            try (OutputStream outputStream = socket.getOutputStream()) {
                while (true) {
                    // 仍是四個(gè)步驟
                    // 1、先從控制臺(tái)讀取用戶輸入的字符串
                    System.out.print("-> ");
                    String request = scanner.next();

                    // 2、把這個(gè)用戶輸入的內(nèi)容,構(gòu)造成一個(gè)請(qǐng)求,并發(fā)送
                    PrintWriter printWriter = new PrintWriter(outputStream);
                    printWriter.println(request);
                    printWriter.flush(); //  刷新,如果沒有這個(gè)刷新,可能客戶端就不能第一時(shí)間看到響應(yīng)結(jié)果

                    // 3、從服務(wù)器讀取響應(yīng)數(shù)據(jù),并解析
                    Scanner respScanner = new Scanner(inputStream);
                    String response = respScanner.next();

                    // 4、把響應(yīng)結(jié)果顯示到控制臺(tái)上
                    System.out.printf("req : %s, resp : %s\n", request, response);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws IOException {
        TcpEchoClient client =  new TcpEchoClient("127.0.0.1", 9090);
        client.start();
    }
}

運(yùn)行結(jié)果:

[/127.0.0.1 : 7085] 客戶端建立連接!
[/127.0.0.1 : 7085] req : test, resp: test!
[/127.0.0.1 : 7085] 客戶端斷開連接!

1.3、服務(wù)器 — 多線程

問題:

雖然此時(shí)的 TCP 代碼已經(jīng)跑起來了,但是此處還存在一個(gè)很嚴(yán)重的問題!
當(dāng)前的服務(wù)器,同一時(shí)刻,只能處理一個(gè)連接! [不科學(xué)]
為啥當(dāng)前咱們的服務(wù)器程序,只能處理一個(gè)客戶端?
能夠和客戶端交互的前提是,要先調(diào)用 accept,接收連接 (接通電話)

上面的代碼,第一次 accept 結(jié)束之后,就會(huì)進(jìn)入 processConnection,在processConnection 又會(huì)有一個(gè)循環(huán)
如果 processConnection 里面的循環(huán)不結(jié)束,processConnection 就無法執(zhí)行完成
如果無法執(zhí)行完成,就導(dǎo)致外層循環(huán)無法進(jìn)入下一輪,也就無法第二次調(diào)用 accept , 也就不能接收第二個(gè)客戶端的連接了

當(dāng)前這個(gè)問題,就好像你接了個(gè)電話,和對(duì)方你一言我一語的聊天,然后其他人再打電話,就沒法繼續(xù)接通了

解決:

要想解決上述問題,就得讓 processConnection 的執(zhí)行,和前面的 accept 的執(zhí)行互相不干擾,不能讓 processConnection 里面的循環(huán)導(dǎo)致 accept 無法及時(shí)調(diào)用

多線程!

問題:為啥咱們剛才 UDP 版本的程序就沒用多線程,也是好著的呀?
因?yàn)?UDP 不需要處理連接,UDP 只要一個(gè)循環(huán),就可以處理所有客戶端的請(qǐng)求
但是此處,TCP 既要處理連接,又要處理一個(gè)連接中的若干次請(qǐng)求,就需要兩個(gè)循環(huán),里層循環(huán),就會(huì)影響到外層循環(huán)的進(jìn)度了

  • 主線程,循環(huán)調(diào)用 accept當(dāng)有客戶端連接上來的時(shí)候,就直接讓主線程創(chuàng)建一個(gè)新線程,由新線程負(fù)責(zé)對(duì)客戶端的若干個(gè)請(qǐng)求,提供服務(wù),(在新線程里,通過 while 循環(huán)來處理請(qǐng)求),這個(gè)時(shí)候,多個(gè)線程是并發(fā)執(zhí)行的關(guān)系 (宏觀上看起來同時(shí)執(zhí)行),就是各自執(zhí)行各自的了,就不會(huì)相互干擾
    (也要注意,每個(gè)客戶端連上來都得分配一個(gè)線程)

只需要在剛剛的代碼中,改動(dòng) start() 的即可:

Thread t = new Thread(() -> {
	processConnection(clientSocket);
});
t.start();

public static void main(String[] args) throws IOException {
    TcpEchoClient client =  new TcpEchoClient("127.0.0.1", 9090);
    client.start();
}

完整代碼:

package network;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class TcpThreadEchoServer {
    // listen 英文原意:監(jiān)聽。但是 Java socket 中體現(xiàn)出 "監(jiān)聽" 的含義,
    // 這樣叫是因?yàn)?,操作系統(tǒng)原生的 API 中,有一個(gè)操作叫做 listen
    // private ServerSocket listenSocket = null;
    private ServerSocket serverSocket = null;

    public TcpThreadEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服務(wù)器啟動(dòng)了!");
        while (true) {
            // 1、建立連接
            // 由于 TCP 是有連接的,不能一上來就讀數(shù)據(jù),需要先建立連接 (接電話)
            // accept 就是在接電話,接電話的前提是,有人給你打,如果當(dāng)前客戶端嘗試建立連接,此處的 accept 就會(huì)阻塞
            // accept 返回了一個(gè) socket 對(duì)象,稱為 clientSocket,后續(xù)和客戶端之間的溝通,都是都過 clientSocket 來完成的
            Socket clientSocket  = serverSocket.accept();

            // [改進(jìn)方法] 在這里,每次 accept 成功,都創(chuàng)建一個(gè)新的線程,由新線程負(fù)責(zé)執(zhí)行這個(gè) processConnection 方法,串行變并發(fā)
            Thread t = new Thread(() -> {
                // 2、處理連接
                // 這里之所分成了兩步 就是因?yàn)橐⑦B接 一個(gè)專門負(fù)責(zé)建立連接 一個(gè)專門負(fù)責(zé)數(shù)據(jù)通信
                processConnection(clientSocket);
            });
            t.start();
        }
    }

    // 處理連接
    private void processConnection(Socket clientSocket) {
        System.out.printf("[%s : %d] 客戶端建立連接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
        // 接下來處理請(qǐng)求和響應(yīng)
        try (InputStream inputStream = clientSocket.getInputStream()) {
            try (OutputStream outputStream = clientSocket.getOutputStream()) {
                // 把 inputStream 中的數(shù)據(jù)讀出來,寫入到 outputStream 中
                // 循環(huán)地處理每個(gè)請(qǐng)求,分別返回響應(yīng)
                Scanner scanner = new Scanner(inputStream);
                while (true) {
                    // 1、讀取請(qǐng)求
                    if (!scanner.hasNext()) {
                        System.out.printf("[%s : %d] 客戶端斷開連接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
                        break;
                    }
                    String request = scanner.next(); // 此處用 Scanner 更方便,如果用 InputStream 的 read 也可以

                    // 2、根據(jù)請(qǐng)求,計(jì)算響應(yīng)
                    String response = process(request);

                    // 3、將這個(gè)響應(yīng)返回客戶端
                    //    方便起見,用 PrintWriter 把 OutputStream 包裹一下
                    PrintWriter printWriter = new PrintWriter(outputStream);
                    printWriter.println(response);
                    //  刷新,如果沒有這個(gè)刷新,可能客戶端就不能第一時(shí)間看到響應(yīng)結(jié)果
                    printWriter.flush();

                    System.out.printf("[%s : %d] req : %s, resp: %s!\n", clientSocket.getInetAddress().toString(),
                            clientSocket.getPort(), request, response);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 記得關(guān)閉!
            try {
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        TcpThreadEchoServer server = new TcpThreadEchoServer(9090);
        server.start();
    }
}

此時(shí)運(yùn)行,沒有問題:

改成多線程版本了之后,雖然前面的代碼已經(jīng)進(jìn)入到處理連接的邏輯了,但是并不影響第二次去調(diào)用 accept

服務(wù)器啟動(dòng)了!
[/127.0.0.1 : 7366] 客戶端建立連接!
[/127.0.0.1 : 7371] 客戶端建立連接!
[/127.0.0.1 : 7366] req : hello, resp: hello!
[/127.0.0.1 : 7371] req : java, resp: java!

當(dāng)前的這個(gè)問題,其實(shí)是電話打過去了,只是對(duì)方?jīng)]接聽,對(duì)方聽到響鈴了嘛?聽到了
嘗試建立連接的請(qǐng)求,已經(jīng)發(fā)過去,對(duì)方也知道了,只是對(duì)方不想理你而已
當(dāng)客戶端 new Socket 成功的時(shí)候,其實(shí)在操作系統(tǒng)內(nèi)核層面,已經(jīng)建立好連接了 ( TCP 三次握手),但是應(yīng)用程序 ,沒有接通這個(gè)連接


1.4、服務(wù)器 — 線程池

還是在剛剛的代碼中,改動(dòng) start() 的即可:

package network;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TcpThreadPoolEchoServer {
    // listen 英文原意:監(jiān)聽。但是 Java socket 中體現(xiàn)出 "監(jiān)聽" 的含義,
    // 這樣叫是因?yàn)?,操作系統(tǒng)原生的 API 中,有一個(gè)操作叫做 listen
    // private ServerSocket listenSocket = null;
    private ServerSocket serverSocket = null;

    public TcpThreadPoolEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服務(wù)器啟動(dòng)了!");
        ExecutorService pool = Executors.newCachedThreadPool();
        while (true) {
            // 1、建立連接
            // 由于 TCP 是有連接的,不能一上來就讀數(shù)據(jù),需要先建立連接 (接電話)
            // accept 就是在接電話,接電話的前提是,有人給你打,如果當(dāng)前客戶端嘗試建立連接,此處的 accept 就會(huì)阻塞
            // accept 返回了一個(gè) socket 對(duì)象,稱為 clientSocket,后續(xù)和客戶端之間的溝通,都是都過 clientSocket 來完成的
            Socket clientSocket  = serverSocket.accept();

            // [改進(jìn)方法] 在這里,每次 accept 成功,都創(chuàng)建一個(gè)新的線程,由新線程負(fù)責(zé)執(zhí)行這個(gè) processConnection 方法,串行變并發(fā)
            // 通過線程池來實(shí)現(xiàn)
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    processConnection(clientSocket);
                }
            });
        }
    }

    // 處理連接
    private void processConnection(Socket clientSocket) {
        System.out.printf("[%s : %d] 客戶端建立連接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
        // 接下來處理請(qǐng)求和響應(yīng)
        try (InputStream inputStream = clientSocket.getInputStream()) {
            try (OutputStream outputStream = clientSocket.getOutputStream()) {
                // 把 inputStream 中的數(shù)據(jù)讀出來,寫入到 outputStream 中
                // 循環(huán)地處理每個(gè)請(qǐng)求,分別返回響應(yīng)
                Scanner scanner = new Scanner(inputStream);
                while (true) {
                    // 1、讀取請(qǐng)求
                    if (!scanner.hasNext()) {
                        System.out.printf("[%s : %d] 客戶端斷開連接!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
                        break;
                    }
                    String request = scanner.next(); // 此處用 Scanner 更方便,如果用 InputStream 的 read 也可以

                    // 2、根據(jù)請(qǐng)求,計(jì)算響應(yīng)
                    String response = process(request);

                    // 3、將這個(gè)響應(yīng)返回客戶端
                    //    方便起見,用 PrintWriter 把 OutputStream 包裹一下
                    PrintWriter printWriter = new PrintWriter(outputStream);
                    printWriter.println(response);
                    //  刷新,如果沒有這個(gè)刷新,可能客戶端就不能第一時(shí)間看到響應(yīng)結(jié)果
                    printWriter.flush();

                    System.out.printf("[%s : %d] req : %s, resp: %s!\n", clientSocket.getInetAddress().toString(),
                            clientSocket.getPort(), request, response);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 記得關(guān)閉!
            try {
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        TcpThreadPoolEchoServer server = new TcpThreadPoolEchoServer(9090);
        server.start();
    }
}

2、TCP 翻譯

繼承 TcpThreadPoolEchoServer,將 process 改成 public

package network;

import java.io.IOException;
import java.util.HashMap;

public class TcpDictServer extends TcpThreadPoolEchoServer {
    private HashMap<String, String> dict = new HashMap<>();

    public TcpDictServer(int port) throws IOException {
        super(port);

        dict.put("cat", "貓");
        dict.put("dog", "狗");
        dict.put("pig", "豬");
    }

    @Override
    public String process(String request) {
        return dict.getOrDefault(request, "該詞無法被翻譯!");
    }

    public static void main(String[] args) throws IOException {
        TcpDictServer server = new TcpDictServer(9090);
        server.start();
    }
}

根據(jù)請(qǐng)求計(jì)算響應(yīng),是一個(gè)服務(wù)器程序最最復(fù)雜的過程

問題:

一個(gè) TCP 服務(wù)器,能否讓一個(gè) UDP 客戶端連上?

TCP 和 UDP 他們無論是 API 代碼,還是協(xié)議底層 的工作過程,都是差異巨大的 (生殖隔離)。不是單純的 “把流轉(zhuǎn)成數(shù)據(jù)報(bào)” 就可以的,一次通信,需要用到五元組,協(xié)議類型不匹配,通信是無法完成的!文章來源地址http://www.zghlxwxcb.cn/news/detail-409486.html


到了這里,關(guān)于【Java 網(wǎng)絡(luò)編程】網(wǎng)絡(luò)通信原理、TCP、UDP 回顯服務(wù)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • .NET網(wǎng)絡(luò)編程——TCP通信

    .NET網(wǎng)絡(luò)編程——TCP通信

    ? ? ? ? 就是將不同區(qū)域的電腦連接到一起,組成局域網(wǎng)、城域網(wǎng)或廣域網(wǎng)。把分部在不同地理區(qū)域的計(jì)算機(jī)于專門的外部設(shè)備用通信線路 互聯(lián)成一個(gè)規(guī)模大、功能強(qiáng)的網(wǎng)絡(luò)系統(tǒng),從而使眾多的計(jì)算機(jī)可以方便地互相傳遞信息,共享硬件、軟件、數(shù)據(jù)信息等資源。 ??????

    2024年02月16日
    瀏覽(29)
  • 【網(wǎng)絡(luò)編程】網(wǎng)絡(luò)通信基礎(chǔ)——簡述TCP/IP協(xié)議

    【網(wǎng)絡(luò)編程】網(wǎng)絡(luò)通信基礎(chǔ)——簡述TCP/IP協(xié)議

    個(gè)人主頁:兜里有顆棉花糖 歡迎 點(diǎn)贊?? 收藏? 留言? 加關(guān)注??本文由 兜里有顆棉花糖 原創(chuàng) 收錄于專欄【網(wǎng)絡(luò)編程】【Java系列】 本專欄旨在分享學(xué)習(xí)網(wǎng)絡(luò)編程的一點(diǎn)學(xué)習(xí)心得,歡迎大家在評(píng)論區(qū)交流討論?? ip地址簡單來說就是用來描述網(wǎng)絡(luò)上一個(gè)設(shè)備的所在位置。 端

    2024年02月04日
    瀏覽(36)
  • Java【網(wǎng)絡(luò)編程2】使用 TCP 的 Socket API 實(shí)現(xiàn)客戶端服務(wù)器通信(保姆級(jí)教學(xué), 附代碼)

    Java【網(wǎng)絡(luò)編程2】使用 TCP 的 Socket API 實(shí)現(xiàn)客戶端服務(wù)器通信(保姆級(jí)教學(xué), 附代碼)

    ??各位讀者好, 我是小陳, 這是我的個(gè)人主頁 ??小陳還在持續(xù)努力學(xué)習(xí)編程, 努力通過博客輸出所學(xué)知識(shí) ??如果本篇對(duì)你有幫助, 煩請(qǐng)點(diǎn)贊關(guān)注支持一波, 感激不盡 ?? 希望我的專欄能夠幫助到你: JavaSE基礎(chǔ): 基礎(chǔ)語法, 類和對(duì)象, 封裝繼承多態(tài), 接口, 綜合小練習(xí)圖書管理系統(tǒng)

    2024年02月05日
    瀏覽(33)
  • TCP網(wǎng)絡(luò)通信編程之字符流

    TCP網(wǎng)絡(luò)通信編程之字符流

    【案例1】 【題目描述】 【 注意事項(xiàng)】 (3條消息) 節(jié)點(diǎn)流和處理流 字符處理流BufferedReader、BufferedWriter,字節(jié)處理流-BufferedInputStream和BufferedOutputStream (代碼均正確且可運(yùn)行_Studying~的博客-CSDN博客 1。這里需要使用字符處理流,來將字節(jié)流轉(zhuǎn)為字符流。 使用的字符流,所以一定

    2024年02月15日
    瀏覽(55)
  • TCP網(wǎng)絡(luò)通信編程之netstat

    TCP網(wǎng)絡(luò)通信編程之netstat

    【netstat指令】 ?【說明】 (1)Listening 表示某個(gè)端口在監(jiān)聽 (2)如果有一個(gè)外部程序(客戶端)連接到該端口,就會(huì)顯示一條連接信息 (3)指令netstat -anb 可以參看是那個(gè)程序在監(jiān)聽該端口,但是需要以管理員身份運(yùn)行dos控制臺(tái)。

    2024年02月15日
    瀏覽(38)
  • 【Qt網(wǎng)絡(luò)編程】實(shí)現(xiàn)TCP協(xié)議通信

    【Qt網(wǎng)絡(luò)編程】實(shí)現(xiàn)TCP協(xié)議通信

    傳輸控制協(xié)議(TCP,Transmission Control Protocol)是 一種面向連接的、可靠的、基于字節(jié)流的傳輸層通信協(xié)議 ,由IETF的RFC 793 定義。 TCP建立連接前,需要進(jìn)行三次握手,如下圖所示: TCP斷開連接前,需要進(jìn)行四次揮手,如下圖所示: Qt中提供了QTcpSocket類和QTcpServer類分別用于創(chuàng)

    2024年02月16日
    瀏覽(22)
  • 「網(wǎng)絡(luò)編程」第二講:網(wǎng)絡(luò)編程socket套接字(三)_ 簡單TCP網(wǎng)絡(luò)通信程序的實(shí)現(xiàn)

    「網(wǎng)絡(luò)編程」第二講:網(wǎng)絡(luò)編程socket套接字(三)_ 簡單TCP網(wǎng)絡(luò)通信程序的實(shí)現(xiàn)

    「前言」文章是關(guān)于網(wǎng)絡(luò)編程的socket套接字方面的,上一篇是網(wǎng)絡(luò)編程socket套接字(二),下面開始講解!? 「歸屬專欄」網(wǎng)絡(luò)編程 「主頁鏈接」個(gè)人主頁 「筆者」楓葉先生(fy) 「楓葉先生有點(diǎn)文青病」「每篇一句」 I?do?not?know?where?to?go,but?I?have?been?on?the?road. 我不知

    2024年02月11日
    瀏覽(29)
  • 基于UDP/TCP的網(wǎng)絡(luò)通信編程實(shí)現(xiàn)

    基于UDP/TCP的網(wǎng)絡(luò)通信編程實(shí)現(xiàn)

    紅色是心中永不褪色的赤誠 操作系統(tǒng)為網(wǎng)絡(luò)編程提供了 Socket api , Socket是基于TCP/IP協(xié)議的網(wǎng)絡(luò)通信的基本單元, 基于Socket的網(wǎng)絡(luò)程序開發(fā)就是 網(wǎng)絡(luò)編程. 由于直接與應(yīng)用層聯(lián)系的是傳輸層, 所以針對(duì)應(yīng)用層協(xié)議(TCP, UDP), Shocket提供了三種套接字, 分別是 流套接字(使用TCP) , 數(shù)據(jù)報(bào)

    2024年02月08日
    瀏覽(33)
  • 【網(wǎng)絡(luò)通信】socket編程——TCP套接字

    【網(wǎng)絡(luò)通信】socket編程——TCP套接字

    TCP依舊使用代碼來熟悉對(duì)應(yīng)的套接字,很多接口都是在udp中使用過的 所以就不會(huì)單獨(dú)把他們拿出來作為標(biāo)題了,只會(huì)把第一次出現(xiàn)的接口作為標(biāo)題 通過TCP的套接字 ,來把數(shù)據(jù)交付給對(duì)方的應(yīng)用層,完成雙方進(jìn)程的通信 在 tcpServer.hpp 中,創(chuàng)建一個(gè)命名空間 yzq 用于封裝 在命名

    2024年02月13日
    瀏覽(102)
  • 網(wǎng)絡(luò)編程day2——基于TCP/IP協(xié)議的網(wǎng)絡(luò)通信

    ? ? ? ? 計(jì)算機(jī)S ? ? ? ? ? ? ? ? ? ? ? ? ????????????????????????計(jì)算機(jī)C ? ? ?創(chuàng)建socket對(duì)象 ? ? ? ? ? ? ? ? ????????????????? 創(chuàng)建socket對(duì)象 ? ? ?準(zhǔn)備通信地址(自己的ip(非公網(wǎng)ip))? ? ? 準(zhǔn)備通信地址 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? (計(jì)算

    2024年02月10日
    瀏覽(40)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包