实用消息组装方法
1.变长消息体使用柔性数组
linux中消息通常使用结构体进行封装,在消息结构体中使用柔性数组不仅不占用结构体空间,还可以使消息结构体的长度随使用的需要进行扩展。
消息结构:
typedef struct tagDate
{
s_32cmd_id;
s_32len;
s_8body[0];
}Date;
#defineMSG_HEAD_LEN (sizeof(Date))
一般使用方法:
Date* sdate = NULL;
……
sdate = (Date*)malloc(MSG_HEAD_LEN+ user_body_len);
sdate->cmd_id = user_cmd;
sdate->len = (MSG_HEAD_LEN+ user_body_len);
memcpy(sdate->body,user_body, user_body_len);
……
user_body可以是另外一个结构体,至此,消息封装完成。使用柔性数组与使用指针的区别主要有一下两点:
①当使用指针p时,p占用4字节空间,而使用柔性数组时,不占结构体空间,只是一个地址常量。
②当使用指针p时,需要为p开辟消息体的空间,p所指向的空间与结构体分离,释放空间时需要单独释放且需要在释放结构体指针之前完成;而使用柔性数组时,消息体的内存空间与结构体空间相邻,是连续的。
2.兼容消息体使用类型转换和偏移
有时候我们在工程中使用的消息结构体需要新增成员,但是又要兼容以前的消息,这时候如果定义两套消息结构,适配点可能比较多,如果消息体当初设计的容量有冗余,可以考虑强制类型转化加偏移的方法适配新的消息结构。
原有的消息结构:
typedef struct tagDate
{
s_32cmd_id;
s_32len;
s_8body[1024];
}Date;
新的消息结构:
typedef struct tagDate_new
{
s_32cmd_id;
s_32len;
s_32 type;
s_8body[1020];
}Date_new;
#defineMSG_HEAD_LEN 8
#defineMSG_HEAD_LEN_OFFSET 4
比如在消息结构中新增一个成员type,如果原来消息结构中body的容量有富裕,可以从body中借4个字节用作type。
填充消息前的消息初始化:
void init_msg_head(Date* date)
{
if (g_new_msg_type== 1)
{
date->len = MSG_HEAD_LEN_OFFSET;
}
else
{
date->len= 0;
}
}
填充消息成员:
Date sdate;
……
sdate.cmd_id = user_cmd;
Date_new* tmp_date = (Date_new*)&sdate;
tmp_date->type =user_type;
填充消息体:
put_msg_body(&sdate,user_body,user_body_len);
void put_msg_body(Date*date, void* user_body,int user_body_len)
{
memcpy(&((sdate->body)[sdate->len]), user_body, user_body_len);
}
sdate.len += MSG_HEAD_LEN;
这是一个简单的发送端消息实现,消息接收端在收到消息后,使用完type后,可以使用memmove将新消息结构的body前移覆盖掉type,后面的消息处理流程可以保持原状。
void handle_new_msg(Date_new* date)
{
……
handle_msg_type(date->type);
……
//还原消息结构兼容原有的处理流程
memmove((void*)&(date->type),
(void*)(date->body),
date->len- MSG_HEAD_LEN - MSG_HEAD_LEN_OFFSET);
date->len -= MSG_HEAD_LEN_OFFSET;
char* tmp = (char*)date;
memset((void*)&(tmp[date->len]),
0,
MSG_HEAD_LEN_OFFSET);
……
}
这个方法可以用在一些比较特殊的场合,比如给消息增加鉴权字段,新的消息中含有鉴权而老的消息结构没有,一般接收消息后,首先取出消息头中的鉴权字段校验,通过后即可用消息体将鉴权字段覆盖,之后的消息处理流程可以沿用老的流程。发送消息时通过body体偏移和新消息结构体强制转换,将老消息结构一部分body体转化为鉴权字段使用。
3.使用sendmsg方法拼接多个消息
sendmsg一般使用为一次性将多个消息拼接发送。其中使用到了io向量iovec,一般使用方法如下:
#define IO_MAXSIZE 100
/*消息体结构支持多消息组合*/
typedef struct tagMsg
{
s_32 count;
struct iovec io[IO_MAXSIZE];
}MultiMsg;
typedef structtagIOSockPara
{
u_32 sockfd;
struct sockaddr_in remote_addr;
}IOSockPara;
/*存入分段数据*/
int Data_Push(MultiMsg *msg, void *data, int len)
{
if ((NULL == data)||(msg->count >= IO_MAXSIZE))
{
printf("Push failure !\n");
return FAILURE;
}
msg->io[msg->count].iov_base = data;
msg->io[msg->count].iov_len = len;
msg->count++;
return SUCCESS;
}
/*基于端口的发包函数*/
u_32SendPacket(IOSockPara* ioport, MultiMsg *msg, u_32 msg_len)
{
s_32 send_size;
struct msghdr msgtmp;
memset(&msgtmp,0,sizeof(msgtmp));
msgtmp.msg_name = &(ioport->remote_addr);
msgtmp.msg_namelen = sizeof(struct sockaddr_in);
msgtmp.msg_iov = msg->io;
msgtmp.msg_iovlen = msg->count;
send_size = sendmsg(ioport->sockfd, &msgtmp, 0);
if (send_size < 0)
{
perror("sendmsg err\n");
}
return SUCCESS;
}
以上是工程中经常用到的方法,根据场景各有优势,做个小结,后续有心得继续补充。