如何从零开始定义一个相似websocket的即时通信协议

clipboard.png

深南大道镇楼

定义一个本身的通信协议并不难,关键在于这个协议的可用性,可拓展性,复杂业务场景的实用性

即时通信应用中,客户端和服务器端均可以当作一个服务器

一块儿复习一下websocket

  • WebSocket是一种在单个TCP链接上进行全双工通讯的协议。WebSocket通讯协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。
  • WebSocket使得客户端和服务器之间的数据交换变得更加简单,容许服务端主动向客户端推送数据,在WebSocket API中,浏览器和服务器只须要完成一次握手,二者之间就直接能够建立持久性的链接,并进行双向数据传输。

说说ws协议的优势:

  • 说到优势,这里的对比参照物是 HTTP 协议,归纳地说就是:支持双向通讯,更灵活,更高效,可扩展性更好。
  • 支持双向通讯,实时性更强。
  • 更好的二进制支持。
  • 较少的控制开销。链接建立后,ws 客户端、服务端进行数据交换时,协议控制的数据包头部较小。在不* 包含头部的状况下,服务端到客户端的包头只有 2~10 字节(取决于数据包长度),客户端到服务端的的话,须要加上额外的 4 字节的掩码。而 HTTP 协议每次通讯都须要携带完整的头部。
  • 支持扩展。ws 协议定义了扩展,用户能够扩展协议,或者实现自定义的子协议。(好比支持自定义压缩算法等)
咱们先看看 web socket协议的实现具体过程,再用代码抽象,定义本身的即时通信协议:
  • 链接握手过程node

    • 关于WebSocket有一句很常见的话: Websocket复用了HTTP的握手通道, 它具体指的是:
    • 客户端经过HTTP请求与WebSocket服务器协商升级协议, 协议升级完成后, 后续的数据交换则遵守WebSocket协议
    • 客户端: 申请协议升级
    • 首先由客户端换发起协议升级请求, 根据WebSocket协议规范, 请求头必须包含以下的内容
GET / HTTP/1.1
    Host: localhost:8080
    Origin: http://127.0.0.1:3000
    Connection: Upgrade
    Upgrade: websocket
    Sec-WebSocket-Version: 13
    Sec-WebSocket-Key: w4v7O6xFTi36lq3RNcgctw
  • 请求头详解golang

    • 请求行: 请求方法必须是GET, HTTP版本至少是1.1
    • 请求必须含有Host
    • 若是请求来自浏览器客户端, 必须包含Origin
    • 请求必须含有Connection, 其值必须含有"Upgrade"记号
    • 请求必须含有Upgrade, 其值必须含有"websocket"关键字
    • 请求必须含有Sec-Websocket-Version, 其值必须是13
    • 请求必须含有Sec-Websocket-Key, 用于提供基本的防御, 好比无心的链接
  • 1.2 服务器: 响应协议升级
  • 服务器返回的响应头必须包含以下的内容web

    • HTTP/1.1 101 Switching Protocols
    • Connection:Upgrade
    • Upgrade: websocket
    • Sec-WebSocket-Accept: Oy4NRAQ13jhfONC7bP8dTKb4PTU=
    • 响应行: HTTP/1.1 101 Switching Protocols
    • 响应必须含有Upgrade, 其值为"weboscket"
    • 响应必须含有Connection, 其值为"Upgrade"
    • 响应必须含有Sec-Websocket-Accept, 根据请求首部的Sec-Websocket-key计算出来

Sec-WebSocket-Key/Accept的计算

  • 规范提到:
  • Sec-WebSocket-Key值由一个随机生成的16字节的随机数经过base64编码获得的
  • Key能够避免服务器收到非法的WebSocket链接, 好比http请求链接到websocket, 此时服务端能够直接拒绝
  • Key能够用来初步确保服务器认识ws协议, 但也不能排除有的http服务器只处理Sec-WebSocket-Key, 并不实现ws协议
  • Key能够避免反向代理缓存
  • 在浏览器中发起ajax请求, Sec-Websocket-Key以及相关header是被禁止的, 这样能够避免客户端发送ajax请求时, 意外请求协议升级
  • 最终须要强调的是: Sec-WebSocket-Key/Accept并非用来保证数据的安全性, 由于其计算/转换公式都是公开的, 并且很是简单, 最主要的做用是预防一些意外的状况

WebSocket通讯的最小单位是帧, 由一个或多个帧组成一条完整的消息, 交换数据的过程当中, 发送端和接收端须要作的事情以下:

  • 发送端: 将消息切割成多个帧, 并发送给服务端
  • 接收端: 接受消息帧, 并将关联的帧从新组装成完整的消息

数据帧格式详解

clipboard.png

  • FIN: 占1bit
  • 0表示不是消息的最后一个分片
  • 1表示是消息的最后一个分片
  • RSV1, RSV2, RSV3: 各占1bit, 通常状况下全为0, 与Websocket拓展有关, 若是出现非零的值且没有采用WebSocket拓展, 链接出错
Opcode: 占4bit

%x0: 表示本次数据传输采用了数据分片, 当前数据帧为其中一个数据分片
%x1: 表示这是一个文本帧
%x2: 表示这是一个二进制帧
%x3-7: 保留的操做代码, 用于后续定义的非控制帧
%x8: 表示链接断开
%x9: 表示这是一个心跳请求(ping)
%xA: 表示这是一个心跳响应(pong)
%xB-F: 保留的操做代码, 用于后续定义的非控制帧
  • Mask: 占1bitajax

    • 0表示不对数据载荷进行掩码异或操做
    • 1表示对数据载荷进行掩码异或操做
  • Payload length: 占7或7+16或7+64bit算法

    • 0~125: 数据长度等于该值
    • 126: 后续的2个字节表明一个16位的无符号整数, 值为数据的长度
    • 127: 后续的8个字节表明一个64位的无符号整数, 值为数据的长度
  • Masking-key: 占0或4bytes数据库

    • 1: 携带了4字节的Masking-key
    • 0: 没有Masking-key
    • 掩码的做用并非防止数据泄密,而是为了防止早期版本协议中存在的代理缓存污染攻击等问题
  • payload data: 载荷数据
  • 数据传递后端

    • WebSocket的每条消息可能被切分红多个数据帧, 当接收到一个数据帧时,会根据FIN值来判断, 是否为最后一个数据帧
    • 数据帧传递示例:
    • FIN=0, Opcode=0x1: 发送文本类型, 消息尚未发送完成,还有后续帧
    • FIN=0, Opcode=0x0: 消息没有发送完成, 还有后续帧, 接在上一条后面
    • FIN=1, Opcode=0x0: 消息发送完成, 没有后续帧, 接在上一条后面组成完整消息
正式开始定义属于咱们本身的通信协议:
  • 咱们为何要自定义TCP应用层传输协议?
  • 针对特定的用户群体,实现通信信息的真正加密,复杂场景下更灵活的通讯
  • 由于在TCP流传输的过程当中,可能会出现分包与黏包的现象。咱们为了解决这些问题,须要咱们自定义通讯协议进行封包与解包。
  • 什么是分包与黏包?
  • 分包:指接受方没有接受到一个完整的包,只接受了部分。
  • 黏包:指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
  • PS:由于TCP是面向字节流的,是没有边界的概念的,严格意义上来讲,是没有分包和黏包的概念的,可是为了更好理解,也更好来描述现象,我在这里就接着采用这两个名词来解释现象了。我以为你们知道这个概念就好了,没必要细扣,能解决问题就行。
  • 产生分包与黏包现象的缘由是什么?
  • 产生分包缘由:
  • 多是IP分片传输致使的,也多是传输过程当中丢失部分包致使出现的半包,还有可能就是一个包可能被分红了两次传输,在取数据的时候,先取到了一部分(还可能与接收的缓冲区大小有关系),总之就是一个数据包被分红了屡次接收。
  • 产生黏包的缘由:
  • 因为TCP协议自己的机制(面向链接的可靠地协议-三次握手机制)客户端与服务器会维持一个链接(Channel),数据在链接不断开的状况下,能够持续不断地将多个数据包发往服务器,可是若是发送的网络数据包过小,那么他自己会启用Nagle算法(可配置是否启用)对较小的数据包进行合并(基于此,TCP的网络延迟要UDP的高些)而后再发送(超时或者包大小足够)。那么这样的话,服务器在接收到消息(数据流)的时候就没法区分哪些数据包是客户端本身分开发送的,这样产生了粘包;服务器在接收到数据后,放到缓冲区中,若是消息没有被及时从缓存区取走,下次在取数据的时候可能就会出现一次取出多个数据包的状况,形成粘包现象
  • 什么是封包与解包?
  • TCP/IP 网络数据以流的方式传输,数据流是由包组成,如何断定接收方收到的包是不是一个完整的包就要在发送时对包进行处理,这就是封包技术,将包处理成包头,包体。

包头是包的开始标记,整个包的大小就是包的结束标。api

  • 如何自定义协议?
  • 发送时数据包是由包头+数据 组成的:其中包头内容分为包类型+包长度。
  • 接收时,只须要先保证将数据包的包头读完整,经过收到的数据包包头里的数据长度和数据包类型,判断出咱们将要收到一个带有什么样类型的多少长度的数据。而后循环接收直到接收的数据大小等于数据长度中止,此时咱们完成接收一个完整数据包。
用代码书写一个常见的解密后的包:
{
header:{
cmdid:oxa212,
msgid:xxxxxx,
sessionid:xxxx
....
},
body:{
sessiontype:1,
datalength:100,
formid:xxx,
told:xxxx,
msgid:xxxxxxx,
content:'dear'
}
}
  • 今天为了下降难度,没有使用prob格式传输哦。
  • 固然还有心跳的发包和回包,与上面相似,只是内容不一致。
今天只书写客户端 node.js的部分代码,服务端的代码,打算后期使用 golang书写。
  • 上面说到了,WebSocket通讯的最小单位是帧, 由一个或多个帧组成一条完整的消息, 交换数据的过程当中, 发送端和接收端须要作的事情以下:
  • 发送端: 将消息切割成多个帧, 并发送给服务端
  • 接收端: 接受消息帧, 并将关联的帧从新组装成完整的消息
  • 出现黏包和分包的问题,通俗易懂的说就是,建立buffer缓冲区,把二进制的数据一点一点点切出来,而后变成特定的js对象使用。

第一步 先与服务端创建tcp连接

const {Socket} = require('net') 
const tcp = new Socket()
tcp.setKeepAlive(true);
tcp.setNoDelay(true);
//保持底层tcp连接不断,长链接

第二步,指定对应域名端口号连接

tcp.connect(80,142.122.0.0)

第三步 创建成功连接后发送心跳包,而且服务端回复心跳包

  • 每一个人定制的心跳发包回包都不同,具体格式能够参考上面,自行定制心跳包的内容和检测时间,多长时间检测不到心跳的处理机制。

第四步,收到服务端数据,拆包,根据不一样的数据类型给予不一样的处理机制,决定哪些渲染到页面上,哪些放入数据库,作持久性存储等。

  • 这里写一点拆包代码
根据后端传送的数据类型 使用对应不一样的解析
readUInt8 readUInt16LE readUInt32LE readIntLE等处理后获得myBuf 

const myBuf = buffer.slice(start);//从对应的指针开始的位置截取buffer
const header = myBuf.slice(headstart,headend)//截取对应的头部buffer
const body = JSON.parse(myBuf.slice(headend-headstart,bodylength).tostring()) 
//精确截取body的buffer,而且转化成js对象
怎么拆包,长度是多少,要看你们各自的定义方式,参考 websocket的定义格式:

bVbuevC

Node.js目前支持的字符编码包括:

  • ascii - 仅支持 7 位 ASCII 数据。若是设置去掉高位的话,这种编码是很是快的。
  • utf8 - 多字节编码的 Unicode 字符。许多网页和其余文档格式都使用 UTF-8 。
  • utf16le - 2 或 4 个字节,小字节序编码的 Unicode 字符。支持代理对(U+10000 至 U+10FFFF)。
  • ucs2 - utf16le 的别名。
  • base64 - Base64 编码。
  • latin1 - 一种把 Buffer 编码成一字节编码的字符串的方式。
  • binary - latin1 的别名。
  • hex - 将每一个字节编码为两个十六进制字符。

组包

  • 建立 Buffer 类浏览器

    • Buffer 提供了如下 API 来建立 Buffer 类:
    • Buffer.alloc(size[, fill[, encoding]]): 返回一个指定大小的 Buffer 实例,若是没有设置 fill,则默认填满 0
    • Buffer.allocUnsafe(size): 返回一个指定大小的 Buffer 实例,可是它不会被初始化,因此它可能包含敏感的数据
    • Buffer.allocUnsafeSlow(size)
    • Buffer.from(array): 返回一个被 array 的值初始化的新的 Buffer 实例(传入的 array 的元素只能是数字,否则就会自动被 0 覆盖)
    • Buffer.from(arrayBuffer[, byteOffset[, length]]): 返回一个新建的与给定的 ArrayBuffer 共享同一内存的 Buffer。
    • Buffer.from(buffer): 复制传入的 Buffer 实例的数据,并返回一个新的 Buffer 实例
    • Buffer.from(string[, encoding]): 返回一个被 string 的值初始化的新的 Buffer 实例
所谓组包,就是把对应的 js对象,变成二进制数据,而后推送给服务端
  • 这里写一个简单的组包
const obj = {
        header:{
            datalength:123,
            sessiontype:1,
            cmdid:xxx,},
         body:{
            content:"hello",
            sessionid:xxx,
            fromid:xxx,
            toid:xxx
                }
            }
    将上面的js对象转化成buf后,推送给服务端 
    tcp.write(buf,cb)
    
    cb是一个异步回掉,当数据推送完后才会调用。

下面给出经常使用的buffer操做api

  • 方法参考手册
  • 如下列出了 Node.js Buffer 模块经常使用的方法(注意有些方法在旧版本是没有的):
  • 序号 方法 & 描述
  • 1 new Buffer(size)缓存

    • 分配一个新的 size 大小单位为8位字节的 buffer。 注意, size 必须小于 kMaxLength,不然,将会抛出异常 RangeError。废弃的: 使用 Buffer.alloc() 代替(或 Buffer.allocUnsafe())。
  • 2 new Buffer(buffer)

    • 拷贝参数 buffer 的数据到 Buffer 实例。废弃的: 使用 Buffer.from(buffer) 代替。
  • 3 new Buffer(str[, encoding])

分配一个新的 buffer ,其中包含着传入的 str 字符串。 encoding 编码方式默认为 'utf8'。 废弃的: 使用 Buffer.from(string[, encoding]) 代替。

  • 4 buf.length

    • 返回这个 buffer 的 bytes 数。注意这未必是 buffer 里面内容的大小。length 是 buffer 对象所分配的内存数,它不会随着这个 buffer 对象内容的改变而改变。
  • 5 buf.write(string[, offset[, length]][, encoding])

    • 根据参数 offset 偏移量和指定的 encoding 编码方式,将参数 string 数据写入buffer。 offset 偏移量默认值是 0, encoding 编码方式默认是 utf8。 length 长度是将要写入的字符串的 bytes 大小。 返回 number 类型,表示写入了多少 8 位字节流。若是 buffer 没有足够的空间来放整个 string,它将只会只写入部分字符串。 length 默认是 buffer.length - offset。 这个方法不会出现写入部分字符。
  • 6 buf.writeUIntLE(value, offset, byteLength[, noAssert])

将 value 写入到 buffer 里, 它由 offset 和 byteLength 决定,最高支持 48 位无符号整数,小端对齐,例如:

const buf = Buffer.allocUnsafe(6);

buf.writeUIntLE(0x1234567890ab, 0, 6);

// 输出: <Buffer ab 90 78 56 34 12>
console.log(buf);
noAssert 值为 true 时,再也不验证 value 和 offset 的有效性。 默认是 false。
  • 7 buf.writeUIntBE(value, offset, byteLength[, noAssert])

    • 将 value 写入到 buffer 里, 它由 offset 和 byteLength 决定,最高支持 48 位无符号整数,大端对齐。noAssert 值为 true 时,再也不验证 value 和 offset 的有效性。 默认是 false。
const buf = Buffer.allocUnsafe(6);

buf.writeUIntBE(0x1234567890ab, 0, 6);

// 输出: <Buffer 12 34 56 78 90 ab>
console.log(buf);
  • 8 buf.writeIntLE(value, offset, byteLength[, noAssert])

    • 将value 写入到 buffer 里, 它由offset 和 byteLength 决定,最高支持48位有符号整数,小端对齐。noAssert 值为 true 时,再也不验证 value 和 offset 的有效性。 默认是 false。
  • 9 buf.writeIntBE(value, offset, byteLength[, noAssert])

    • 将value 写入到 buffer 里, 它由offset 和 byteLength 决定,最高支持48位有符号整数,大端对齐。noAssert 值为 true 时,再也不验证 value 和 offset 的有效性。 默认是 false。
  • 10 buf.readUIntLE(offset, byteLength[, noAssert])

    • 支持读取 48 位如下的无符号数字,小端对齐。noAssert 值为 true 时, offset 再也不验证是否超过 buffer 的长度,默认为 false。
  • 11 buf.readUIntBE(offset, byteLength[, noAssert])

    • 支持读取 48 位如下的无符号数字,大端对齐。noAssert 值为 true 时, offset 再也不验证是否超过 buffer 的长度,默认为 false。
  • 12 buf.readIntLE(offset, byteLength[, noAssert])

    • 支持读取 48 位如下的有符号数字,小端对齐。noAssert 值为 true 时, offset 再也不验证是否超过 buffer 的长度,默认为 false。
  • 13 buf.readIntBE(offset, byteLength[, noAssert])

    • 支持读取 48 位如下的有符号数字,大端对齐。noAssert 值为 true 时, offset 再也不验证是否超过 buffer 的长度,默认为 false。
  • 14 buf.toString([encoding[, start[, end]]])

    • 根据 encoding 参数(默认是 'utf8')返回一个解码过的 string 类型。还会根据传入的参数 start (默认是 0) 和 end (默认是 buffer.length)做为取值范围。
  • 15 buf.toJSON()

    • 将 Buffer 实例转换为 JSON 对象。
  • 16 buf[index]

    • 获取或设置指定的字节。返回值表明一个字节,因此返回值的合法范围是十六进制0x00到0xFF 或者十进制0至 255。
  • 17 buf.equals(otherBuffer)

    • 比较两个缓冲区是否相等,若是是返回 true,不然返回 false。
  • 18 buf.compare(otherBuffer)

    • 比较两个 Buffer 对象,返回一个数字,表示 buf 在 otherBuffer 以前,以后或相同。
  • 19 buf.copy(targetBuffer[, targetStart[, sourceStart[, sourceEnd]]])

    • buffer 拷贝,源和目标能够相同。 targetStart 目标开始偏移和 sourceStart 源开始偏移默认都是 0。 sourceEnd 源结束位置偏移默认是源的长度 buffer.length 。
  • 20 buf.slice([start[, end]])

    • 剪切 Buffer 对象,根据 start(默认是 0 ) 和 end (默认是 buffer.length ) 偏移和裁剪了索引。 负的索引是从 buffer 尾部开始计算的。
  • 21 buf.readUInt8(offset[, noAssert])

    • 根据指定的偏移量,读取一个无符号 8 位整数。若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 若是这样 offset 可能会超出buffer 的末尾。默认是 false。
  • 22 buf.readUInt16LE(offset[, noAssert])

    • 根据指定的偏移量,使用特殊的 endian 字节序格式读取一个无符号 16 位整数。若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出 buffer 的末尾。默认是 false。
  • 23 buf.readUInt16BE(offset[, noAssert])

    • 根据指定的偏移量,使用特殊的 endian 字节序格式读取一个无符号 16 位整数,大端对齐。若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出 buffer 的末尾。默认是 false。
  • 24 buf.readUInt32LE(offset[, noAssert])

    • 根据指定的偏移量,使用指定的 endian 字节序格式读取一个无符号 32 位整数,小端对齐。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出buffer 的末尾。默认是 false。
  • 25 buf.readUInt32BE(offset[, noAssert])

    • 根据指定的偏移量,使用指定的 endian 字节序格式读取一个无符号 32 位整数,大端对齐。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出buffer 的末尾。默认是 false。
  • 26 buf.readInt8(offset[, noAssert])

    • 根据指定的偏移量,读取一个有符号 8 位整数。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出 buffer 的末尾。默认是 false。
  • 27 buf.readInt16LE(offset[, noAssert])

    • 根据指定的偏移量,使用特殊的 endian 格式读取一个 有符号 16 位整数,小端对齐。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出 buffer 的末尾。默认是 false。
  • 28 buf.readInt16BE(offset[, noAssert])

根据指定的偏移量,使用特殊的 endian 格式读取一个 有符号 16 位整数,大端对齐。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出 buffer 的末尾。默认是 false。

  • 29 buf.readInt32LE(offset[, noAssert])

    • 根据指定的偏移量,使用指定的 endian 字节序格式读取一个有符号 32 位整数,小端对齐。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出buffer 的末尾。默认是 false。
  • 30 buf.readInt32BE(offset[, noAssert])

    • 根据指定的偏移量,使用指定的 endian 字节序格式读取一个有符号 32 位整数,大端对齐。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出buffer 的末尾。默认是 false。
  • 31 buf.readFloatLE(offset[, noAssert])

    • 根据指定的偏移量,使用指定的 endian 字节序格式读取一个 32 位双浮点数,小端对齐。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出buffer的末尾。默认是 false。
  • 32 buf.readFloatBE(offset[, noAssert])

    • 根据指定的偏移量,使用指定的 endian 字节序格式读取一个 32 位双浮点数,大端对齐。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出buffer的末尾。默认是 false。
  • 33 buf.readDoubleLE(offset[, noAssert])

    • 根据指定的偏移量,使用指定的 endian字节序格式读取一个 64 位双精度数,小端对齐。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出buffer 的末尾。默认是 false。
  • 34 buf.readDoubleBE(offset[, noAssert])

    • 根据指定的偏移量,使用指定的 endian字节序格式读取一个 64 位双精度数,大端对齐。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 offset 可能会超出buffer 的末尾。默认是 false。
  • 35 buf.writeUInt8(value, offset[, noAssert])

    • 根据传入的 offset 偏移量将 value 写入 buffer。注意:value 必须是一个合法的无符号 8 位整数。 若参数 noAssert 为 true 将不会验证 offset 偏移量参数。 这意味着 value 可能过大,或者 offset 可能会超出 buffer 的末尾从而形成 value 被丢弃。 除非你对这个参数很是有把握,不然不要使用。默认是 false。
  • 36 buf.writeUInt16LE(value, offset[, noAssert])

    • 根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer。注意:value 必须是一个合法的无符号 16 位整数,小端对齐。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value 可能过大,或者 offset 可能会超出buffer的末尾从而形成 value 被丢弃。 除非你对这个参数很是有把握,不然尽可能不要使用。默认是 false。
  • 37 buf.writeUInt16BE(value, offset[, noAssert])

    • 根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer。注意:value 必须是一个合法的无符号 16 位整数,大端对齐。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value 可能过大,或者 offset 可能会超出buffer的末尾从而形成 value 被丢弃。 除非你对这个参数很是有把握,不然尽可能不要使用。默认是 false。
  • 38 buf.writeUInt32LE(value, offset[, noAssert])

    • 根据传入的 offset 偏移量和指定的 endian 格式(LITTLE-ENDIAN:小字节序)将 value 写入buffer。注意:value 必须是一个合法的无符号 32 位整数,小端对齐。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着value 可能过大,或者offset可能会超出buffer的末尾从而形成 value 被丢弃。 除非你对这个参数很是有把握,不然尽可能不要使用。默认是 false。
  • 39 buf.writeUInt32BE(value, offset[, noAssert])

    • 根据传入的 offset 偏移量和指定的 endian 格式(Big-Endian:大字节序)将 value 写入buffer。注意:value 必须是一个合法的有符号 32 位整数。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value 可能过大,或者offset可能会超出buffer的末尾从而形成 value 被丢弃。 除非你对这个参数很是有把握,不然尽可能不要使用。默认是 false。
  • 40 buf.writeInt8(value, offset[, noAssert])
  • 41 buf.writeInt16LE(value, offset[, noAssert])

    • 根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer。注意:value 必须是一个合法的 signed 16 位整数。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value 可能过大,或者 offset 可能会超出 buffer 的末尾从而形成 value 被丢弃。 除非你对这个参数很是有把握,不然尽可能不要使用。默认是 false 。
  • 42 buf.writeInt16BE(value, offset[, noAssert])

    • 根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer。注意:value 必须是一个合法的 signed 16 位整数。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value 可能过大,或者 offset 可能会超出 buffer 的末尾从而形成 value 被丢弃。 除非你对这个参数很是有把握,不然尽可能不要使用。默认是 false 。
  • 43 buf.writeInt32LE(value, offset[, noAssert])

    • 根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer。注意:value 必须是一个合法的 signed 32 位整数。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value 可能过大,或者 offset 可能会超出 buffer 的末尾从而形成 value 被丢弃。 除非你对这个参数很是有把握,不然尽可能不要使用。默认是 false。
  • 44 buf.writeInt32BE(value, offset[, noAssert])

    • 根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer。注意:value 必须是一个合法的 signed 32 位整数。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value 可能过大,或者 offset 可能会超出 buffer 的末尾从而形成 value 被丢弃。 除非你对这个参数很是有把握,不然尽可能不要使用。默认是 false。
  • 45 buf.writeFloatLE(value, offset[, noAssert])

    • 根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer 。注意:当 value 不是一个 32 位浮点数类型的值时,结果将是不肯定的。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value可能过大,或者 offset 可能会超出 buffer 的末尾从而形成 value 被丢弃。 除非你对这个参数很是有把握,不然尽可能不要使用。默认是 false。
  • 46 buf.writeFloatBE(value, offset[, noAssert])

    • 根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer 。注意:当 value 不是一个 32 位浮点数类型的值时,结果将是不肯定的。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value可能过大,或者 offset 可能会超出 buffer 的末尾从而形成 value 被丢弃。 除非你对这个参数很是有把握,不然尽可能不要使用。默认是 false。
  • 47 buf.writeDoubleLE(value, offset[, noAssert])

    • 根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer。注意:value 必须是一个有效的 64 位double 类型的值。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value 可能过大,或者 offset 可能会超出 buffer 的末尾从而形成value被丢弃。 除非你对这个参数很是有把握,不然尽可能不要使用。默认是 false。
  • 48 buf.writeDoubleBE(value, offset[, noAssert])

    • 根据传入的 offset 偏移量和指定的 endian 格式将 value 写入 buffer。注意:value 必须是一个有效的 64 位double 类型的值。 若参数 noAssert 为 true 将不会验证 value 和 offset 偏移量参数。 这意味着 value 可能过大,或者 offset 可能会超出 buffer 的末尾从而形成value被丢弃。 除非你对这个参数很是有把握,不然尽可能不要使用。默认是 false。
  • 49 buf.fill(value, offset)

    • 使用指定的 value 来填充这个 buffer。若是没有指定 offset (默认是 0) 而且 end (默认是 buffer.length) ,将会填充整个buffer。
最后的总结:
  • 实时通信,特别是三端加密和消息同步这块,是很是复杂的,本人大概只写到了10分之1
  • 组包和拆包,具体要根据你特定的业务场景还有公司定制的协议去具体操做,这里只是一个大概阐述
  • 后台的代码之后我会尽力用golangnode.js各写一份。
  • 海量高并发场景,机房部署,总体架构这里都没有写,由于确实太多了,一旦并发量上来了,不管先后端要作的事情都很是多
  • 有幸公司昨天在深圳万象城请到了Bilibili的架构师毛剑先生给咱们培训,他让我对后端的认识又深入了很多,特别是IM总体架构和优化这块,之后总结好了,也会给你们分享
  • 若是有写得不对的地方,请指出,谢谢。
  • 加解密的过程没有写上来,这块也是很是核心。
相关文章
相关标签/搜索