深入Node TCP模塊的理解
1. TCP
在Node.js中,提供了net模塊用來實(shí)現(xiàn)TCP服務(wù)器和客戶端的通信。
1.1 TCP服務(wù)器
net.createServer([options][, connectionListener])
- options.allowHalfOpen 是否允許單方面連接,默認(rèn)值為false
- connectionListener參數(shù)用于指定當(dāng)客戶端與服務(wù)器建立連接時(shí)所要調(diào)用的回調(diào)函數(shù),回調(diào)中有一個(gè)參數(shù)socket,指的是TCP服務(wù)器監(jiān)聽的socket端口對(duì)象
也可以通過監(jiān)聽connection事件的方式來指定監(jiān)聽函數(shù)
server.on('connection',function(socket){});
1.1.1 啟動(dòng)TCP服務(wù)器
可以使用listen方法通知服務(wù)器開始監(jiān)聽客戶端的連接
server.listen(port,[host],[backlog],[callback])
- port 必須指定的端口號(hào)
- host 指定需要監(jiān)聽的IP地址或主機(jī)名,如果省略的話服務(wù)器將監(jiān)聽來自于任何客戶端的連接
- backlog指定位于等待隊(duì)列中的客戶端連接的最大數(shù)量,默認(rèn)值為511
server.on('listening',function(){});
1.1.2 使用TCP服務(wù)器
let net = require('net');
let server = net.createServer(function(socket){
console.log('客戶端已連接');
});
server.listen(8080,'localhost',function(){
console.log('服務(wù)器開始監(jiān)聽');
});
1.1.3 address
server.address()
- port 端口號(hào)
- address TCP服務(wù)器監(jiān)聽的地址
- family 協(xié)議的版本
1.1.4 getConnections
查看當(dāng)前與TCP服務(wù)器建立連接的客戶端的連接數(shù)量以及設(shè)置最大連接數(shù)量
server.getConnections(callback); server.maxConnections = 2;
1.1.5 close
使用close方法可以顯式拒絕所有的客戶端的連接請(qǐng)求,當(dāng)所有已連接的客戶端關(guān)閉后服務(wù)器會(huì)自動(dòng)關(guān)閉,并觸發(fā)服務(wù)器的close事件。
server.close();
server.on('close',callback);
1.2 socket
1.2.1 address
net.Socket代表一個(gè)socket端口對(duì)象,它是一個(gè)可讀可寫流。
let net = require('net');
let util = require('util');
let server = net.createServer(function(socket){
server.getConnections((err,count)=>{
server.maxConnections = 1;
console.log('最大連接數(shù)量%d,當(dāng)前連接數(shù)量%d',server.maxConnections,count);
});
let address = socket.address();
console.log('客戶端地址 %s',util.inspect(address));
});
1.2.2 讀取數(shù)據(jù)
let server = net.createServer(function (socket) {
socket.setEncoding('utf8');
socket.on('data', function (data) {
console.log('本次收到的內(nèi)容為%s,累計(jì)收到的字節(jié)數(shù)是%d', data, socket.bytesRead);
});
});
1.2.3 監(jiān)聽關(guān)閉事件
let server = net.createServer(function (socket) {
socket.on('end', function () {
console.log('客戶端已經(jīng)關(guān)閉');
});
});
1.2.4 pipe
pipe方法可以將客戶端發(fā)送的數(shù)據(jù)寫到文件或其它目標(biāo)中。
socket.pipe(destinatin,[options]);
options.end 設(shè)置為false時(shí)當(dāng)客戶端結(jié)束寫操作或關(guān)閉后并不會(huì)關(guān)閉目標(biāo)對(duì)象,還可以繼續(xù)寫入數(shù)據(jù)
let net = require('net');
let path = require('path');
let ws = require('fs').createWriteStream(path.resolve(__dirname, 'msg.txt'));
let server = net.createServer(function (socket) {
socket.on('data', function (data) {
console.log(data);
});
socket.pipe(ws, { end: false });
socket.on('end', function () {
ws.end('over', function () {
socket.unpipe(ws);
});
});
});
1.2.5 unpipe
const net = require('net');
const path = require('path');
let file = require('fs').createWriteStream(path.join(__dirname, 'msg.txt'));
let server = net.createServer(function (socket) {
socket.pipe(file, {
end: false
});
setTimeout(function () {
file.end('bye bye');
socket.unpipe(file);
}, 5000);
// socket.on('end', function () {
// file.end('bye bye');
// });
});
server.listen(8080);
1.2.5 pause&resume
pause 可以暫停 data 事件觸發(fā),服務(wù)器會(huì)把客戶端發(fā)送的數(shù)據(jù)暫存在緩存區(qū)里
const net = require('net');
const net = require('net');
const path = require('path');
let file = require('fs').createWriteStream(path.join(__dirname, 'msg.txt'));
let server = net.createServer(function (socket) {
socket.pause();
setTimeout(function () {
socket.resume();
socket.pipe(file);
}, 10 * 1000);
});
server.listen(8080);
1.2.6 setTimeout
let net = require('net');
let path = require('path');
let ws = require('fs').createWriteStream(path.resolve(__dirname, 'msg.txt'));
let server = net.createServer(function (socket) {
socket.setTimeout(5 * 1000);
socket.pause();
socket.on('timeout', function () {
socket.pipe(ws);
});
//socket.setTimeout(0);取消超時(shí)時(shí)間的設(shè)置
});
server.listen(8080);
1.2 TCP客戶端
1.2.1 創(chuàng)建TCP客戶端
let socket = new net.Socket([options])
- fd socket文件描述符
- type 客戶端所有協(xié)議
- allowHalfOpen 是否允許半連接,服務(wù)器收到FIN包時(shí)不回發(fā)FIN包,可以使服務(wù)器可以繼續(xù)向客戶端發(fā)數(shù)據(jù)
socket.connect(port, host, callback);
socket.on('connect', callback);
1.2.2 向服務(wù)器端寫入數(shù)據(jù)、end 、error、destroy,close
- write表示向服務(wù)器寫入數(shù)據(jù)
- end 用于結(jié)束連接
- error 連接發(fā)生錯(cuò)誤
- destroy 銷毀流
- close 表示連接關(guān)閉成功,hasError=true代表有可能有錯(cuò)誤
socket.write(data,[encoding],[callback]);
let net = require('net');
let server = net.createServer(function (socket) {
console.log("客戶端已經(jīng)連接");
socket.setEncoding('utf8');
socket.on('data', function (data) {
console.log("已接收客戶端發(fā)送的數(shù)據(jù):%s", data);
socket.write('服務(wù)器:' + data);
})
socket.on('error', function (err) {
console.log('與客戶端通信過程中發(fā)生了錯(cuò)誤,錯(cuò)誤編碼為%s', err.code);
socket.destroy();
});
socket.on('end', function (err) {
console.log('客戶端已經(jīng)關(guān)閉連接');
socket.destroy();
});
socket.on('close', function (hasError) {
console.log(hasError ? '異常關(guān)閉' : '正常關(guān)閉');
});
});
server.listen(808, function () {
let client = new net.Socket();
client.setEncoding('utf8');
client.connect(808, '127.0.0.1', function () {
console.log('客戶端已連接');
client.write('hello');
setTimeout(function () {
client.end('byebye');
}, 5000);
});
client.on('data', function (data) {
console.log('已經(jīng)接收到客戶端發(fā)過來的數(shù)據(jù):%s', data);
});
client.on('error', function (err) {
console.log('與服務(wù)器通信過程中發(fā)生了錯(cuò)誤,錯(cuò)誤編碼為%s', err.code);
client.destroy();
});
});
1.2.3 close
停止server接受建立新的connections并保持已經(jīng)存在的connections
server.getConnections((err, count) => {
if (count == 2) server.close();
});
1.2.4 unref&ref
unref方法指定發(fā)客戶端連接被全部關(guān)閉時(shí)退出應(yīng)用程序 如果將allowHalfOpen方法,必須使用與客戶端連接的socket端口對(duì)象的end 方法主動(dòng)關(guān)閉服務(wù)器端連接
let net = require('net');
let server = net.createServer({ allowHalfOpen: true }, function (socket) {
console.log("客戶端已經(jīng)連接");
socket.setEncoding('utf8');
socket.on('data', function (data) {
console.log("已接收客戶端發(fā)送的數(shù)據(jù):%s", data);
socket.write('服務(wù)器確認(rèn)數(shù)據(jù):' + data);
})
socket.on('error', function (err) {
console.log('與客戶端通信過程中發(fā)生了錯(cuò)誤,錯(cuò)誤編碼為%s', err.code);
socket.destroy();
});
socket.on('end', function (err) {
console.log('客戶端已經(jīng)關(guān)閉連接');
socket.end();
server.unref();
});
socket.on('close', function (hasError) {
if (hasError) {
console.log('由于錯(cuò)誤導(dǎo)致socket關(guān)閉');
server.unref();
} else {
console.log('端口正常關(guān)閉');
}
});
server.getConnections((err, count) => {
if (count == 2) server.close();
});
});
server.listen(808, function () { });
server.on('close', function () {
console.log('服務(wù)器關(guān)閉');
});
1.2.5 bufferSize
write的返回值和bufferSize屬性值
let server = net.createServer({ allowHalfOpen: true }, function (socket) {
console.log("客戶端已經(jīng)連接");
socket.setEncoding('utf8');
let rs = fs.createReadStream(path.resolve(__dirname, 'a.txt'), { highWaterMark: 2 });
rs.on('data', function (data) {
let flag = socket.write(data);
console.log("flag:", flag);
console.log('緩存字節(jié):' + socket.bufferSize);
console.log('已發(fā)送字節(jié):' + socket.bytesWritten);
})
socket.on('data', function (data) {
console.log('data', data);
});
socket.on('drain', function (err) {
"緩存區(qū)已全部發(fā)送"
});
});
1.2.6 keepAlive
當(dāng)服務(wù)器和客戶端建立連接后,當(dāng)一方主機(jī)突然斷電、重啟、系統(tǒng)崩潰等意外情況時(shí),將來不及向另一方發(fā)送FIN包,這樣另一方將永遠(yuǎn)處于連接狀態(tài)。 可以使用setKeepAlive方法來解決這一個(gè)問題
socket.setKeepAlive([enaable],[initialDelay]);
- enable 是否啟用嗅探,為true時(shí)會(huì)不但向?qū)Ψ桨l(fā)送探測(cè)包,沒有響應(yīng)則認(rèn)為對(duì)方已經(jīng)關(guān)閉連接,自己則關(guān)閉連接
- initialDelay 多久發(fā)送一次探測(cè)包,單位是毫秒
1.2.7 聊天室1.0
/**
* 1.創(chuàng)建一個(gè)服務(wù)器
* 2. 客戶端可以連接服務(wù)器
* 3.客戶端可以發(fā)言,然后廣播給大家
* 4.客戶端連接和退出后都要通知大家。
* 5.顯示當(dāng)前的在線人數(shù)
*/
let net = require('net');
let util = require('util');
let clients = {};
let server = net.createServer(function (socket) {
server.getConnections(function (err, count) {
socket.write(`weclome,there is ${count} users now,please input your username\r\n`);
});
let nickname;
socket.setEncoding('utf8');
socket.on('data', function (data) {
data = data.replace(/\r\n/, '');
if (data == 'byebye') {
socket.end();
} else {
if (nickname) {
broadcast(nickname, `${nickname}:${data}`);
} else {
nickname = data;
clients[nickname] = socket;
broadcast(nickname, `welcome ${nickname} joined us!`);
}
}
});
socket.on('close', function () {
socket.destroy();
});
}).listen(8088);
function broadcast(nickname, msg) {
for (let key in clients) {
if (key != nickname) {
clients[key].write(msg + '\r\n');
clients[nickname].destroy();
delete clients[nickname];
}
}
}
1.2.8 聊天室2.0
var key = scoket.remoteAddress+':'+socket.remotePort;
users[key] = {name:'匿名',socket};
socket.on('data',function(){
parse(data);
});
function parse(msg){
swtich(msg.type){
case 'secret':
secret(msg.user,msg.text);
break;
}
case 'boardcast':
boardcast(message.text);
break;
case 'cname':
cname(messsage.text);
break;
case 'list':
list();
break;
default:
socket.write('不能識(shí)別命令');
break;
}
function secret(user,text){
}
function boardcast(text){
}
function cname(text){
}
function list(){
}
b:text 廣播
c:nickname:text 私聊
n:nickname 改名
l 列出在線用戶列表
on('data',function(data){
if(data == 'quit){
}else if(data == 'help'){
}else(){
write(data);
}
});
function convert(){
}
1.3 類方法
- isIP 判斷字符串是否是IP
- isIPv4 判斷字符串是否是IPv4地址
- isIPv6 判斷字符串是否是IPv6地址
2. UDP
2.1 創(chuàng)建socket
let socket = dgram.createSocket(type,[callback]);
socket.on('messsage',function(msg,rinfo){});
- type 必須輸入,制定時(shí)udp4還是udp6
- callback 從該接口接收到數(shù)據(jù)時(shí)調(diào)用的回調(diào)函數(shù)
- msg 接收到的數(shù)據(jù)
- rinfo 信息對(duì)象
- address 發(fā)送著的地址
- family ipv4還是ipv6
- port 發(fā)送者的socket端口號(hào)
- size 發(fā)送者所發(fā)送的數(shù)據(jù)字節(jié)數(shù)
socket.bind(port,[address],[callback]);
socket.on('listening',callabck;
- port 綁定的端口號(hào)
- address 監(jiān)聽的地址
- callback 監(jiān)聽成功后的回調(diào)函數(shù)
2.2 向外發(fā)送數(shù)據(jù)
如果發(fā)送數(shù)據(jù)前還沒有綁定過地址和端口號(hào),操作系統(tǒng)將為其分配一個(gè)隨機(jī)端口并可以接收任何地址的數(shù)據(jù)
socket.send(buf,offset,length,port,address,[callback]);
- buffer 代表緩存區(qū)
- offset 從緩存區(qū)第幾個(gè)字節(jié)開始發(fā)
- length 要發(fā)送的字節(jié)數(shù)
- port 對(duì)方的端口號(hào)
- address 接收數(shù)據(jù)的socket地址
- callback 制定當(dāng)數(shù)據(jù)發(fā)送完畢時(shí)所需要的回調(diào)函數(shù)
- err 錯(cuò)誤對(duì)象
- byets 實(shí)際發(fā)送的字節(jié)數(shù)
2.3 address
獲取此socket相關(guān)的地址信息
let address = socket.address();
- port
- address
- family
2.4 UDP服務(wù)器
var dgram = require('dgram');
var socket = dgram.createSocket('udp4');
socket.on('message',function(msg,rinfo){
console.log(msg.toString());
console.log(rinfo);
socket.send(msg,0,msg.length,rinfo.port,rinfo.address);
});
socket.bind(41234,'localhost');
2.5 UDP客戶端
var dgram = require('dgram');
var socket = dgram.createSocket('udp4');
socket.on('message',function(msg,rinfo){
console.log(msg.toString());
console.log(rinfo);
});
socket.setTTL(128);
socket.send(new Buffer('zz'),0,6,41234,'localhost',function(err,bytes){
console.log('發(fā)送了個(gè)%d字節(jié)',bytes);
});
socket.on('error',function(err){
console.error(err);
});
2.6 廣播
創(chuàng)建一個(gè)UDP服務(wù)器并通過該服務(wù)器進(jìn)行數(shù)據(jù)的廣播
2.6.1 服務(wù)器
let dgram = require('dgram');
let server = dgram.createSocket('udp4);
server.on('message',function(msg){
let buf = new Bufffer('已經(jīng)接收客戶端發(fā)送的數(shù)據(jù)'+msg);
server.setBroadcast(true);
server.send(buf,0,buf.length,41235,"192.168.1.255");
});
server.bind(41234,'192.168.1.100');
2.6.2 客戶端
let dgram = require('dgram');
let client = dgram.createSocket('udp4);
client.bind(41235,'192.168.1.102);
let buf = new Buffer('hello');
client.send(buf,0,buf.length,41234,'192.168.1.100');
client.on('message',function(msg,rinfo){
console.log('received : ',msg);
});
2.7 組播
所謂的組播,就是將網(wǎng)絡(luò)中同一業(yè)務(wù)類型進(jìn)行邏輯上的分組,從某個(gè)socket端口上發(fā)送的數(shù)據(jù)只能被該組中的其他主機(jī)所接收,不被組外的任何主機(jī)接收。
實(shí)現(xiàn)組播時(shí),并不直接把數(shù)據(jù)發(fā)送給目標(biāo)地址,而是將數(shù)據(jù)發(fā)送到組播主機(jī),操作系統(tǒng)將把該數(shù)據(jù)組播給組內(nèi)的其他所有成員。
在網(wǎng)絡(luò)中,使用D類地址作為組播地址。范圍是指 224.0.0.0 ~ 239.255.255.255,分為三類
- 局部組播地址: 224.0.0.0 ~ 224.0.0.255 為路由協(xié)議和其他用途保留
- 預(yù)留組播地址: 224.0.1.0 ~ 238.255.255.255 可用于全球范圍或網(wǎng)絡(luò)協(xié)議
- 管理權(quán)限組播地址 : 239.0.0.0 ~ 239.255.255.255 組織內(nèi)部使用,不可用于Internet
把該socket端口對(duì)象添加到組播組中。
socket.addMembership(multicastAddress,[multicastInterface]);
- multicastAddress 必須指定,需要加入的組播組地址
- multicastInterface 可選參數(shù),需要加入的組播組地址
socket.dropMembership(multicastAddress,[multicastInterface]); socket.setMulticastTTL(ttl); socket.setMulticastLoopback(flag);
2.7.1 服務(wù)器
let dgram = require('dgram');
let server = dgram.createSocket('udp4');
server.on('listening',function(){
server.MulticastTTL(128);
server.setMulticastLoopback(true);
server.addMembership('230.185.192.108');
});
setInterval(broadcast,1000);
function broadcast(){
let buffer = Buffer.from(new Date().toLocaleString());
server.send(buffer,0,buffer.length,8080,"230.185.192.108");
}
2.7.2 客戶端
let dgram = require('dgram');
let client = dgram.createSocket('udp4');
client.on('listening',function(){
client.addMembership('230.185.192.108');
});
client.on('message',function(message,remote){
console.log(message.toString());
});
client.bind(8080,'192.168.1.103');
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Node.js dgram模塊實(shí)現(xiàn)UDP通信示例代碼
- nodejs dgram模塊廣播+組播的實(shí)現(xiàn)示例
- nodejs實(shí)現(xiàn)UDP組播示例方法
- Node.js學(xué)習(xí)之TCP/IP數(shù)據(jù)通訊(實(shí)例講解)
- 基于Nodejs的Tcp封包和解包的理解
- Nodejs之TCP服務(wù)端與客戶端聊天程序詳解
- nodejs簡單實(shí)現(xiàn)TCP服務(wù)器端和客戶端的聊天功能示例
- Nodejs創(chuàng)建TCP服務(wù)器 - king0222
- node.js中TCP Socket多進(jìn)程間的消息推送示例詳解
- Node.js創(chuàng)建Web、TCP服務(wù)器
- node.js基于dgram數(shù)據(jù)報(bào)模塊創(chuàng)建UDP服務(wù)器和客戶端操作示例
相關(guān)文章
node.js使用yargs處理命令行參數(shù)操作示例
這篇文章主要介紹了node.js使用yargs處理命令行參數(shù)操作,結(jié)合實(shí)例形式分析了yargs庫的安裝及node.js使用yargs處理命令行參數(shù)具體實(shí)現(xiàn)技巧,需要的朋友可以參考下2020-02-02
npm?install安裝失敗報(bào)錯(cuò):The?operation?was?rejected?by?your?
這篇文章主要給大家介紹了關(guān)于npm?install安裝失敗報(bào)錯(cuò):The?operation?was?rejected?by?your?operating?system的相關(guān)資料,文中給出了多種解決方法供大家參考學(xué)習(xí),需要的朋友可以參考下2023-04-04
Node.js(v16.13.2版本)安裝及環(huán)境配置的圖文教程
本文主要介紹了Node.js(v16.13.2版本)安裝及環(huán)境配置的圖文教程,文中通過圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-05-05

