问题:如何在代码层面封装协议细节?如何将接收缓冲区中的数据解析为 Message ?
深度思考
数据是否能够解析成为 Message ?
数据量足够
- 如果数据量足够,是否能够解析不止一个 Message?
- 如何处理剩余数据 (属于下一个 Message)
数据量不足
- 是否达到协议最小长度(12 字节)?
- 如何处理数据量超过最小长度,但不足以创建一个 Message 的情况?
初步的解决方案
- 定义一个模块用于从字节流解析 Message
- 可 从指定内存 或 从指定文件描述符 读取并解析
当至少存在 12 个字节时开始解析
- 首先解析协议中的头信息和数据区长度(length)
- 根据数据区长度继续从字节流读取数据(payload)
- 当协议数据解析完成时,创建 Message 并返回,否则,返回 NULL
协议解析模块的初步设计
解析器接口定义
typedef void MParser;
MParser *MParser_New();
Message *MParser_ReadMem(MParser *parser, unsigned char *mem, unsigned int length);
Message *MParser_ReadFd(MParser *parser, int fd);
void MParser_Reset(MParser *parser);
void MParser_Del(MParser *parser);
解析器数据结构
typedef struct msg_parser {
Message cache; // 缓存已解析的消息头
int header; // 标识消息头是否解析成功
int need; // 标识还剩多少字节才能完成解析
Message *msg; // 解析中的协议消息(半成品)
}MsgParser;
条件:内存长度至少连续 12 个字节
memcpy(&p->cache, mem, p->need);
p->cache.type = ntohs(p->cache.type); // 从网络字节序转换为本机字节序
p->cache.cmd = ntohs(p->cache.cmd);
p->cache.index = ntohs(p->cache.index);
p->cache.total = ntohs(p->cache.total);
p->cache.length = ntohs(p->cache.length);
mem += p->need;
length -= p->need;
p->header = 1;
p->need = p->cache.length;
从内存中读取 payload 中的数据(可读取多次)
if (!p->msg) { // 成功创建消息头之后, 创建 Message
p->msg = malloc(sizeof(p->cache) + p->need);
if (p->msg) {
*p->msg = p->cache;
}
}
if (p->msg) {
unsigned int len = (p->need < length) > p->need : length;
unsigned int offset = p->msg->length - p->need;
memcpy(p->msg->payload + offset, mem, len);
p->need -= len;
}
编程实验:协议解析模块初步设计
msg_parser.h
#ifndef MSG_PARSER_H
#define MSG_PARSER_H
#include \"message.h\"
typedef void MParser;
MParser *MParser_New();
Message *MParser_ReadMem(MParser *parser, unsigned char *mem, unsigned int length);
Message *MParser_ReadFd(MParser *parser, int fd);
void MParser_Reset(MParser *parse);
void MParser_Del(MParser *parse);
#endif
msg_parser.c
#include
#include
#include
#include
#include \"msg_parser.h\"
typedef struct msg_parser {
Message cache;
int header;
int need;
Message *msg;
}MsgParser;
MParser *MParser_New()
{
MsgParser *ret = calloc(1, sizeof(MsgParser));
MParser_Reset(ret);
return ret;
}
Message *MParser_ReadMem(MParser *parser, unsigned char *mem, unsigned int length)
{
Message *ret = NULL;
MsgParser *p = (MsgParser*)parser;
if (!p || !mem || !length) {
return ret;
}
if (!p->header) {
if (p->need <= length) {
memcpy(&p->cache, mem, p->need);
p->cache.type = ntohs(p->cache.type);
p->cache.cmd = ntohs(p->cache.cmd);
p->cache.index = ntohs(p->cache.index);
p->cache.total = ntohs(p->cache.total);
p->cache.length = ntohl(p->cache.length);
mem += p->need;
length -= p->need;
p->header = 1;
p->need = p->cache.length;
ret = MParser_ReadMem(parser, mem, length);
}
} else {
if (!p->msg) {
p->msg = malloc(sizeof(p->cache) + p->need);
if (p->msg) {
*p->msg = p->cache;
}
}
if (p->msg) {
unsigned int len = (p->need < length) ? p->need : length;
unsigned int offset = p->msg->length - p->need;
memcpy(p->msg->payload, mem, len);
p->need -= len;
}
if (!p->need) {
ret = p->msg;
p->msg = NULL;
MParser_Reset(p);
}
}
return ret;
}
Message *MParser_ReadFd(MParser *parser, int fd)
{
Message *ret = NULL;
return ret;
}
void MParser_Reset(MParser *parse)
{
MsgParser *p = (MsgParser*)parse;
if (p) {
p->header = 0;
p->need = sizeof(p->cache);
free(p->msg);
p->msg = NULL;
}
}
void MParser_Del(MParser *parse)
{
MsgParser *p = (MsgParser*)parse;
if (p) {
free(p->msg);
free(p);
}
}
test.c
#include
#include
#include
#include \"msg_parser.h\"
int main()
{
MParser *p = MParser_New();
char buf[] = {0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04};
char data[] = {0x11, 0x12, 0x13, 0x14};
Message *m = MParser_ReadMem(p, buf, sizeof(buf));
int i = 0;
if (!m) {
printf(\"parse again...\\n\");
m = MParser_ReadMem(p, data, sizeof(data));
}
printf(\"m = %p\\n\", m);
if (m) {
printf(\"type = %d\\n\", m->type);
printf(\"cmd = %d\\n\", m->cmd);
printf(\"index = %d\\n\", m->index);
printf(\"total = %d\\n\", m->total);
printf(\"length = %d\\n\", m->length);
for (i=0; ilength; ++i) {
printf(\"0x%02x \", m->payload[i]);
}
printf(\"\\n\");
free(m);
}
MParser_Del(p);
return 0;
}
思考:如何通过 socket 文件描述符实时解析协议数据?