TCP
Nagle算法
如果每次發送一個很小的數據包,比如一個字節內容的數據包而不優化,就會導致網絡中只有極少數有效數據的數據包,這會導致浪費大量的網絡資源。Nagle算法針對這種情況,要求緩存區的數據達到一定數據量或者一定時間後才將其發出,所以數據包將會被Nagle算法合併,以此來優化網絡。這種優化雖然提高了網絡帶寬的效率,但有的數據可能會被延遲發送。
在Nodejs中,由於TCP默認啓動Nagle算法,可以調用socket.setNoDelay(ture)去掉Nagle算法,使得write()可以立即發送數據到網絡中。但需要注意的是,儘管在網絡的一端調用write()會觸發另一端的data事件,但是並不是每次write()都會觸發另一端的data事件,在關閉Nagle算法後,接收端可能會將接收到的多個小數據包合併,然後只觸發一次data事件。也就是説socket.setNoDelay(ture)只能解決一端的數據粘包問題。
什麼是粘包?
在組包過程中,把上一個包的內容與下一個包裏的粘在了一起被錯誤地當成了一個數據包解析了出來。這就是所謂的粘包。
粘包出現的根本原因是不確定消息的邊界。接收端在面對"無邊無際"的二進制流的時候,根本不知道收了多少 01 才算一個消息。一不小心拿多了就説是粘包。其實粘包根本不是 TCP 的問題,是使用者對於 TCP 的理解有誤導致的一個問題。
只要在發送端每次發送消息的時候給消息帶上識別消息邊界的信息,接收端就可以根據這些信息識別出消息的邊界,從而區分出每個消息。
通過拆包封包的方式解決粘包方案
node.js中,不會按照發送端的write寫入對應觸發接收端的data事件,它會有以下3種情況:
- 發送端多次
write()的數據可能被打包成一個數據包發送到接收端。 - 發送端通過
write()一次寫入的數據可能因為體積過大被截斷到多個數據包中 - 發送端通過
write()一次寫入打包成一個數據報發送到接收端
我們要處理就是前2中情況。TCP是基於流的傳輸機制,那麼它的數據順序在傳輸過程中是確定的先進先出原則。所以,可以通過在每次write()在數據頭部添加一些標識,將每次write()傳輸的數據間隔開,然後在接收端基於這些間隔數據的標識將數據拆分或合併。
UDP
UDP又稱用户數據包協議,與TCP一樣同屬於網絡傳輸層。UDP與TCP最大的不同是UDP不是面向連接的。
UDP的應用場景:DNS,音視頻傳輸
- server.js
const PORT = 3333;
const HOST = '127.0.0.1';
const dgram = require("dgram");
var server = dgram.createSocket('udp4');
server.on("listen", function() {
const address = server.address();
console.log('UDP Server listening on ' + address.address + ":" + address.port);
})
server.on('message', function (message, remote) {
console.log(remote.address + ':' + remote.port +' - ' + message);
});
socket.bind(POST, HOST);
- client
const PORT = 3333;
const HOST = '127.0.0.1';
const dgram = require('dgram');
const message = Buffer.from('Test Message');
const client = dgram.createSocket('udp4');
client.send(message, PORT, HOST, function(err, bytes) {
if (err) throw err;
console.log('UDP message sent to ' + HOST +':'+ PORT);
client.close();
});
UDP套接字事件
UDP套接字相對TCP套接字使用起來更簡單,它只是一個EventEmitter的實例,而非Stream的實例。它具備如下自定義事件。
- message:當UDP套接字偵聽網卡端口後,接收到消息時觸發該事件,觸發攜帶的數據為消息Buffer對象和一個遠程地址信息。
- listening:當UDP套接字開始偵聽時觸發該事件。
- close:調用close()方法時觸發該事件,並不再觸發message事件。如需再次觸發message事件,重新綁定即可。
- error:當異常發生時觸發該事件,如果不偵聽,異常將直接拋出,使進程退出。