笔者是游戏测试,近期比较频繁在搞这部分的,下个阶段应该也会做这块的,文章打算坚持写下去,主要语言是 node.js js>=6.x
udp 可以说都是自定义的,场景适用很广,用于 DNS,P2P,通信短信业务,端游时期的帧同步,手游王者荣耀的 udp 就是腾讯改造队列 udp。
第三方比较成熟的模块使用也是属于比较顶层的,处理顶层事件的监听和处理。因为都是自定义的,所以对 udp 的协议进行压力测试时需要按照规则去重新实现一套。
udp 如何可靠和不可靠,这里就先不讲了,广义是传输接收本身,狭义是从数据报文等往上一层去看。
下面先看看 1 段 udp 案例,是怎么样的 1 组数据报文。
UDP reliableData
|校验方式 32bit|协议类型 4bit|flag 4bit|Ack 24bit |c2s+用户id 64bit|分块下标 8bit|Pakid 分包id 24|分块数据
以上是包头块 + 分块数据 包头块 size 是
32+4+4+24+64+8+24(bit)
分块数据最终要组装成 ProtoBody
流可以选择用 16 进制,这里需要开发 1 个解码的函数,然后shellProtocol.decode.ProtoBody.toString(16)
这个后面具体讲。
判断合法性这里不是常规的校验类型,是独有的,crc 校验是为了确保包到 udp 层已经是组装好的,组装错的包会在 crc 时被丢弃掉,数据分片 | 分块下标 8bit|Pakid 分包 id 24| 在 ip 层之后处理。
这里需要进行按位去计算,密码学是独立的组件,只要知道是哪一类型的,就可以直接拿来用。
var crypto = require('crypto');
var crc = require('crc32');//需要确定crc类型,具体需要读源码判断
这里还是稍微带一部分
32bit =4 字节,2 位 16 进制数据等于 1 个字节,所以这里长度要确保是 16 位
//我们需要变成0000000000101001 好的测试代码用tape,烂的测试代码如下
var hex="0x29";//test数据
var binary = parseInt(hex, 16).toString(2);
console.log(binary); //101001
var oldLength = binary.length;
console.log(oldLength);//6
var hexLen =hex.toString().length*4 //长度要16
//oldLength < hex.toString().length * 4 写个for迭代器在拼接保留下
//不用第三方库的处理的代码
function hexTobinary(hex) {
//16进制-->2进制
var binary = parseInt(hex, 16).toString(2);
//16进制长度
var oldLength = binary.length;
//16进制长度*4等于2进制
if (oldLength < hex.toString().length * 4) {
for (var i = 0; i < hex.toString().length * 4 - oldLength; i++)
//保留拼接保留0
binary = '0' + binary;
}
return binary;
}
var hex="0x29";
console.log(hexTobinary(hex)); //cls
同样还需要写各种其他 crc 转码的,以后要慢慢介绍。crc 会关联到这个 udp 组装的包是否会被丢弃,需要结合后面讲。
| 协议类型 4bit| 作为数据报的身份验证协议
|flag 4bit| 这里决定了 udp 分类 比如结束标记,只执行 1 次标记,记录传输对象的位置,阻塞/等待 超时处理等等 核心很大一块内容,大部分代码都是 C 风格的。
|Ack 24bit | 确定序号 可以参考下文
这里拿一段 tcp 的打包 Protocol.encode,这里不包含 flag 的
// 打包成二进制
msgPkg = msgPack.pack();
var byteBuffer = new ByteBuffer(); //核心在ByteBuffer的定义,也是根据实际业务组装的
byteBuffer.int32(12 + byteLength); //包长 4字节
byteBuffer.int32(0); //序列号 00000000
byteBuffer.int32(EProtoId); //命令号 4字节
byteBuffer.int32(0); //序列号之后的4个字节的 00 00 00 00
byteBfr = byteBuffer.pack();
// 将byteBfr和msgPkg拼接起来赋给buff,然后返回buff
var buff = Buffer.concat([byteBfr, msgPkg])
上为客户端到服务端,服务器接收就是客户端发送,下面定义的{clientPort...为类指}
<无符号>short clientPort 16bit
short serverPort 16bit
short msgLength 16bit
short checkroleid 16bit ==>这里也可能是别的
接收端需要互相绑定端口,1 个基于 server 端的官方的范例如下,node.js 的使用到 dgram 基于事件层的。
//socket.bind([port][, address][, callback])
var dgram = require('dgram');
var server = dgram.createSocket('udp4');
server.on('error', function (err) {
console.log('异常信息' + err.stack);
server.close();
});
//rinfo family
server.on('message', function (msg, rinfo) {
console.log('服务器收到:' + msg + '地址' + rinfo.address + ':' + rinfo.port);
});
server.on('listening', function () {
var address = server.address();
console.log('监听信息:' + address.address + ':' + address.port);
});
server.bind(30000);
小论 udp(二)会连接 js |c2s+用户id 64bit|
的部分继续讲,后面还有js|分块下标 8bit|Pakid分包id 24|
和基于 dgram 等具体用法。