问题:如何在代码层面封装协议细节?如何将接收缓冲区中的数据解析为 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 <malloc.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>

#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 <stdio.h>
#include <unistd.h>
#include <stdlib.h>

#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; i<m->length; ++i) {
            printf("0x%02x ", m->payload[i]);
        }

        printf("\n");

        free(m);
    }

    MParser_Del(p);

    return 0;    
}

思考:如何通过 socket 文件描述符实时解析协议数据?

TianSong
737 声望139 粉丝

阿里山神木的种子在3000年前已经埋下,今天不过是看到当年注定的结果,为了未来的自己,今天就埋下一颗好种子吧