1. 引子
最近在學習node.js的net模組,涉及到了tcp/ip,arp,rcmp,http等協議,在這之前本人僅對http協議進行過深入的研究,至於其它協議僅僅只是知道有這些協議存在而已,未深入研究過。說實在的,網路協議概念很簡單,但是也很抽象,網上查了很多資料,都是一些晦澀難懂的語言,所以個人覺得,明白協議的作用、怎麼用、以何種形式用,再去看協議的具體工作過程更會讓人印象深刻,下面就簡明扼要的闡述TCP、IP、UDP。
TCP/IP協議,是傳輸控制協議/因特網互聯協議,又名網路通訊協議,是Internet最基本的協議、Internet國際網際網路絡的基礎,由網路層的IP協議和傳輸層的TCP協議組成。TCP/IP 定義了電子裝置如何連入因特網,以及資料如何在它們之間傳輸的標準。協議採用了4層的層級結構,每一層都呼叫它的下一層所提供的協議來完成自己的需求。通俗而言:TCP負責發現傳輸的問題,一有問題就發出訊號,要求重新傳輸,直到所有資料安全正確地傳輸到目的地。而IP是給因特網的每一臺聯網裝置規定一個地址。-- 百度百科
不同的計算機系統,就好像語言不同的兩個人互相見了面,完全不能交流資訊。因而他們需要定義一些共通的東西來進行交流,TCP/IP就是為此而生。
封面圖中,展示了OSI七層及TCP/IP五層協議的對應關係;
- 網路由下往上分為物理層、資料鏈路層、網路層、傳輸層、應用層。
- IP協議對應於網路層,TCP協議對應於傳輸層,而HTTP協議對應於應用層,三者從本質上來說沒有可比性,socket則是對TCP/IP協議的封裝和應用(程式設計師層面上)。
- 也可以說,TPC/IP協議是傳輸層協議,主要解決資料如何在網路中傳輸,而HTTP是應用層協議,主要解決 如何包裝資料。
2. 一張圖讓你瞭解TCP/IP到底是啥?
IP能鎖定一臺物理機器,對應著一張網路卡,外界發來的資料包網路卡都會接收。如果所有程式都需要監聽網路卡接發資料,每個包都被髮到了所有應用程式,那應用程式符合不了,最後會垮掉,所以就誕生了埠這個標識,從資料安全層面考慮,一個標識號只能被一個應用程式監聽。其實網路卡都是被系統層封裝了,埠和程式之間的關係也是系統封裝好的。我們只需要用socket就行,給定一個埠號就行了。其它的事都交給作業系統去做。 TCP讀取埠號,這個埠號就是建立socket時註冊的,socket建立成功應該有一個process ID,這應該是作業系統來完成的,TCP於是就把[ 埠號 Process ID] 聯絡了起來,於是就和這個Process ID程式交換,完成資料的傳送和接收。 點選檢視參考連結
通過這篇文章,我才知道IP和TCP到底做了些啥玩意....... 就是定址和保證資料傳遞正確;
3. TCP
TCP三次握手四次斷開
1. 三次握手
- 第一次握手
主機A
通過一個標識為SYN標識位的資料段傳送給主機B請求連線
,通過該資料段告訴主機B希望建立連線,需要B應答,並告訴主機B傳輸的起始序列號; - 第二次握手是
主機B
用一個確認應答
ACK和同步序列號SYNC標誌位的資料段來響應主機A,一是傳送ACK告訴主機A收到了資料段,二是通知主機A從哪個序列號做標記; - 第三次握手是
主機A確認收到
了主機B的資料段並可以開始傳輸實際資料。
2. 四次斷開
主機A
傳送FIN控制位發出斷開連線的請求
;主機B
進行響應,確認收到斷開連線請求
;主機B
提出反方向的關閉要求
;主機A確認
收到的主機B的關閉連線請求
;
問題:為什麼斷開要四次,而不是三次?因為主機B在響應收到斷開連結請求的同時,還存在未傳送完的資料;
4. UDP
UDP協議並不提供超時重傳,出錯重傳等功能,所以說其是不可靠的協議。
5. TCP與UDP的區別
TCP(Transimision Control Protocal) ==> http ftp smtp ==> 電話
- 傳輸控制協議
- 可靠的、面向連線的協議
- 傳輸效率低
UDP(User Datagram Protocal) ==> qq, 微信 ==> 廣播
- 使用者資料包協議
- 不可靠的、無連線的服務
- 傳輸效率高
- TCP是面向連結的,雖然說網路的不安全不穩定特性決定了多少次握手都不能保證連線的可靠性,但TCP的三次握手在最低限度上(實際上也很大程度上保證了)保證了連線的可靠性;而UDP不是面向連線的,UDP傳送資料前並不與對方建立連線,對接收到的資料也不傳送確認訊號,傳送端不知道資料是否會正確接收,當然也不用重發,所以說UDP是無連線的、不可靠的一種資料傳輸協議。 2。也正由於1所說的特點,使得UDP的開銷更小資料傳輸速率更高,因為不必進行收發資料的確認,所以UDP的實時性更好。
知道了TCP和UDP的區別,就不難理解為何採用TCP傳輸協議的MSN比採用UDP的QQ傳輸檔案慢了,但並不能說QQ的通訊是不安全的,因為程式設計師可以手動對UDP的資料收發進行驗證,比如傳送方對每個資料包進行編號然後由接收方進行驗證啊什麼的,即使是這樣,UDP因為在底層協議的封裝上沒有采用類似TCP的“三次握手”而實現了TCP所無法達到的傳輸效率。
6. node.js net模組
- net模組也是node的核心模組,用於底層的網路通訊;
- http.Server繼承了net.Server;
- http客戶端與http服務端的通訊均依賴於socket(net.Socket);
6.1 net模組組成
主要包含兩個部分:
-
net.Server tcp/server, 服務端TCP監聽來自客戶端的請求,並使用TCP連線(socket)向客戶端傳送資料; 內部通過socket來實現與客戶端的通訊;
-
net.Socket tcp/本地,客戶端TCP連線到伺服器,並與伺服器交換資料; socket的node實現,實現了全雙工的stream的介面;
6.2 服務端net.Server
let net = require('net')
let PORT = 8081
let HOST = 'localhost'
/**
* 1. 建立一個TCP伺服器例項,呼叫listen函式開始監聽指定埠;
* 2. 傳入net.createServer()的回撥函式,作為connection事件的處理函式;
* 3. 在每個connection事件中,該回撥函式接收到的socket物件是唯一的;
* 4. 該連線自動關聯一個socket物件
* */
let server = net.createServer((socket) => {
console.log('connection:' + socket.remoteAddress, socket.remotePort)
// 為這個socket例項新增一個“data”事件處理函式
socket.on('data', (data) => {
console.log('DATA' + socket.remoteAddress + ":" + data);
socket.write('You said "'+ data +'"\r\n') // 向客戶端回發該資料
})
socket.on('end', () => {
console.log('客戶端關閉')
/**
* 服務端收到客戶端發出的關閉連線請求時,會觸發end事件
* 這個時候客戶端沒有真正的關閉,只是開始關閉;
* 當真正的關閉的時候,會觸發close事件;
* */
server.unref();
//呼叫了該方法,則所有的客戶端關閉跟本伺服器的連線後,將關閉伺服器
})
// 客戶端關閉事件
socket.on('close', () => {
console.log('CLOSED: ' + socket.remoteAddress + ' ' + socket.remotePort);
})
/*socket.pause()
socket.setTimeout(3000) //設定客戶端超時時間,如果客戶端一直不輸入,超過這個時間,就認為超時了
socket.on('timeout', () => {
console.log('超時了')
socket.pipe(ws, {end: false})
// 預設情況下,當可讀流讀到末尾的時候會關閉可寫流
})*/
})
server.listen(PORT, HOST, () => {
console.log('服務端的地址是:', server.address())
})
server.on('error', (err) => {
console.log(err)
})
//服務端也可以通過顯式處理"connection"事件來建立TCP連線,只是寫法不同,二者沒有區別即:
/*
let server = net.createServer()
server.listen(PORT,HOST)
server.on('connection', (socket) => {
console.log('CONNECTED: ' + sock.remoteAddress +':'+ sock.remotePort);
})*/
server.on('close', () => {
//關閉伺服器,停止接收新的客戶端的請求
console.log( 'close事件:服務端關閉' );
})
server.on('error', (error) => {
console.log( 'error事件:服務端異常:' + error.message );
})
複製程式碼
6.3 客戶端net.Socket
let net = require('net')
//建立一個TCP客戶端連線到剛建立的伺服器上,該客戶端向伺服器傳送一串訊息,並在得到伺服器的反饋後關閉連線。
var client = new net.Socket()
let PORT = 8081
let HOST = 'localhost'
client.connect(PORT, HOST, () => {
console.log('connect to ' + HOST + ':' + PORT)
client.write('I am happyGloria.') //建立連線後立即向伺服器傳送資料,伺服器將收到這些資料
})
client.on('data', (data) => {
console.log('DATA: ' + data)
client.destroy() // 完全關閉連線
})
client.on('close', function () {
console.log('Connection closed')
})
複製程式碼
6.4 基於tcp的聊天室
let net = require('net')
let util = require('util')
let HOST = 'localhost'
let PORT = 8082
let clients = {}
function broadcast (username, msg) {
for (let name in clients) {
if (name != username) {
clients[name].write(msg + '\r\n')
}
}
}
let server = net.createServer((socket) => {
socket.setEncoding('utf8')
server.getConnections((err, count) => {
socket.write('線上人數是' + count + '位,請輸入你的暱稱:\r\n')
})
let username
socket.on('data', (data) => {
data = data.replace(/\r\n/, '')
if (username) {
broadcast(username, `${username} 說: ${data}`)
} else {
if (clients[data]) {
socket.write('您的暱稱' + data + '被佔用了,請您更換新的暱稱\r\n')
} else {
username = data
clients[username] = socket
broadcast(username, `歡迎${username}加入`)
}
}
})
socket.on('end', () => {
broadcast(username, `${username}離開聊天室`)
clients[username] && clients[username].destroy()
delete clients[username]
})
})
server.listen(PORT, HOST, () => {
console.log(`tcp聊天室已啟動,地址是${util.inspect(server.address())}`)
})
複製程式碼
參考資料: https://www.zhihu.com/question/51074319