深入Node TCP模块的理解


Posted in Javascript onMarch 13, 2019

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');

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
Gird组件 Part-3:范例RSSFeed Viewer
Mar 10 Javascript
解决jquery .ajax 在IE下卡死问题的解决方法
Oct 26 Javascript
html超链接打开窗口大小的方法
Mar 05 Javascript
用jquery写的一个万年历(自写)
Jan 20 Javascript
node.js从数据库获取数据
May 08 Javascript
JavaScript中原型链存在的问题解析
Sep 25 Javascript
JQuery统计input和textarea文字输入数量(代码分享)
Dec 29 Javascript
javascript 跨域问题以及解决办法
Jul 17 Javascript
vue-router路由与页面间导航实例解析
Nov 07 Javascript
微信小程序实现留言功能
Oct 31 Javascript
layui自定义插件citySelect实现省市区三级联动选择
Jul 26 Javascript
如何实现iframe父子传参通信
Feb 05 Javascript
详解如何使用微信小程序云函数发送短信验证码
Mar 13 #Javascript
vue计算属性computed的使用方法示例
Mar 13 #Javascript
vue防止花括号{{}}闪烁v-text和v-html、v-cloak用法示例
Mar 13 #Javascript
vue生命周期与钩子函数简单示例
Mar 13 #Javascript
微信小程序picker组件关于objectArray数据类型的绑定方法
Mar 13 #Javascript
vue实现百度下拉列表交互操作示例
Mar 12 #Javascript
vue基础之使用get、post、jsonp实现交互功能示例
Mar 12 #Javascript
You might like
什么是短波收听SWL
2021/03/01 无线电
用PHP实现读取和编写XML DOM代码
2010/04/07 PHP
解析PHP中intval()等int转换时的意外异常情况
2013/06/21 PHP
PHP基于文件存储实现缓存的方法
2015/07/20 PHP
用javascript实现在小方框中浏览大图的代码
2007/08/14 Javascript
jquery 使用简明教程
2014/03/05 Javascript
jQuery中find()方法用法实例
2015/01/07 Javascript
jQuery中on()方法用法实例
2015/01/19 Javascript
JS实现设置ff与ie元素绝对位置的方法
2016/03/08 Javascript
浅谈jquery点击label触发2次的问题
2016/06/12 Javascript
标准的js无缝滚动效果
2016/08/30 Javascript
微信端开发--登录小程序步骤
2017/01/11 Javascript
静态页面实现 include 引入公用代码的示例
2017/09/25 Javascript
jquery select插件异步实时搜索实例代码
2017/10/20 jQuery
vue.js根据代码运行环境选择baseurl的方法
2018/02/28 Javascript
vue webpack实用技巧总结
2018/04/24 Javascript
Phaser.js实现简单的跑酷游戏附源码下载
2018/10/26 Javascript
Vee-validate 父组件获取子组件表单校验结果的实例代码
2019/05/20 Javascript
JavaScript定时器常见用法实例分析
2019/11/15 Javascript
Bootstrap FileInput实现图片上传功能
2021/01/28 Javascript
Python常用正则表达式符号浅析
2014/08/13 Python
在Python中测试访问同一数据的竞争条件的方法
2015/04/23 Python
python利用有道翻译实现"语言翻译器"的功能实例
2017/11/14 Python
Python3 加密(hashlib和hmac)模块的实现
2017/11/23 Python
python 保存float类型的小数的位数方法
2018/10/17 Python
pytorch 加载(.pth)格式的模型实例
2019/08/20 Python
PYTHON如何读取和写入EXCEL里面的数据
2019/10/28 Python
python实现最短路径的实例方法
2020/07/19 Python
详解Python高阶函数
2020/08/15 Python
Python数据可视化常用4大绘图库原理详解
2020/10/23 Python
SkinCeuticals官网:美国药妆品牌
2018/04/19 全球购物
波兰办公用品和学校用品在线商店:Dlabiura24.pl
2020/11/18 全球购物
冰淇淋店的创业计划书
2014/02/07 职场文书
高中学生评语大全
2014/04/25 职场文书
2014年驾驶员工作总结
2014/11/18 职场文书
2015感人爱情寄语
2015/02/26 职场文书