造轮子系列(二): 史上最简朴的长衔接通信协议及完成

背景

如今写客户端或许网页的时刻, 越来越多的须要与长衔接打交道, 尤其是在这个老板动不动就要搞一个谈天体系的时期, 后端大哥们因而分分钟就可以造一个基于TCP或许WebSockets的音讯协定出来. 然则问题在于每做一个新项目, 后端大哥们就可以造出一个新协定, 而且能有种种奇异的限定. 比方说要在长衔接当中坚持一个状况机, 发送某条音讯后收到的下一条音讯肯定是XXX, 或许完全一个JSON就直接丢了出来等等. 虽然都能用, 然则却须要在种种处所维护着差别的底层通讯库, 没有章法可依, 所以草拟了这个协定.

现在最热点的音讯协定莫过于MQTT和gRPC了, 前者被定义为A lightweight messaging protocol for small sensors and mobile devices, optimized for high-latency or unreliable networks, 即一个为传感器和挪动装备定制的音讯协定. 最大的特性莫过于其牢固音讯头只要2字节, 以及QoS服务质量掌握了. 关于前者, 无可厚非, 任何一个长衔接的音讯协定都应当可以做到云云, 以至更简朴(STMP就是云云), 其次其QoS设想使得通讯层面就变得很复杂, 使得其更像一个音讯行列协定, 而不是简朴的通讯协定. 而gRPC则是一个基于ProtocolBuffers发展起来的RPC协定以完成. 集成度很高, 底层基于HTTP 2, 所以通用性很好, 假如是做大项目而且团队有肯定的手艺/运维积聚的话, 是异常引荐的挑选, 然则这和STMP不争执, STMP面向的是对协定硬朗性要求不高, 只须要一个能用的范例的企业/团队中, 你可以用在Web端, 也可以用在客户端, 或许智能家居等嵌入式装备中, 反观gRPC, 则显得过于复杂.

简介

协定取名STMP, 意义是最简朴的音讯协定(The simplest message protocol). 项目托管在GitHub上, 包含了完全的协定文档以及相干完成, 细致相识请移步GitHub, 同时迎接提交PR/Issue, 地点是https://github.com/acrazing/stmp.

简朴来讲, STMP有以下特性:

  • 异常精简的牢固头部, 唯一一字节(二进制序列化)
  • 支撑二进制序列化(TCP)以及文本序列化(WebSockets), 文本序列化支撑音讯分包传送(通报二进制数据)
  • 与IP协定掩码相似的上层路由掌握
  • 负载编码花样对协定通明
  • 心跳检测
  • 四种音讯范例: 心跳, 要求, 关照, 复兴
  • 与HTTP协定相似的返回状况码掌握

音讯字段定义

一个全双工的通讯体系中, 双端须要有用辨认对方发来的音讯, 并作出响应的处置惩罚, 挑选是不是回应等操纵, 所以除了现实的负载以外, 还须要多少标志字段. STMP中, 完全的音讯字段列表以下, 须要注重的是并非每条音讯都邑包含一切的这些字段, 须要依据收集环境以及音讯范例肯定应当包含的字段列表. 然则假如某条音讯包含了以下这些字段中的某一些字段的话,排序递次肯定与字段在下面涌现的递次雷同.

  • 音讯范例(KIND): 示意一条音讯的范例, 可以的取值有:

    • 0: 心跳音讯(Ping Message)
    • 1: 要求音讯(Request Message)
    • 2: 关照音讯(Notify Message)
    • 3: 复兴音讯(Response Message)
  • 音讯编码花样(ENCODING): 示意负载的编码花样, 上层运用/编解码层收到音讯后, 可以经由过程此字段对负载举行解码操纵, 由于头部长度限定, 可以的取值局限为0-7, 已商定的编码花样以下:

    • 0: 保存花样, 示意不包含负载, 此时音讯中肯定不存在PS以及PAYLOAD字段
    • 1: Protocol Buffers, 参考 Protocol Buffers
    • 2: JSON, 参考 JSON
    • 3: MessagePack, 参考 MessagePack
    • 4: BSON, 参考 BSON
    • 5: 原始二进制数据
  • 音讯ID(ID): 音讯的暂时ID, 取值局限为0x0000-0xFFFF, 用于要求与复兴音讯当中, 要求方应当保证在超时的时限内此ID唯一, 复兴方在复兴时带上此ID以供发送方辨认
  • 音讯要求行动(ACTION): 要求的行动, 用于上层运用举行路由掌握, 取值局限为0x00000000-0xFFFFFFFF, 即32位整型, 上层运用中可以写成xxx.xxx.xxx.xxx的情势, 与IP相似. 接收方在收到响应的行动后必须可以准确辨认, 并转交给响应的处置惩罚器举行处置惩罚. 个中0x00-0xFF为保存行动, 用于协定内部运用. 现在已运用的行动有:

    • 0x00: 版本协商(Check Versions)
  • 状况码(STATUS): 处置惩罚效果状况码, 用在复兴音讯中, 表明对要求的处置惩罚效果, 取值局限为0x00-0xFF, 个中0x00-0x7F为保存取值, 寄义与ACTION无关, 0x80-0xFF为用户定义的状况值, 寄义依据ACTION差别有可以差别. 现在已定义的状况码有(和HTTP相似, 只不过换了个值罢了):

    • 0x00: Ok, 200
    • 0x10: MovedPermanently, 301
    • 0x11: Found, 302
    • 0x12: NotModified, 304
    • 0x20: BadRequest, 400
    • 0x21: Unauthorized, 401
    • 0x22: PaymentRequired, 402
    • 0x23: Forbidden, 403
    • 0x24: NotFound, 404
    • 0x25: RequestTimeout, 408
    • 0x26: RequestEntityTooLarge, 413
    • 0x27: TooManyRequests, 429
    • 0x30: InternalServerError, 500
    • 0x31: NotImplemented, 501
    • 0x32: BadGateway, 502
    • 0x33: ServiceUnavailable, 503
    • 0x34: GatewayTimeout, 504
    • 0x35: VersionNotSupported, 505
  • 负载长度(PS): 示意PAYLOAD的长度, 以字节为单元, 取值局限为0x00000000-0xFFFFFFFF, 即负载最大长度为4Gb, 此字段存在与否由收集环境与ENCODING决议, 假如ENCODING0, 或许收集环境可以准确的分包(比方WebSockets环境), 则肯定不存在此字段, 不然肯定存在此字段.
  • 负载(PAYLOAD): 现实的负载, 长度由PS或许收集分包效果肯定, 编码体式格局由ENCODING决议, 协定本身不担任负载的编解码, 须要交由上层的运用举行诠释.

音讯范例

如前所述, STMP中音讯分类四种范例, 差别的音讯范例可以包含的字段及寄义有所差别, 细致以下:

心跳音讯

双端为了保证对方衔接有用性, 必须按期发送一个心跳音讯给对方, 此音讯肯定不包含任何除了KIND外的别的任何字段. 同时此音讯不须要 复兴, 假如一方在商定的时间内没有收到对方发送的心跳音讯, 则表明对方已断开衔接或许涌现异常, 应当马上断开衔接.

要求音讯

此音讯示意发送方要求接收方返回某一个资本, 假如在指定的时间内未收到接收方的复兴, 则摒弃守候, 并向上层运用返回一个STATUS0x25的复兴, 示意要求超时.
此音讯肯定包含KIND, ENCODING, ID, ACTION字段, 可以包含PS, PAYLOAD字段, 肯定不包含STATUS字段.

关照音讯

此音讯示意发送方向接收方发送一个关照, 接收方无需复兴此音讯.

此音讯肯定包含KIND, ENCODING, ACTION字段, 可以包含PS, PAYLOAD字段, 肯定不包含ID, STATUS字段.

复兴音讯

此音讯示意发送方向接收方发送一个复兴音讯以复兴对方曾发送的某一条要求音讯, 此音讯的ID为接收方发送的此条要求音讯ID. 假如上层运用在指定的时间内未返回音讯, 则向发送方发送一个STATUS0x34的复兴音讯, 表明上层运用处置惩罚超时.

此音讯肯定包含KIND, ENCODING, ID, STATUS字段, 可以包含PS, PAYLOAD字段, 肯定不包含ACTION字段.

音讯序列化

针对差别的收集环境, 协定制订了两套差别的序列化体式格局以应对, 重要原因是浏览器环境中将字符串转换成ArrayBuffer再经由过程WebSockets发送机能着实没法直视(完成体式格局可以参考stmp/impl/js/stmp/text.ts, 重如果将UTF-16编码和字符串转换成UTF-8的Uint8Array), 同时为了更好的Web端调试, 所以制订了一套文本序列化计划.

二进制序列化

二进制序列化中, 牢固头部占一个字节, 包含KIND以及ENCODING字段, 假如KIND0, 则ENOCDING也必须为0, 示意一个心跳音讯. 完全的构造以下:

|   0 ... 7   |  8 ... 15  |  16 ... 23  |  24 ... 31  |
| FixedHeader |           ID             |    ACTION   |
|               ACTION                   |    STATUS   |
|                         PS                           |
|                 PAYLOAD    ...                       |

个中的多字节字段, 包含ID, ACTION, PS字段, 假如存在的话, 肯定BigEndian的体式格局通报. 另外, 牢固头部以下:

|   0   |   1   |   2   |   3   |   4   |   5   |   6   |   7   |
|     KIND      |       ENCODING        |   0   |   0   |   0   |

末了三个位为保存位(未用到), 悉数置零.

文本就序列化

一切的字段经由过程字符|衔接, 即:

KIND(1)|ENCODING(1)|ID?(1-5)|ACTION?(1-10)|STATUS?(1-3)|PS?(1-10)|PAYLOAD?(...)

音讯支解, 在运用文本序列化体式格局通报二进制数据时, 浏览器环境不能高效的将两者混淆在一起, 所以许可分红两个包举行传送, 前者通报头部信息, 后者通报现实的二进制PAYLOAD, 此时ENCODING肯定不0, 同时, PAYLOAD在头部包中不存在. WebSockets本身保证了包的有序性.

关于一个心跳音讯, 只要一个KIND字段, 所以其效果肯定为"0".

辨别文本音讯与二进制音讯

这是比较风趣的处所, 文本音讯和二进制音讯可以经由过程首字节完全区分开来: 关于文本音讯, 首字节为'0', '1', '2', '3'中的一个, 即0x30-0x33, 而关于二进制音讯, 要么为0x00(心跳音讯), 要么大于或许即是0x40, 由于KIND不为0时其值肯定大于0b01000000.

版本协商

协定版本有两个字段, 分别为MAJORMINOR, 两者取值局限均为015, 即0x00xF, 可以序列化为MAJOR.MINOR的情势.

当前协定版本为0.1.

客户端在提议衔接胜利后, 须要发送一个ACTION为0x00的音讯给服务端, 音讯ID必须为0, 负载编码体式格局为Raw, 负载为客户端可接受的版本号
列表. 服务端在收到此音讯后, 假如可以处置惩罚客户端发送过来的版本列表中的某一个, 则复兴一个STATUS为Ok的复兴音讯, 负载为所挑选的协定版本
号, 假如不能处置惩罚, 则返回一个VersionNotSupported毛病音讯, 负载为空, 而且封闭衔接.

版本号序列化

在二进制音讯中, 一个版本号序列化为1字节长度的信息, 个中前4位为MAJOR, 后4位为MINOR值. 多个版本号直接衔接在一起. 在文本音讯中, 一个版本号序列化为2字节长度的信息, 个中前1字节为MAJOR, 后1字节为MINOR值, 多个版本号直接相连.

完成

现在仅完成了Golang和JS的简朴的音讯编解码部份, 地点在: go版本, js版本, 另有许多事情要做T_T, 假如有人提PR就好了?????.

    原文作者:acrazing
    原文地址: https://segmentfault.com/a/1190000010402938
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞