一 前言
在之前的文章中有提過(guò)Socket(作為進(jìn)程間通信的一種方式),其實(shí)Socket是封裝了底層的TCP和UDP協(xié)議,為開(kāi)發(fā)者提供的友好網(wǎng)絡(luò)編程接口(注意是個(gè)編程庫(kù),不是協(xié)議),對(duì)開(kāi)發(fā)者而言是網(wǎng)絡(luò)編程的重要知識(shí)點(diǎn)。要了解Socket我們先從網(wǎng)絡(luò)的基礎(chǔ)知識(shí)講起,看看Socket在網(wǎng)絡(luò)模型中的位置。
二 網(wǎng)絡(luò)基礎(chǔ)知識(shí)
20世紀(jì)70年代Vinton Gray Cerf(文頓·瑟夫)和Robert Elliot Kahn(羅伯特·卡恩)兩位先生設(shè)計(jì)了TCP/IP協(xié)議模型,奠定了現(xiàn)代網(wǎng)絡(luò)的基礎(chǔ)知識(shí)。這世上從來(lái)就沒(méi)有什么理所當(dāng)然就存在的事情,感謝先輩們的付出,向他們致敬。后來(lái)又經(jīng)過(guò)分層設(shè)計(jì),演變成了現(xiàn)在的四層模型(自上而下分為應(yīng)用層、傳輸層、網(wǎng)絡(luò)互聯(lián)層和網(wǎng)絡(luò)接口層)。這個(gè)四層協(xié)議模型為業(yè)界所通用,但不知道什么原因國(guó)際化標(biāo)準(zhǔn)組織搞了一個(gè)七層的OSI組織模型,這個(gè)七層模型層數(shù)太多,遭到很多專家反對(duì),其中普度大學(xué)的教授Douglas Comer(先生在網(wǎng)絡(luò)界的盛名不需多說(shuō))這樣批評(píng)這個(gè)七層模型(下面是引用WolfcsTec翻譯的教授文章原話)
研究人員開(kāi)始回顧 OSI 七層參考模型的起源,琢磨這個(gè)笨重的、模糊不清的模型為什么如此的持久,總是揮之不去。他們發(fā)現(xiàn)了一個(gè)令人吃驚的事實(shí)。我們?cè)缇椭?,這個(gè)模型是一個(gè)小組(國(guó)際化標(biāo)準(zhǔn)組織)的工作,但是我們不知道,那群人在一天夜里聚在酒吧里取笑美國(guó)的流行文化。他們?cè)诓徒砑埳虾鷣y寫下迪斯尼電影《白雪公主和七個(gè)小矮人》中的七個(gè)小矮人的名字。有人開(kāi)玩笑說(shuō),“7”對(duì)網(wǎng)絡(luò)分層來(lái)講是一個(gè)很好的數(shù)字。第二天上午在標(biāo)準(zhǔn)化委員會(huì)的會(huì)議上,工作組傳看著餐巾紙,一致同意了他們前天夜里喝醉以后的重大發(fā)現(xiàn)。那天結(jié)束時(shí),他們對(duì)七個(gè)層次重新命名(聽(tīng)起來(lái)更加科學(xué)),于是基本模型誕生了。以下羅列了七層的名字和一些解釋:
層次 小矮人的名字 OSI分層的名字 解釋
1 Sleepy Physical(物理層) . . . . . .
2 Sneezy Link (鏈路層) . . . . . .
3 Happy Network(網(wǎng)絡(luò)層) . . . . . .
4 Doc Transport(傳輸層) . . . . . . .
5 Dopey Session(會(huì)話層) . . . . . .
6 Bashful Presentation(表示層) . . . . . .
7 Grumpy Application (應(yīng)用層) . . . . . .
這個(gè)故事的教訓(xùn):如果你是一個(gè)標(biāo)準(zhǔn)委員會(huì)的工程師,就不要和你的同事去喝酒——深夜里一個(gè)拙劣的笑話,有可能成為工業(yè)界幾十年都揮之不去的夢(mèng)魘。
好吧,教授這段話,誅心之論,那還說(shuō)什么,我們就看看計(jì)算機(jī)的四層TCP/IP網(wǎng)絡(luò)模型。
2.1 TCP/IP協(xié)議模型
廢話不多說(shuō),先上一張我畫(huà)的圖

以應(yīng)用層的Http協(xié)議簡(jiǎn)單解釋這四層
- 應(yīng)用層:用戶在瀏覽器輸入一個(gè)"http://"開(kāi)頭的url地址后,瀏覽器會(huì)根據(jù)網(wǎng)址的含義,生成HTTP請(qǐng)求消息。然后把消息向下轉(zhuǎn)交給傳輸層。
- 傳輸層:傳輸層按照網(wǎng)絡(luò)數(shù)據(jù)包的長(zhǎng)度對(duì)數(shù)據(jù)拆分,在每個(gè)包前面加上TCP頭部并向下轉(zhuǎn)交給網(wǎng)絡(luò)互聯(lián)層(IP層)。
- 網(wǎng)絡(luò)互聯(lián)層:網(wǎng)絡(luò)互聯(lián)層在TCP包的前面加上IP頭部,然后向下轉(zhuǎn)交給接口層。
- 網(wǎng)絡(luò)接口層:如果是以太網(wǎng)絡(luò)時(shí),會(huì)查詢MAC地址,并加上MAC頭部,向下轉(zhuǎn)交給網(wǎng)卡驅(qū)動(dòng)。
2.2 網(wǎng)絡(luò)數(shù)據(jù)傳輸流程
上面說(shuō)了TCP/IP協(xié)議的四層模型,好像沒(méi)看到Socket(這說(shuō)明他確實(shí)不是一個(gè)協(xié)議),接下來(lái)梳理網(wǎng)絡(luò)數(shù)據(jù)傳輸?shù)牧鞒獭O瓤醋约菏崂淼囊粡埦W(wǎng)絡(luò)數(shù)據(jù)在客戶端/服務(wù)端之間的數(shù)據(jù)傳輸圖。

流程很清晰,就不在多說(shuō),說(shuō)幾個(gè)關(guān)注點(diǎn):
- 請(qǐng)求方,數(shù)據(jù)打包,并層層加頭部向下傳遞;
- 接收方,數(shù)據(jù)向上層層解析;
- 在應(yīng)用層生成數(shù)據(jù)后,會(huì)通過(guò)調(diào)用Socket庫(kù)委托系統(tǒng)協(xié)議棧(TCP/IP協(xié)議)發(fā)送和接受數(shù)據(jù);
- Scoket庫(kù)封裝了TCP或者UDP協(xié)議;
下面先看看TCP和UDP協(xié)議連接流程。
2.3 TCP協(xié)議
TCP是一種面向連接、面向字節(jié)流、全雙工通信、可靠的協(xié)議。
2.3.1 TCP連接
先看一下TCP連接過(guò)程(也就是傳說(shuō)中的"三次握手"),如下圖:

這里引用林沛滿的一段解釋(很形象):
客戶端:“我能和你建立連接嗎?我的初始發(fā)送序號(hào)是X。如果你答應(yīng)就Ack=X+1。”
服務(wù)端:“收到,Ack=X+1。我也想和你建立連接。我的初始序號(hào)是Y,你如果答應(yīng)就Ack=Y+1?!?br> 客戶端:“收到,Ack=Y+1?!?/p>
注意上面的SYN標(biāo)識(shí)正在發(fā)起連接。上面三次握手完成了TCP的連接。
為什么要搞三次這么復(fù)雜,兩次不行嗎。兩次不可靠,后面會(huì)補(bǔ)上為什么兩次不可靠的原因。
2.3.2 TCP斷開(kāi)
后續(xù)加上
2.4 UDP協(xié)議
UDP協(xié)議無(wú)需連接,數(shù)據(jù)發(fā)出去就不管了。另外,UDP傳輸?shù)臄?shù)據(jù)包相比較TCP而言,量很少。最典型的應(yīng)用場(chǎng)景就是DNS查詢。
三 Socket
上面說(shuō)了Socket是封裝了TCP或者UDP協(xié)議,提供給開(kāi)發(fā)者的編程接口庫(kù)。
其實(shí)就實(shí)現(xiàn)了上面講的TCP協(xié)議和UDP協(xié)議原理。
一般Socket使用的幾個(gè)步驟:
- 創(chuàng)建Socket。
- 連接階段(將管道連接到服務(wù)器)。
- 通信階段(通信階段)。
- 斷開(kāi)階段(斷開(kāi)連接管理,并刪除套接字)。
// 1.創(chuàng)建Socket對(duì)象,并指定連接服務(wù)端的IP及端口號(hào) ,連接。
Socket socket = new Socket("192.168.0.100", 8001);
// 判斷客戶端和服務(wù)器是否連接成功
socket.isConnected());
// 2.通信階段
//創(chuàng)建輸入流對(duì)象InputStream
InputStream is = socket.getInputStream()
// 解析服務(wù)端返回?cái)?shù)據(jù)
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
//讀取解析后的數(shù)據(jù)
br.readLine();
// 從Socket 獲得輸出流對(duì)象OutputStream
OutputStream outputStream = socket.getOutputStream();
// 寫入需要發(fā)送的數(shù)據(jù)到輸出流對(duì)象中
outputStream.write("test").getBytes());
// 發(fā)送數(shù)據(jù)到服務(wù)端
outputStream.flush();
// 3.斷開(kāi)階段
os.close();
// 斷開(kāi) 客戶端發(fā)送到服務(wù)器 的連接,即關(guān)閉輸出流對(duì)象OutputStream
br.close();
// 斷開(kāi) 服務(wù)器發(fā)送到客戶端 的連接,即關(guān)閉輸入流讀取器對(duì)象BufferedReader
socket.close();
// 最終關(guān)閉整個(gè)Socket連接
當(dāng)然也需要服務(wù)端配合。
代碼地址(利用BIO模式實(shí)現(xiàn)):
Client端
Server 端
參考文章
[1] Wireshark網(wǎng)絡(luò)分析就這么簡(jiǎn)單.林沛滿
[2] 網(wǎng)絡(luò)是怎樣連接的.戶根勤
[3] 鳥(niǎo)哥的Linux私房菜
重點(diǎn)推薦《網(wǎng)絡(luò)是怎樣連接的》這本書(shū),本人還沒(méi)看完,作者從軟件到硬件,從服務(wù)器到交換機(jī),每一方面都在行,而且文字功底好。
由于本人能力有限,有的方面理解不當(dāng),后續(xù)會(huì)根據(jù)情況繼續(xù)調(diào)整。