技术文章 > 服务端 > pomelo 用户指南-通信协议格式 

协议格式

pomelo 核心提供了两种 connector,sioconnector 和 hybridconnector。其中 sioconnector 基于 socket.io,使用 json 作为其通信格式,hybridconnector 则用于 tcp/websocket 的通信,它底层使用的是二进制协议。虽然在 sioconnector 中,socket.io 的实现很好,对于超时、握手等都做了处理,并且使用 json 作为通信格式,方便了协议的定制和修改,但同时也带来了较多的通讯冗余数据。hybridconnector 则是使用了二进制版本通讯协议,同时提供了 route 字典压缩和 protobuf 压缩,提高带宽利用率,以满足诸如移动环境的需求,同时上层接口仍保持 json 格式的接口,对以前版本之前的代码不产生任何影响,保留兼容性。在本部分,主要介绍 hybridconnector 实现的具体的通信协议。
pomelo 的二进制协议包含两层编码:package 和 message。message 层主要实现 route 压缩和 protobuf 压缩,message 层的编码结果将传递给 package 层。package 层主要实现 pomelo 应用基于二进制协议的握手过程,心跳和数据传输编码,package 层的编码结果可以通过 tcp,websocket 等协议以二进制数据的形式进行传输。message 层编码可选,也可替换成其他二进制编码格式,都不影响 package 层编码和发送。
Pomelo协议层的结构如下图所示:


pomelo package


package 协议主要用来封装在面向连接的二进制流的通讯协议(如:tcp)上的 pomelo 数据包。package 分为控制包和数据包两种类型。前者用来实现 pomelo 应用层面的控制流程,包括客户端和服务器的握手,心跳和服务器主动断开连接的通知等控制信息。后者则是用来在客户端和服务器之间传输应用数据。

package格式

package 分为 header 和 body 两部分。header 描述 package 包的类型和包的长度,body 则是需要传输的数据内容。具体格式如下:


各个package类型的具体描述和控制流程如下。

握手

握手流程主要提供一个机会,让客户端和服务器在连接建立后,进行一些初始化的数据交换。交换的数据分为系统和用户两部分。系统部分为 pomelo 框架所需信息,用户部分则是用户可以在具体应用中自定义的内容。
握手的内容为 utf-8 编码的 json 字符串(不压缩),通过 body 字段传输。
握手请求:
{
  "sys": {
    "version": "1.1.1",
    "type": "js-websocket"
  }, 
  "user": {
  	// any customized request data
  }
}
握手响应:
{
  "code": 200, 			// response code
  "sys": {
    "heartbeat": 3, 	// heartbeat interval in second
    "dict": {}, 		// route dictionary
    "protos": {}		// protobuf definition data
  }, 
  "user": {
  	// any customized response data
  }
}
握手的流程如下:


当底层连接建立后,客户端向服务器发起握手请求,并附带必要的数据。服务器检验握手数据后,返回握手响应。如果握手成功,客户端向服务器发送一个握手 ack,握手阶段至此成功结束。

心跳

心跳包的 length 字段为 0,body 为空。
心跳的流程如下:


服务器可以配置心跳时间间隔。当握手结束后,客户端发起第一个心跳。服务器和客户端收到心跳包后,延迟心跳间隔的时间后再向对方发送一个心跳包。
心跳超时时间为 2 倍的心跳间隔时间。服务器检测到心跳超时并不会主动断开客户端的连接。客户端检测到心跳超时,可以根据策略选择是否要主动断开连接。

数据

数据包用来在客户端和服务器之间传输数据所用。数据包的 body 是由上层传下来的任意二进制数据,package 层不会对 body 内容做任何处理。

服务器主动断开

当服务器主动断开客户端连接时(如:踢掉某个在线玩家),会先向客户端发送一个控制消息,然后再断开连接。客户端可以通过这个消息来判断是否是服务器主动断开连接的。

pomelo message


message 协议的主要作用是封装消息头,包括route和消息类型两部分,不同的消息类型有着不同的消息头,在消息头里面可能要打入 message id (即requestId) 和 route 信息。由于可能会有 route 压缩,而且对于服务端 push 的消息,message id 为空,对于客户端请求的响应,route 为空,因此 message 的头格式比较复杂。
消息头分为三部分:flag、message id、route。如下图所示:


从上图可以看出,pomelo 消息头是可变的,会根据具体的消息类型和内容而改变。其中:

标志位 flag

flag 占用 message 头的第一个 byte,其内容如下:


现在只用到了其中的 4 个 bit,这四个 bit 包括两部分,占用 3 个 bit 的 message type 字段和占用 1 个 bit 的 route 标识,其中:

消息类型

不同类型的消息,对应不同消息头,消息类型通过 flag 字段的第 2-4 位来确定,其对应关系以及相应的消息头如下图:


上面的 - 表示不影响消息类型的 bit 位。

route 压缩标志位

route 主要分为压缩和未压缩两种,由 flag 的最后一位(route 压缩标志位)指定,当 flag 中的 route 标志为 0 时,表示未压缩的 route,为 1 则表示是压缩 route。route 通过系统生成和用户自定义的字典进行压缩,具体内容见 pomelo 压缩协议。route 字段的编码会依赖 flag 的这一位,其格式如下图:


上图是不同的 flag 标志对应的 route 字段的内容:

总结


在本部分,介绍了 pomelo 提供的 hybridconnector 的线上协议,包括 package 层和 message 层。当用户使用 hybridconnector 的时候,可以根据这里提供的协议信息,在客户端可以依据此协议完成与服务端的通信。


来源:摘自 https://github.com/NetEase/pomelo/wiki/%E5%8D%8F%E8%AE%AE%E6%A0%BC%E5%BC%8F,本站 行痴 整理