深入Node TCP模块的理解
1. TCP
在Node.js中,提供了net模块用来实现TCP服务器和客户端的通信。
1.1 TCP服务器
net.createServer([options][, connectionListener])
- options.allowHalfOpen 是否允许单方面连接,默认值为false
- connectionListener参数用于指定当客户端与服务器建立连接时所要调用的回调函数,回调中有一个参数socket,指的是TCP服务器监听的socket端口对象
也可以通过监听connection事件的方式来指定监听函数
server.on('connection',function(socket){});
1.1.1 启动TCP服务器
可以使用listen方法通知服务器开始监听客户端的连接
server.listen(port,[host],[backlog],[callback])
- port 必须指定的端口号
- host 指定需要监听的IP地址或主机名,如果省略的话服务器将监听来自于任何客户端的连接
- backlog指定位于等待队列中的客户端连接的最大数量,默认值为511
server.on('listening',function(){});
1.1.2 使用TCP服务器
let net = require('net');
let server = net.createServer(function(socket){
console.log('客户端已连接');
});
server.listen(8080,'localhost',function(){
console.log('服务器开始监听');
});
1.1.3 address
server.address()
- port 端口号
- address TCP服务器监听的地址
- family 协议的版本
1.1.4 getConnections
查看当前与TCP服务器建立连接的客户端的连接数量以及设置最大连接数量
server.getConnections(callback);
server.maxConnections = 2;
1.1.5 close
使用close方法可以显式拒绝所有的客户端的连接请求,当所有已连接的客户端关闭后服务器会自动关闭,并触发服务器的close事件。
server.close();
server.on('close',callback);
1.2 socket
1.2.1 address
net.Socket代表一个socket端口对象,它是一个可读可写流。
let net = require('net');
let util = require('util');
let server = net.createServer(function(socket){
server.getConnections((err,count)=>{
server.maxConnections = 1;
console.log('最大连接数量%d,当前连接数量%d',server.maxConnections,count);
});
let address = socket.address();
console.log('客户端地址 %s',util.inspect(address));
});
1.2.2 读取数据
let server = net.createServer(function (socket) {
socket.setEncoding('utf8');
socket.on('data', function (data) {
console.log('本次收到的内容为%s,累计收到的字节数是%d', data, socket.bytesRead);
});
});
1.2.3 监听关闭事件
let server = net.createServer(function (socket) {
socket.on('end', function () {
console.log('客户端已经关闭');
});
});
1.2.4 pipe
pipe方法可以将客户端发送的数据写到文件或其它目标中。
socket.pipe(destinatin,[options]);
options.end 设置为false时当客户端结束写操作或关闭后并不会关闭目标对象,还可以继续写入数据
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 事件触发,服务器会把客户端发送的数据暂存在缓存区里
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);取消超时时间的设置
});
server.listen(8080);
1.2 TCP客户端
1.2.1 创建TCP客户端
let socket = new net.Socket([options])
- fd socket文件描述符
- type 客户端所有协议
- allowHalfOpen 是否允许半连接,服务器收到FIN包时不回发FIN包,可以使服务器可以继续向客户端发数据
socket.connect(port, host, callback);
socket.on('connect', callback);
1.2.2 向服务器端写入数据、end 、error、destroy,close
- write表示向服务器写入数据
- end 用于结束连接
- error 连接发生错误
- destroy 销毁流
- close 表示连接关闭成功,hasError=true代表有可能有错误
socket.write(data,[encoding],[callback]);
let net = require('net');
let server = net.createServer(function (socket) {
console.log("客户端已经连接");
socket.setEncoding('utf8');
socket.on('data', function (data) {
console.log("已接收客户端发送的数据:%s", data);
socket.write('服务器:' + data);
})
socket.on('error', function (err) {
console.log('与客户端通信过程中发生了错误,错误编码为%s', err.code);
socket.destroy();
});
socket.on('end', function (err) {
console.log('客户端已经关闭连接');
socket.destroy();
});
socket.on('close', function (hasError) {
console.log(hasError ? '异常关闭' : '正常关闭');
});
});
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('已经接收到客户端发过来的数据:%s', data);
});
client.on('error', function (err) {
console.log('与服务器通信过程中发生了错误,错误编码为%s', err.code);
client.destroy();
});
});
1.2.3 close
停止server接受建立新的connections并保持已经存在的connections
server.getConnections((err, count) => {
if (count == 2) server.close();
});
1.2.4 unref&ref
unref方法指定发客户端连接被全部关闭时退出应用程序 如果将allowHalfOpen方法,必须使用与客户端连接的socket端口对象的end 方法主动关闭服务器端连接
let net = require('net');
let server = net.createServer({ allowHalfOpen: true }, function (socket) {
console.log("客户端已经连接");
socket.setEncoding('utf8');
socket.on('data', function (data) {
console.log("已接收客户端发送的数据:%s", data);
socket.write('服务器确认数据:' + data);
})
socket.on('error', function (err) {
console.log('与客户端通信过程中发生了错误,错误编码为%s', err.code);
socket.destroy();
});
socket.on('end', function (err) {
console.log('客户端已经关闭连接');
socket.end();
server.unref();
});
socket.on('close', function (hasError) {
if (hasError) {
console.log('由于错误导致socket关闭');
server.unref();
} else {
console.log('端口正常关闭');
}
});
server.getConnections((err, count) => {
if (count == 2) server.close();
});
});
server.listen(808, function () { });
server.on('close', function () {
console.log('服务器关闭');
});
1.2.5 bufferSize
write的返回值和bufferSize属性值
let server = net.createServer({ allowHalfOpen: true }, function (socket) {
console.log("客户端已经连接");
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('缓存字节:' + socket.bufferSize);
console.log('已发送字节:' + socket.bytesWritten);
})
socket.on('data', function (data) {
console.log('data', data);
});
socket.on('drain', function (err) {
"缓存区已全部发送"
});
});
1.2.6 keepAlive
当服务器和客户端建立连接后,当一方主机突然断电、重启、系统崩溃等意外情况时,将来不及向另一方发送FIN包,这样另一方将永远处于连接状态。 可以使用setKeepAlive方法来解决这一个问题
socket.setKeepAlive([enaable],[initialDelay]);
- enable 是否启用嗅探,为true时会不但向对方发送探测包,没有响应则认为对方已经关闭连接,自己则关闭连接
- initialDelay 多久发送一次探测包,单位是毫秒
1.2.7 聊天室1.0
/**
* 1.创建一个服务器
* 2. 客户端可以连接服务器
* 3.客户端可以发言,然后广播给大家
* 4.客户端连接和退出后都要通知大家。
* 5.显示当前的在线人数
*/
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('不能识别命令');
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 创建socket
let socket = dgram.createSocket(type,[callback]);
socket.on('messsage',function(msg,rinfo){});
- type 必须输入,制定时udp4还是udp6
- callback 从该接口接收到数据时调用的回调函数
- msg 接收到的数据
- rinfo 信息对象
- address 发送着的地址
- family ipv4还是ipv6
- port 发送者的socket端口号
- size 发送者所发送的数据字节数
socket.bind(port,[address],[callback]);
socket.on('listening',callabck;
- port 绑定的端口号
- address 监听的地址
- callback 监听成功后的回调函数
2.2 向外发送数据
如果发送数据前还没有绑定过地址和端口号,操作系统将为其分配一个随机端口并可以接收任何地址的数据
socket.send(buf,offset,length,port,address,[callback]);
- buffer 代表缓存区
- offset 从缓存区第几个字节开始发
- length 要发送的字节数
- port 对方的端口号
- address 接收数据的socket地址
- callback 制定当数据发送完毕时所需要的回调函数
- err 错误对象
- byets 实际发送的字节数
2.3 address
获取此socket相关的地址信息
let address = socket.address();
- port
- address
- family
2.4 UDP服务器
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('发送了个%d字节',bytes);
});
socket.on('error',function(err){
console.error(err);
});
2.6 广播
创建一个UDP服务器并通过该服务器进行数据的广播
2.6.1 服务器
let dgram = require('dgram');
let server = dgram.createSocket('udp4);
server.on('message',function(msg){
let buf = new Bufffer('已经接收客户端发送的数据'+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 组播
所谓的组播,就是将网络中同一业务类型进行逻辑上的分组,从某个socket端口上发送的数据只能被该组中的其他主机所接收,不被组外的任何主机接收。
实现组播时,并不直接把数据发送给目标地址,而是将数据发送到组播主机,操作系统将把该数据组播给组内的其他所有成员。
在网络中,使用D类地址作为组播地址。范围是指 224.0.0.0 ~ 239.255.255.255,分为三类
- 局部组播地址: 224.0.0.0 ~ 224.0.0.255 为路由协议和其他用途保留
- 预留组播地址: 224.0.1.0 ~ 238.255.255.255 可用于全球范围或网络协议
- 管理权限组播地址 : 239.0.0.0 ~ 239.255.255.255 组织内部使用,不可用于Internet
把该socket端口对象添加到组播组中。
socket.addMembership(multicastAddress,[multicastInterface]);
- multicastAddress 必须指定,需要加入的组播组地址
- multicastInterface 可选参数,需要加入的组播组地址
socket.dropMembership(multicastAddress,[multicastInterface]);
socket.setMulticastTTL(ttl);
socket.setMulticastLoopback(flag);
2.7.1 服务器
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');
以上是 深入Node TCP模块的理解 的全部内容, 来源链接: utcz.com/z/342085.html