《Linux网络开发必学教程》9_应用协议解析模块(上)

问题:如何在代码层面封装协议细节?如何将接收缓冲区中的数据解析为 Message ?
深度思考
数据是否能够解析成为 Message ?
  • 数据量足够
    • 如果数据量足够,是否能够解析不止一个 Message?
    • 如何处理剩余数据 (属于下一个 Message)
  • 数据量不足
    • 是否达到协议最小长度(12 字节)?
    • 如何处理数据量超过最小长度,但不足以创建一个 Message 的情况?
初步的解决方案
  • 定义一个模块用于从字节流解析 Message
  • 可 从指定内存 或 从指定文件描述符 读取并解析
  • 【《Linux网络开发必学教程》9_应用协议解析模块(上)】当至少存在 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 文件描述符实时解析协议数据?

    推荐阅读