Websocket
WebSocket 网络模块
介绍#
mk.network.Websocket
(以及微信运行时下的 mk.network.WebsocketWX
)是基于 mk.network.Base
的 WebSocket 客户端封装,面向用户提供一套以事件驱动、支持二进制/自定义协议、可等待回复的消息通信层。
特点#
- 可插拔编解码器(基于
MKCodecBase
),支持任意数据格式传输。 - 消息事件化:通过
message
对象以消息类型为 key 进行on
/once
/off
/send
/request
等操作。 - 请求-响应等待:
message.request(data)
返回 Promise,自动根据“消息序列号”匹配响应并 resolve。 - 心跳(心跳超时检测 + 可选客户端定时发心跳)与断线重连(间隔 & 次数控制)。
- 支持发送潮(SendTide)——把多条消息按节奏发送以避免“消息洪峰”。
- 兼容微信平台(
Websocket
/WebsocketWX
)。
用户层接口总览#
mk.network.Base
:抽象基类的用户可见封装(实际使用时通过mk.network.Websocket
/mk.network.WebsocketWX
)。mk.network.Base_
:命名空间,包含初始化配置类型InitConfig
、枚举等(用于构造或阅读默认值)。mk.network.Websocket
:浏览器 / 编辑器 环境下的 WebSocket 实现。mk.network.Websocket_
:Websocket 特有的配置类(如binaryType
,protocolStrList
)。mk.network.WebsocketWX
:微信小程序环境下的 SocketTask 实现(使用wx.connectSocket
)。
所有网络实例都暴露
event
与message
两个主要对象用于事件与消息交互;并可通过config
与codec
做进一步定制。
概念#
消息号#
消息号代表消息体类型
例如
// 消息号 0
interface MessageA {
data: number;
}
// 消息号 1
interface MessageB {
data2: number;
}
interface MessageData {
// 消息号
id: number;
// 消息体:json 格式
data: string;
}
MessageData
为客户端和服务器通信的消息结构,那么怎么知道 MessageData.data
是 MessageA
还是 MessageB
?
这就可以通过消息号MessageData.id
判断,每个消息体都有属于自己的固定消息号
消息序列号#
消息发送批次编号,用于识别服务器返回的数据对应客户端之前发送的哪个消息
例如
- 客户端 -> 消息 A(消息号 0, 序列号 0) -> 服务器
- 客户端 -> 消息 A(消息号 0, 序列号 1) -> 服务器
- 服务器 -> 消息 A 结果(消息号 0, 序列号 0) -> 客户端
客户端发送了两次消息 A,如果服务器返回数据只有消息号,没有消息序列号,我们可以知道这是消息 A 的结果数据, 但是不知道这是第一个还是第二个消息 A 的结果数据
消息序列号就代表了这是第一个还是第二个消息 A 的结果数据
创建#
创建网络对象进行通信
const ws = new mk.network.Websocket({
// codec:消息体编解码器
// sendIntervalMsNum:发送间隔(毫秒),防止一帧内发送多次
// reconnectIntervalMsNum:重连间隔(毫秒),默认 1000
// maxReconnectNum:最大重连次数,默认 5
// waitTimeoutMsNum:等待消息超时时间(毫秒),默认 5000
// heartbeatConfig:心跳配置
// parseMessageIdFunc(data: any):消息号解析函数,参数为消息体
// parseMessageSequenceFunc(data: any):消息序列号解析函数,参数为消息体
});
// 微信下可使用 new mk.network.WebsocketWX
属性#
event#
网络全局事件对象,提供了如下事件类型
事件名 | 描述 |
---|---|
open | 网络连接 |
close | 网络断开 |
reconnectSuccess | 重连成功 |
reconnectFail | 重连失败 |
heartbeatTimeout | 心跳超时 |
recv | 收到任意消息 |
message#
网络消息事件对象,可以用它进行消息发送,监听,请求
使用方式基本和事件一致,以下只说明差异部分
方法#
-
on(type_, callback_, target_?, isOnce_)
-
type_
:监听消息,类型(Function | string | number
)类型为函数则使用
parseMessageIdFunc(type_.prototype)
得到的消息号进行监听类型非函数类型则使用
type_
进行监听 -
callback_
:消息回调,参数为接收到的服务端消息
-
-
emit(data_) 或 emit(type_, data_)
模拟服务器消息推送,例如开发调试,多人游戏中的人机或单机模式
type_
:消息号,可选,类型(string | number
)data_
:消息体,框架会使用parseMessageIdFunc(data*)
获取的消息号进行emit
-
send(data_)
发送消息到服务器
data_
:消息体
-
request(data_, timeoutMsNum_)
发送消息到服务器并等待返回
data_
:消息体timeoutMsNum_
:超时时间,-1:无超时时间;0-n:等待时间(毫秒);不填则为构造配置中的 waitTimeoutMsNum
once、off、has
的type_
和callback_
的类型与on
一致
最简单示例#
创建一个通用网络对象,并且收发字符串
const ws = new mk.network.Websocket();
// 连接服务器
await ws.connect('ws://127.0.0.1:8848');
// 发送字符串消息
ws.message.send('123');
// 监听所有返回的消息
ws.event.on(
ws.event.key.recv,
(data: string) => {
console.log('收到消息', data);
},
this
);
带消息号示例#
现在我们创建一个通过 JSON 数据通信的示例,并且在 JSON 中附带消息号和消息序列号
// 创建编解码器,收到的数据会解码为 Object,发送的数据编码为 JSON
const codec = new (class extends mk.CodecBase {
decode(data: string): Object {
return JSON.parse(data);
}
encode(data: Object): string {
return JSON.stringify(data);
}
})();
// 创建网络对象
const ws = new mk.network.Websocket({
codec: codec,
// 从消息体解析得到消息号
parseMessageIdFunc: (data: any) => {
return data.id;
},
// 从消息体解析得到消息序列号
parseMessageSequenceFunc: (data: any) => {
return data.sequence;
},
});
/** 消息序列号 */
let messageSequenceNum = 0;
/** 创建消息 A */
const createMessageA = (data: number): any => {
return {
// 消息号 0 代表消息 A,创建其他消息则递增
id: 0,
// 消息序列号自增代表发送批次
sequence: messageSequenceNum++,
// 消息数据
data: data,
};
};
const messageA = createMessageA(123);
const messageA2 = createMessageA(456);
// 监听消息号为 0 的消息接收数据
ws.message.on(0, (data) => {
console.log('监听返回', data);
});
// 发送 messageA2 消息等待服务器返回
ws.message.request(messageA2, -1)?.then((data) => {
console.log('请求返回', data);
});
// 模拟服务器发送 messageA 数据,触发监听返回(消息号一致)
ws.message.emit(messageA);
// 模拟服务器发送 messageA2 数据,触发监听返回(消息号一致)和请求返回(消息序列号一致)
ws.message.emit(messageA2);
日志结果
监听返回 {id: 0, sequence: 0, data: 123}
监听返回 {id: 0, sequence: 1, data: 456}
请求返回 {id: 0, sequence: 1, data: 456}
其他消息结构#
在游戏中最常用的实际是 protobuf,但是我们的编解码机制可以支持任意格式,前提是可以从消息体解析得到消息号,和消息序列号
示例:
enum MessageID {
/** 测试消息 */
Test = 0;
Test2 = 1;
}
message Package {
/** 消息号 */
int32 id = 1;
/** 消息序列号 */
int32 sequence = 2;
/** 消息体 */
bytes data = 3;
}
// 后缀说明,C: 客户端发送消息,S:服务器返回消息,B:服务器推送消息
message TestC {
int32 data = 1;
}
message TestS {
int32 data = 1;
}
message Test2B {
string data = 1;
}
我们可以从消息体的命名得到 MessageID
中对应的消息号,而消息序列号在编解码中递增就行了
实现参考:框架网络功能示例 代码中的 网络 1 部分
重连#
配置#
流程(自动)#
-
网络断开时,event 会派发
close
事件 -
框架通过网络对象配置的 重连间隔(reconnectIntervalMsNum) 和 最大重连次数(maxReconnectNum) 进行重连
-
如果重连成功,event 派发
reconnectSuccess
事件,重连失败则派发reconnectFail
事件
心跳#
配置#
-
heartbeatConfig.intervalMsN
: 发送间隔(毫秒),客户端向服务器发送的心跳间隔 -
heartbeatConfig.timeoutMsN
: 超时时间(毫秒),客户端多久未收到服务器心跳为超时 -
heartbeatConfig.initFunc(doneFunc)
: 心跳初始化函数,- 参数
doneFunc
: 用户在initFunc
函数内监听服务器心跳消息,收到心跳时调用参数doneFunc()
- 返回值
null
: 代表客户端不向服务器发送心跳数据() => any
: 客户端会在每次心跳时执行这个函数,将返回值(心跳数据)发送给服务器
- 参数
流程#
-
网络对象 open 状态时,框架开始以间隔时间(heartbeatConfig.intervalMsN) 发送 心跳数据(heartbeatConfig.initFunc 返回函数的返回值)
-
网络对象 close 状态时则时停止心跳
消息潮#
如果你想要存储数据以批量发送,例如指定间隔,或手动触发发送,则可以使用消息潮
创建#
let sendTide = new mk.network.Base_.SendTide(
// 网络对象
ws,
// 发送间隔,-1:手动触发,>0:自动发送间隔毫秒
1000
);
方法#
-
send
: 发送消息接口 -
trigger
: 触发发送,前提是发送间隔为 -1 -
clear
: 清理所有未发送消息