实现自己的应用层协议和解析器
我们每天都在使用应用层协议,http协议,smtp协议,dns协议等。但是我们有没有想过实现自己的应用层协议呢?有没有想过应用层协议的实现和解析呢?本文就着这些问题,分享一下如何实现定义一个简单的应用层协议,并实现对应的解析器。
1 定义协议
协议,顾名思义,就是一种通信的约定,通信双方都能理解就行,我们可以定义复杂的协议,也可以定义简单的协议。本文在于讲解原理,定义一个几乎最简单的协议。格式如下
开始字符一字节|整个报文长度四字节|序列号四字节|数据部分n字节|结束字符一字节
// 开始标识符
const PACKET_START = 0x3;
// 结尾标识符
const PACKET_END = 0x4;
// 整个数据包长度
const TOTAL_LENGTH = 4;
// 序列号长度
const SEQ_LEN = 4;
// 数据包头部长度
const HEADER_LEN = TOTAL_LENGTH + SEQ_LEN;
2 解析协议
本文是基于tcp的应用层协议,我们知道tcp是面向字节流的协议,他只负责透明传输字节,不负责解释字节,但是我们的数据包是有固定格式的,如果tcp把我们两个包放在一起传输,那我们如何识别呢?所以我们协议格式里,定义了开头和结束字符,一般定义一些特殊字符或者特殊格式为结束字符,这里我们定义的开始和结束字符是0x3和0x4。所以我们要做的事情就是,判断tcp交给我们的字节流,根据开始和结束字符,逐个解析出我们的协议报文。
3 实现
下面开始通过代码实现这个协议的解析。首先实现一个有限状态机。
/**
*
* @param {*} state 状态和处理函数的集合
* @param {*} initState 初始化状态
* @param {*} endState 结束状态
*/
function getMachine(state, initState, endState) {
// 保存初始化状态
let ret = initState;
let buffer;
return function(data) {
if (ret === endState) {
return;
}
if (data) {
buffer = buffer ? Buffer.concat([buffer, data]) : data;
}
// 还没结束,继续执行
while(ret !== endState) {
if (!state[ret]) {
return;
}
/*
执行状态处理函数,返回[下一个状态, 剩下的数据],
*/
const result = state[ret](buffer);
// 如果下一个状态是-1或者返回的数据是空说明需要更多的数据才能继续解析
if (result[0] === -1) {
return;
}
// 记录下一个状态和数据
[ret, buffer] = result;
if (!buffer.length) {
return;
}
}
}
}
因为解析的过程就是各种状态的转移和处理,所以用有限状态机来实现会清晰很多。上面的代码是一个小型的有限状态机框架。我们通过定义状态和对应的处理函数、开始状态、结束状态。然后得到一个状态机,就可以对输入的数据进行处理了。接下来我们定义一些数据结构。
定义一个表示数据包的类。
// 表示一个协议包
class Packet {
constructor() {
this.length = 0;
this.seq = 0;
this.data = null;
}
set(field, value) {
this[field] = value;
}
get(field) {
return this[field];
}
}
定义状态机状态和函数集
// 解析器状态
const PARSE_STATE = {
PARSE_INIT: 0,
PARSE_HEADER: 1,
PARSE_DATA: 2,
PARSE_END: 3,
};
// 保存当前正在解析的数据包
var packet;
const STATE_TRANSITION = {
[PARSE_STATE.PARSE_INIT](data) {
if (!data || !data[0]) {
return [-1, data];
}
if (data[0] !== PACKET_START) {
return [-1, data ? data.slice(1) : data];
}
packet = new Packet();
// 跳过开始标记符
return [PARSE_STATE.PARSE_HEADER, data.slice(Buffer.from([PACKET_START]).length)];
},
[PARSE_STATE.PARSE_HEADER](data) {
if (data.length < HEADER_LEN) {
return [-1, data];
}
// 有效数据包的长度 = 整个数据包长度 - 头部长度
packet.set('length', data.readUInt32BE() - HEADER_LEN);
// 序列号
packet.set('seq', data.readUInt32BE(TOTAL_LENGTH));
// 解析完头部了,跳过去
data = data.slice(HEADER_LEN);
return [PARSE_STATE.PARSE_DATA, data];
},
[PARSE_STATE.PARSE_DATA](data) {
const len = packet.get('length');
if (data.length < len) {
return [-1, data];
}
packet.set('data', data.slice(0, len));
// 解析完数据了,完成一个包的解析,跳过数据部分和结束符
data = data.slice(len);
// 解析完一个数据包,输出
return [PARSE_STATE.PARSE_END, data];
},
[PARSE_STATE.PARSE_END](data) {
if (!data || !data[0]) {
return [-1, data];
}
if (data[0] !== PACKET_END) {
return [-1, data ? data.slice(1) : data];
}
console.log('parse success: ', packet);
// 跳过开始标记符
return [PARSE_STATE.PARSE_INIT, data.slice(Buffer.from([PACKET_START]).length)];
},
};
这就是所有的代码。最后我们写两个测试用例玩一下。首先写一个服务器。
const net = require('net');
const parse = require('./machine');
net.createServer(function(socket) {
socket.on('data', function() {
console.log('receiver: ', ...arguments)
parse(...arguments);
});
socket.on('error', function() {
console.log(...arguments)
})
}).listen(10001);
然后写两个测试用例
用例一:正常发送
const net = require('net');
async function test() {
const socket = net.connect({port: 10001});
socket.on('error', function() {
console.log(...arguments);
});
let i = 0;
const a = setInterval(() => {
socket.write(Buffer.from([0x3,0x0, 0x0, 0x0, 0x9, 0x0, 0x0, 0x0, 0x1, i+1, 0x4]));
i++ > 5 && (socket.end(), clearInterval(a));
},1000)
}
test()
用例二:延迟发送
const net = require('net');
async function test() {
const socket = net.connect({port: 10001});
socket.on('error', function() {
console.log(...arguments);
});
let data = Buffer.from([0x3,0x0, 0x0, 0x0, 0x9, 0x0, 0x0, 0x0, 0x1, 0x1, 0x4]);
let i = 0;
const id = setInterval(() => {
if (!data.length) {
socket.end();
return clearInterval(id);
}
const packet = data.slice(0, 1);
console.log(packet)
socket.write(packet);
data = data.slice(1);
}, 500);
}
test()
这就完成了一个应用层协议的实现和解析。源码地址(https://github.com/theanarkh/tiny-application-layer-protocol)
本文地址:https://blog.csdn.net/THEANARKH/article/details/107274327
推荐阅读
-
使用C#实现基于TCP和UDP协议的网络通信程序的基本示例
-
自己实现的TrieTree,对比一下效率 数据结构和算法
-
JavaSE阶段综合项目学生管理系统(写属于自己的MyList接口和ArrayList集合实现类工具)
-
利用Qt/C++在腾讯云/阿里云服务器搭建TCP/IP协议实现网络通信以及Qt在linux下的安装和程序打包踩坑(详解)
-
c语言:实现一个函数,打印乘法口诀表,口诀表的行数和列数自己指定
-
python实现的udp协议Server和Client代码实例
-
Python实现同时兼容老版和新版Socket协议的一个简单WebSocket服务器
-
用c#自己实现一个简单的JSON解析器
-
实现自己的应用层协议和解析器
-
Nginx配置实现基于tcp协议的反向代理和负载均衡