Part5-1-3 Nodejs 通信

通信必要条件

主机之间需要有传输介质(网线,wifi,蓝牙)

主机上必须有网卡设备(调制与解调制,将电压与二进制数据进行转换)

主机之间需要协商网络速率

常见通讯方式

交换机通讯

路由器通讯

建立多台主机互联

定位局域网中的其他主机

通过 Mac 地址来唯一标识一台主机

但是交换机无法满足互联网需求:

交换机的接口数量有上限

局域网存在大量主机会造成广播风暴

 明确目标主机 IP 地址

网络层次模型

OSI七层模型:

应用层:用户与网络的接口

表示层:数据加密、转换、压缩

会话层:控制网络连接建立与终止

传输层:控制数据传输可靠性

网络层:确定目标网络

数据链路层:确定目标主机

物理层:各种物理设备和标准

数据从 A 至 B,先封装再解封

TCP协议

TCP 属于传输层协议

TCP 是面向连接的协议

TCP 用于处理实时通信

常见控制字段

SYN = 1 表示请求建立连接

FIN = 1 表示请求断开连接

ACK = 1 表示数据信息确认

三次握手

创建 TCP 通信

Net 模块实现了底层通信接口

通信过程:

创建服务端:接收和回写客户端数据

创建客户端:发送和接收服务端数据

数据传输:内置服务事件和方法读写数据

通信事件:

listening 事件:调用 server.listen 方法之后触发

connection 事件:新的连接建立时触发

close 事件:当 server 关闭时触发

error 事件:当错误出现的时候触发

通信事件 & 方法:

data 事件:当接收到数据的时候触发该事件

write 方法:在 socket 上发送数据,默认是 utf8 编码

end 操作:当 socket 的一端发送 FIN 包时触发,结束可读端

server.js

const net = require('net')

// 创建服务端实例
const server = net.createServer()

const PORT = 1234
const HOST = 'localhost'

server.listen(PORT, HOST)

server.on('listening', () => {
  console.log(`服务端已经开启在 ${HOST}: ${PORT}`)
})

// 接收消息 回写消息
server.on('connection', (socket) => {
  socket.on('data', (chunk) => {
    const msg = chunk.toString()
    console.log(msg)

    // 回数据
    socket.write(Buffer.from('您好' + msg))
  })
})

server.on('close', () => {
  console.log('服务端关闭了')
})

server.on('error', (err) => {
  if (err.code == 'EADDRINUSE') {
    console.log('地址正在被使用')
  }else{
    console.log(err)
  }
})

client.js

const net = require('net')

const client = net.createConnection({
  port: 1234, 
  host: '127.0.0.1'
})

client.on('connect', () => {
  client.write('前端工程')
})

client.on('data', (chunk) => {
  console.log(chunk.toString())
})

client.on('error', (err) => {
  console.log(err)
})

client.on('close', () => {
  console.log('客户端断开连接')
})

TCP 数据粘包

通信包含数据发送端和接收端

发送端累积数据统一发送

接收端缓冲数据之后再消费

所以对于数据的使用就会产生粘包的问题

TCP拥塞机制决定发送时机

client.js

const net = require('net')

const client = net.createConnection({
  port: 1234, 
  host: '127.0.0.1'
})

let dataArr = [
  '前端工程2', 
  '前端工程3', 
  '前端工程4', 
  '前端工程5', 
]

client.on('connect', () => {
  client.write('前端工程1')
  for(let i=0; i<dataArr.length; i++) {
    (function (val, index) {
      setTimeout(() => {
        client.write(val)
      }, 1000 * (index +1))
    })(dataArr[i], i)
  }
})

client.on('data', (chunk) => {
  console.log(chunk.toString())
})

client.on('error', (err) => {
  console.log(err)
})

client.on('close', () => {
  console.log('客户端断开连接')
})

数据的封包与拆包

数据传输过程

进行数据编码,获取二进制数据包

按规则拆解数据,获取指定长度的数据

Buffer 数据读写

writelnt16BE:将 value 从指定位置写入

readInt16BE:从指定位置开始读取数据

封包解决粘包

myTransform.js

class MyTransformCode{
  constructor() {
    this.packageHeaderLen = 4
    this.serialNum = 0
    this.serialLen = 2
  }

  // 编码
  encode(data, serialNum) {
    const body = Buffer.from(data)

    // 01 先按照指定的长度来申请一片内存空间做为 header 来使用
    const headerBuf = Buffer.alloc(this.packageHeaderLen)

    // 02 
    headerBuf.writeInt16BE(serialNum || this.serialNum)

    headerBuf.writeInt16BE(body.length, this.serialLen)

    if (serialNum == undefined) {
      this.serialNum++
    }

    return Buffer.concat([headerBuf, body])
  }

  // 解码
  decode(buffer) {
    const headerBuf = buffer.slice(0, this.packageHeaderLen)
    const bodyBuf = buffer.slice(this.packageHeaderLen)

    return {
      serialNum: headerBuf.readInt16BE(),
      bodyLength: headerBuf.readInt16BE(this.serialLen),
      body: bodyBuf.toString()
    }
  }

  // 获取包长度的方法
  getPackageLen(buffer) {
    if (buffer.length < this.packageHeaderLen) {
      return 0
    } else {
      return this.packageHeaderLen + buffer.readInt16BE(this.serialLen)
    }
  }
}

module.exports = MyTransformCode

server.js

const net = require('net')
const MyTransform = require('./myTransform.js')

const server = net.createServer()

let overageBuffer = null
let ts = new MyTransform()

server.listen('1234', 'localhost')

server.on('listening', () => {
  console.log('服务端运行在 localhost:1234')
})

server.on('connection', (socket) => {
  socket.on('data', (chunk) => {
    if (overageBuffer) {
      chunk = Buffer.concat([overageBuffer, chunk])
    }
    let packageLen = 0
    while(packageLen = ts.getPackageLen(chunk)) {
      const packageCon = chunk.slice(0, packageLen)
      chunk = chunk.slice(packageLen)

      const ret = ts.decode(packageCon)
      console.log(ret)

      socket.write(ts.encode(ret.body, ret.serialNum))
    }
    overageBuffer = chunk
  })
})

client.js

const net = require('net')
const MyTransform = require('./myTransform.js')

let overageBuffer = null 
let ts = new MyTransform()

const client = net.createConnection({
  host: 'localhost', 
  port: 1234
})

client.write(ts.encode('前端工程1'))
client.write(ts.encode('前端工程2'))
client.write(ts.encode('前端工程3'))
client.write(ts.encode('前端工程4'))
client.write(ts.encode('前端工程5'))

client.on('data', (chunk) => {
  if (overageBuffer) {
    chunk = Buffer.concat([overageBuffer, chunk])
  }
  let packageLen = 0
  while(packageLen = ts.getPackageLen(chunk)) {
    const packageCon = chunk.slice(0, packageLen)
    chunk = chunk.slice(packageLen)

    const ret = ts.decode(packageCon)
    console.log(ret)
  }
  overageBuffer = chunk
})

1

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值