深入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 相关文章推荐
javascript跨域刷新实现代码
Jan 01 Javascript
jQuery用unbind方法去掉hover事件及其他方法介绍
Mar 18 Javascript
JavaScript生成SQL查询表单的方法
Aug 13 Javascript
react实现pure render时bind(this)隐患需注意!
Mar 09 Javascript
HTML中使背景图片自适应浏览器大小实例详解
Apr 06 Javascript
使用selenium抓取淘宝的商品信息实例
Feb 06 Javascript
JavaScript实现星级评价效果
May 17 Javascript
js实现随机div颜色位置 类似满天星效果
Oct 24 Javascript
vue实例的选项总结
Jun 09 Javascript
ES6 十大特性简介
Dec 09 Javascript
Vue Element-ui表单校验规则实现
Jul 09 Vue.js
JS数组方法some、every和find的使用详情
Oct 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
PHP网站备份程序代码分享
2011/06/10 PHP
php数组函数序列之array_unshift() 在数组开头插入一个或多个元素
2011/11/07 PHP
php修改文件上传限制方法汇总
2015/04/07 PHP
YII2框架中查询生成器Query()的使用方法示例
2020/03/18 PHP
jquery数据验证插件(自制,简单,练手)实例代码
2013/10/24 Javascript
JavaScript获取网页表单提交方式的方法
2015/04/02 Javascript
jQuery插件PageSlide实现左右侧栏导航菜单
2015/04/12 Javascript
jquery移动点击的项目到列表最顶端的方法
2015/06/24 Javascript
Bootstrap中的Dropdown下拉菜单更改为悬停(hover)触发
2016/08/31 Javascript
vue.js声明式渲染和条件与循环基础知识
2017/07/31 Javascript
20行JS代码实现粘贴板复制功能
2018/02/06 Javascript
p5.js临摹动态图形的方法
2019/10/23 Javascript
JavaScript修改注册表实例代码
2020/01/05 Javascript
原生js实现表格循环滚动
2020/11/24 Javascript
jquery自定义组件实例详解
2020/12/31 jQuery
Vue2.x-使用防抖以及节流的示例
2021/03/02 Vue.js
python通过yield实现数组全排列的方法
2015/03/18 Python
Python的Django框架中自定义模版标签的示例
2015/07/20 Python
python merge、concat合并数据集的实例讲解
2018/04/12 Python
python3实现磁盘空间监控
2018/06/21 Python
基于随机梯度下降的矩阵分解推荐算法(python)
2018/08/31 Python
Python高级特性与几种函数的讲解
2019/03/08 Python
使用python制作一个解压缩软件
2019/11/13 Python
Django框架models使用group by详解
2020/03/11 Python
英国Flybe航空官网:欧洲最大的独立支线廉价航空公司
2019/07/15 全球购物
师范应届生求职信
2013/11/15 职场文书
研发工程师的岗位职责
2013/11/18 职场文书
网站设计师的岗位职责
2013/11/21 职场文书
求职自荐信范文格式
2013/11/29 职场文书
财务会计专业求职信范文
2013/12/31 职场文书
新学期教师寄语
2014/04/02 职场文书
体育活动总结范文
2014/05/04 职场文书
技术员岗位职责
2015/02/04 职场文书
计划生育责任书
2015/05/09 职场文书
有关信念的名言语录集锦
2019/12/06 职场文书
Python代码风格与编程习惯重要吗?
2021/06/03 Python