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

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

(0)

相关推荐

  • Nodejs创建TCP服务器 - king0222

    1.Nodejs提供了net模块给我们,所以我们创建TCP服务器很简单: require('net').createServer(function(socket) { // new connection socket.on('data', function(data) { // got data }); socket.on('end', function(data) { // connection closed }); socket.write('Some string'); }).listen

  • Node.js创建Web、TCP服务器

    使用http模块创建Web服务器 Web服务器的功能: 接受HTTP请求(GET.POST.DELETE.PUT.PATCH) 处理HTTP请求(自己处理,或请求别的程序处理) 做出响应(返回页面.文件.各类数据等) 常见的Web服务器架构: Nginx.Apache:负责接受HTTP请求,确定谁来处理请求,并返回请求的结果 php-fpm / php模块:处理分配给自己的请求,并将处理结果返回给分配者 常见请求种类: 请求文件:包括静态文件(网页.图片.前端JavaScript文件.css文件

  • Node.js学习之TCP/IP数据通讯(实例讲解)

    1.使用net模块实现基于TCP的数据通讯 提供了一个net模块,专用于实现TCP服务器与TCP客户端之间的通信 1.1创建TCP服务器 在Node.js利用net模块创建TCP服务器 var server = net.createServer([options],[connectionListener]) //options:false当TCP服务器接收到客户端发送的一个FIN包时将会回发一个FIN包 true当TCP服务器接收到客户端发送的一个FIN包时将不会回发FIN包,这使得TCP服务器

  • 基于Nodejs的Tcp封包和解包的理解

    我们知道,TCP是面向连接流传输的,其采用Nagle算法,在缓冲区对上层数据进行了处理.避免触发自动分片机制和网络上大量小数据包的同时也造成了粘包(小包合并)和半包(大包拆分)问题,导致数据没有消息保护边界,接收端接收到一次数据无法判断是否是一个完整数据包.那有什么方案可以解决这问题呢? 1.粘包问题解决方案及对比 很简单,既然消息没有边界,那我们在消息往下传之前给它加一个边界识别就好了. 发送固定长度的消息 使用特殊标记来区分消息间隔 把消息的尺寸与消息一块发送 第一种方案不够灵活:第二种有风

  • node.js中TCP Socket多进程间的消息推送示例详解

    前言 前段时间接到了一个支付中转服务的需求,即支付数据通过http接口传到中转服务器,中转服务器将支付数据发送到异构后台(Lua)的指定tcp socket. 一开始评估的时候感觉蛮简单的,就是http server和tcp server间的通信,不是一个Event实例就能解决的状态管理问题吗?注册一个事件A用于消息传递,在socket连接时注册唯一的ID,然后在http接收到数据时,emit事件A:在监听到事件A时,在tcp server中寻找指定ID对应的socket处理该数据即可. 尽管n

  • Nodejs之TCP服务端与客户端聊天程序详解

    TCP是用来计算机之间进行通信的,通过编写客户端和服务端聊天的代码,对于服务器与客户端的工作步骤有了深刻的了解,在这里根据了不起的Node.js一书来总结一下. TCP聊天程序需要用到Nodejs中的net模块,net模块是对TCP的封装,用于创建TCP服务端与客户端的. 服务器端 count:连接的客户端个数: users:用于存储客户端用户昵称的对象,对象值都为该客户端的Socket,Socket是一个接口,用于服务端与客户端通信. net.createServer:是用来创建TCP服务器,

  • nodejs简单实现TCP服务器端和客户端的聊天功能示例

    本文实例讲述了nodejs简单实现TCP服务器端和客户端的聊天功能.分享给大家供大家参考,具体如下: 服务器端 var net = require('net'); var server = net.createServer(); //聚合所有客户端 var sockets = []; //接受新的客户端连接 server.on('connection', function(socket){ console.log('got a new connection'); sockets.push(soc

  • 深入Node TCP模块的理解

    1. TCP 在Node.js中,提供了net模块用来实现TCP服务器和客户端的通信. 1.1 TCP服务器 net.createServer([options][, connectionListener]) options.allowHalfOpen 是否允许单方面连接,默认值为false connectionListener参数用于指定当客户端与服务器建立连接时所要调用的回调函数,回调中有一个参数socket,指的是TCP服务器监听的socket端口对象 也可以通过监听connection事

  • 详解Node.JS模块 process

    process 模块是 nodejs 提供给开发者用来和当前进程交互的工具,它的提供了很多实用的 API.从文档出发,管中窥豹,进一步认识和学习 process 模块: 如何处理命令参数? 如何处理工作目录? 如何处理异常? 如何处理进程退出? process 的标准流对象 深入理解 process.nextTick 如何处理命令参数? 命令行参数指的是 2 个方面: 传给 node 的参数.例如 node --harmony script.js --version 中,--harmony 就是

  • 学习Node.js模块机制

    一.CommonJS的模块规范 Node与浏览器以及 W3C组织.CommonJS组织.ECMAScript之间的关系 Node借鉴CommonJS的Modules规范实现了一套模块系统,所以先来看看CommonJS的模块规范. CommonJS对模块的定义十分简单,主要分为模块引用.模块定义和模块标识3个部分. 1. 模块引用 模块引用的示例代码如下: var math = require('math'); 在CommonJS规范中,存在require()方法,这个方法接受模块标识,以此引入一

  • Node.js模块封装及使用方法

    Node.js中也有一些功能的封装,类似C#的类库,封装成模块这样方便使用,安装之后用require()就能引入调用. 一.Node.js模块封装 1.创建一个名为censorify的文件夹 2.在censorify下创建3个文件censortext.js.package.json.README.md文件 1).在censortext.js下输入一个过滤特定单词并用星号代替的函数. var censoredWorlds=["sad","bad","mad&

  • 快速掌握Node.js模块封装及使用

    Node.js中也有一些功能的封装,类似C#的类库,封装成模块这样方便使用,安装之后用require()就能引入调用. 一.Node.js模块封装 1.创建一个名为censorify的文件夹 2.在censorify下创建3个文件censortext.js.package.json.README.md文件 1).在censortext.js下输入一个过滤特定单词并用星号代替的函数. var censoredWorlds=["sad","bad","mad&

  • node.js 模块和其下载资源的镜像设置的方法

    以前安装 electron 时总是失败,然后就在淘宝镜像上下载好相应版本的文件放到用户目录来解决问题. 后来研究发现 npm 不仅可以设置 node.js 模块仓库的代理, 同样可以设置像 electron.phantomjs. node-sass 等模块的镜像代理 一.设置淘宝镜像 (共三种方法) 1.环境变量 Unix: # electron export ELECTRON_MIRROR=https://npm.taobao.org/mirrors/electron/ # phantomjs

  • Node.js模块全局安装路径配置方法

    下载安装NodeJS后,在自己选择的路径下会有如下的文件: 默认情况下NodeJS安装会同时安装npm(模块管理器:用于管理用户require的模块,有全局和本地两种). 注: 全局:执行npm install <模块的名字> -g 就会将模块装在全局路径下,当用户在程序中require(<模块的名字>)的时候不用考虑模块在哪,如果不修改全局路径,用户下载的模块会默认在C:\Users\Administrator\AppData\Roaming\npm这个路径下. 局部:执行npm

  • 详细分析Node.js 模块系统

    为了让Node.js的文件可以相互调用,Node.js提供了一个简单的模块系统. 模块是Node.js 应用程序的基本组成部分,文件和模块是一一对应的.换言之,一个 Node.js 文件就是一个模块,这个文件可能是JavaScript 代码.JSON 或者编译过的C/C++ 扩展. 创建模块 在 Node.js 中,创建一个模块非常简单,如下我们创建一个 main.js 文件,代码如下: var hello = require('./hello'); hello.world(); 以上实例中,代

  • 深入理解Node module模块

    在开发复杂Web应用的时候,通常需要把各个功能进行拆分.封装到不同的文件并在需要的时候引用该文件,即进行代码的模块化管理.几乎所有的编程语言都有自己的模块组织方式,比如Java中的包.C#中的程序集,而Node采用CommonJS模块规范. 模块规范 CommonJS旨在将运行在浏览器之外的JS进行标准化,并已经解决了大量的JS问题(如全局命名冲突).Node对CommonJS的实现中,每个模块都会被封装在一个单独的JS文件中,即一个文件就是一个模块,而文件路径就是模块名.在编写每个模块时,都有

  • Node.js模块加载详解

    JavaScript是世界上使用频率最高的编程语言之一,它是Web世界的通用语言,被所有浏览器所使用.JavaScript的诞生要追溯到Netscape那个时代,它的核心内容被仓促的开发出来,用以对抗Microsoft,参与当时白热化的浏览器大战.由于过早的发布,无可避免的造成了它的一些不太好的特性. 尽管它的开发时间很短,但是JavaScript依然具备了很多强大的特性,不过,每个脚本共享一个全局命名空间这个特性除外. 一旦Web页面加载了JavaScript代码,它就会被注入到全局命名空间,

随机推荐