笔者是游戏测试,近期比较频繁在搞这部分的,下个阶段应该也会做这块的,文章打算坚持写下去,主要语言是 node.js js>=6.x

UDP 场景

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)这个后面具体讲。

| 校验方式 32bit|

判断合法性这里不是常规的校验类型,是独有的,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|Ack 24bit |

| 协议类型 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])

|c2s+ 用户 id 64bit|

上为客户端到服务端,服务器接收就是客户端发送,下面定义的{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 等具体用法。


↙↙↙阅读原文可查看相关链接,并与作者交流