我們知道,TCP是一種基于連接的協(xié)議,在進(jìn)行通信前,首先要求客戶端與服務(wù)器端建立一條用于通信的連接。而UDP是一種面向非連接的協(xié)議,在進(jìn)行通信前,不要求首先建立客戶端與服務(wù)器端的連接,可以直接把數(shù)據(jù)包發(fā)送給對(duì)方?;谶@個(gè)原因,UPD也是一種不可靠的協(xié)議,但是其傳輸速度比TCP更快,因此更適用于實(shí)時(shí)通信的場(chǎng)合。
在Node.js中,提供了dgram模塊,用于創(chuàng)建UPD服務(wù)器與客戶端,以及實(shí)現(xiàn)UDP服務(wù)器與客戶端之間的通信。
一、 創(chuàng)建UDP服務(wù)器與客戶端
在dgram模塊中,可以使用crateSocket方法創(chuàng)建一個(gè)用于實(shí)現(xiàn)UDP通信的socket端口對(duì)象。
const socket = dgram.createSocket(type, [callback]);
type:用于指定進(jìn)行UDP通信時(shí)使用的協(xié)議類型,可指定值為“upd4”或“upd6”。
callback:可選,用于指定當(dāng)從該端口接收到數(shù)據(jù)時(shí)調(diào)用的回調(diào)函數(shù)。該回調(diào)函數(shù)使用方法如下:function (msg, rinfo) { }該回調(diào)函數(shù)中可以使用兩個(gè)參數(shù):
msg:參數(shù)值為一個(gè)Buffer對(duì)象,其中存放了接收到的數(shù)據(jù)。
rinfo:參數(shù)值也是一個(gè)對(duì)象,該對(duì)象具有如下屬性:
- address:屬性值為發(fā)送者所使用的地址,如127.0.0.1。
- family:屬性值為一個(gè)標(biāo)識(shí)了發(fā)送者所使用的地址是IPv4地址還是IPv6地址的字符串,例如“IPv4”。
- port:屬性值為發(fā)送者所使用的socket端口號(hào),例如49436。
- size:屬性值為發(fā)送者所發(fā)送的數(shù)據(jù)的字節(jié)數(shù)。
當(dāng)從socket端口中接收到數(shù)據(jù)時(shí),觸發(fā)該socket對(duì)象的message事件,可以不在createSocket方法中使用callback參數(shù)。
socket.on('message', function(msg, rinfo) {
})
在創(chuàng)建了socket端口對(duì)象后,可以使用該socket端口對(duì)象的bind方法來指定該socket端口對(duì)象所監(jiān)聽的地址與端口號(hào)。當(dāng)創(chuàng)建UDP服務(wù)器時(shí),必須使用該方法,這樣UDP客戶端才能知道向哪個(gè)地址發(fā)送數(shù)據(jù)。
socket.bind(port, [address], [callback])
port:參數(shù)值為一個(gè)整數(shù),用于指定該socket端口對(duì)象所監(jiān)聽的端口號(hào)。
address:可選,參數(shù)值為一個(gè)字符串,用于指定該socket端口對(duì)象所監(jiān)聽的地址,該地址可以為一個(gè)IP地址,也可以為一個(gè)主機(jī)名。如果不指定address參數(shù),該socket端口對(duì)象將監(jiān)聽來自于所有地址的數(shù)據(jù)。
callback:可選,當(dāng)指定了bind方法之后,該socket端口對(duì)象將立即監(jiān)聽來自于指定地址和端口號(hào)的數(shù)據(jù),可使用callback參數(shù)值指定開始監(jiān)聽時(shí)所要調(diào)用的回調(diào)函數(shù),也可以對(duì)socket端口對(duì)象的listening事件進(jìn)行監(jiān)聽,該回調(diào)函數(shù)不使用任何參數(shù)。socket.on('listening', function() { }
當(dāng)創(chuàng)建了一個(gè)socket端口對(duì)象之后可以利用該socket端口對(duì)象的send方法向外發(fā)送數(shù)據(jù)。
socket.send(buf, offset, length, port, address, [callback])
buf: 代表了一個(gè)緩存區(qū)的buffer對(duì)象,該緩存區(qū)中存放了需要發(fā)送的數(shù)據(jù)。
offset:參數(shù)值為一個(gè)整數(shù),用于指定從緩存區(qū)的第幾個(gè)字節(jié)處開始取出要發(fā)送的數(shù)據(jù)。
length:參數(shù)值為一個(gè)整數(shù),用于指定需要發(fā)送的字節(jié)數(shù)。
prot:參數(shù)為一個(gè)整數(shù),用于指定接受數(shù)據(jù)的socket端口對(duì)象所使用的端口號(hào)。
address:參數(shù)值為一個(gè)字符串,用于指定接收數(shù)據(jù)的socket端口對(duì)象所屬地址。
callback:可選,指定當(dāng)數(shù)據(jù)發(fā)送完畢時(shí)所需調(diào)用的回調(diào)函數(shù)。function(err, bytes) { }err:發(fā)送數(shù)據(jù)失敗時(shí)觸發(fā)的錯(cuò)誤對(duì)象,在發(fā)送數(shù)據(jù)時(shí)產(chǎn)生的失敗通常是由于DNS解析錯(cuò)誤引起。
bytes:參數(shù)值為發(fā)送數(shù)據(jù)的字節(jié)數(shù)
如果在發(fā)送數(shù)據(jù)前還沒有使用該socket端口對(duì)象的bind方法來指定其端口號(hào)及地址,操作系統(tǒng)將為其分配一個(gè)隨機(jī)端口號(hào)并指定其可以接收來自于任何地址的數(shù)據(jù)。
可以使用socket端口對(duì)象的address方法獲取該socket端口對(duì)象相關(guān)的地址信息。
let address = socket.address()
該方法返回一個(gè)對(duì)象,其中具有如下屬性:
- port:屬性值為該socket端口對(duì)象的端口號(hào)。
- address:屬性值為該socket端口對(duì)象所屬地址。
- family:屬性值為一個(gè)標(biāo)識(shí)了該socket端口對(duì)象所屬地址是IPv4地址還是IPv6地址的字符串。
創(chuàng)建簡單UDP服務(wù)器
const dgram = require('dgram');
const server = dgram.createSocket('udp4');
server.on("message", function (msg, rinfo) {
console.log('已接收到客戶端發(fā)送的數(shù)據(jù):' + msg);
console.log("客戶端地址信息為:", rinfo);
let buf = new Buffer('確認(rèn)信息為:' + msg);
server.send(buf, 0, buf.length, rinfo.port, rinfo.address);
});
server.on("listening", function () {
let address = server.address();
console.log("服務(wù)器開始監(jiān)聽。地址信息為", address);
});
server.bind(41234, 'localhost');
創(chuàng)建簡單UDP客戶端
const dgram = require('dgram');
let message = new Buffer('你好。');
const client = dgram.createSocket('udp4');
client.send(message, 0, message.length, 41234, "localhost", function (err, bytes) {
if (err) {
console.log('發(fā)送數(shù)據(jù)失敗');
} else {
console.log("已發(fā)送字節(jié)數(shù)據(jù)。", bytes);
}
});
client.on('message', function (msg, rinfo) {
console.log('已接收到服務(wù)器端發(fā)送的數(shù)據(jù)', msg);
console.log('服務(wù)器地址為', rinfo.address);
console.log('服務(wù)器所用端口號(hào)為', rinfo.port);
});
未完待續(xù)。。。。