1.需求
- 在日常的编程中,有时候需要在结构体中存放一个长度动态的字符串,一般的做法,是在结构体中定义一个指针成员,这个指针成员指向该字符串所在的动态内存空间
- 但是:针对以上问题 可以有一个更优的解决方案即,在结构体的尾部放置一个0长度的数组是一个绝妙的解决方案,我们既能直接引用该字符串,又不占用结构体的空间。不过,C/C++标准规定不能定义长度为0的数组,因此,有些编译器就把0长度的数组成员作为自己的非标准扩展。
typedef struct
{
WORD usMsgID;
WORD usMsgLen;
char pData[0];//柔性数组
}FLEXIBLE_DEF_STRU;
- 有些编译器会报错无法编译可以改成
typedef struct
{
WORD usMsgID;
WORD usMsgLen;
//大小可变,最佳设计是该改为char Data[1],真正包头大小为sizeof(FLEXIBLE_DEF_STRU)- sizeof(char)
char pData[];
}FLEXIBLE_DEF_STRU;
2.使用条件
①结构体中的柔性数组成员前面必须至少一个其他成员。
②柔性数组必须是结构体的最后一个成员。
③柔性数组成员只作为一个符号地址存在,sizeof 返回的这种结构大小不包括柔性数组的内存。
④包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构体的大小,以适应柔性数组的预期大小。
⑤柔性数组成员不仅可以用于字符数组,还可以是元素为其它类型的数组。
⑥内存已经分配了,数组的长度是可以通过计算出来的
3.怎么使用?(代码)
char szDataInfo[] = "hello world";
//malloc内存大小为结构体大小+数据大小,包含‘\0’
FLEXIBLE_DEF_STRU *pTest = (FLEXIBLE_DEF_STRU *)malloc(sizeof(FLEXIBLE_DEF_STRU)+sizeof(szDataInfo));
char szInfo[] = "hello world";
FLEXIBLE_DEF_STRU *pTest = (FLEXIBLE_DEF_STRU *)malloc(sizeof(FLEXIBLE_DEF_STRU)+sizeof(szInfo));
pTest->usMsgID = 0x1234;
pTest->usMsgLen = sizeof(szInfo);
//若拷贝的内容是字符串,注意'\0'也需要被拷贝走,
//所以使用sizeof(),不能是strlen()
memcpy_s(pTest->pData,pTest->usMsgLen,szInfo, sizeof(szInfo));
printf("%s\n",pTest->pData);
//只需要释放一次内存
SAFE_FREE(pTest);
4.可能遇到的问题
-
对于包含有柔性数组的结构体,我们对内存的申请和释放只需要一次。因为我们的数据区buffer紧随在结构体之后,数据和结构体在同一个内存块中,因此我们只需要释放一次,示意图如下:
-
我们应当尽量使用标准形式,在非C99的场合,可以使用指针方法。需要说明的是:C89不支持这种东西,C99把它作为一种特例加入了标准。但是,C99所支持的是incomplete type,而不是zero array,形同int a[0];这种形式是非法的,C99 支持的形式是形同int a[];只不过有些编译器把int a[0];作为非标准扩展来支持,而且在C99 发布之前已经有了这种非标准扩展了,C99 发布之后,有些编译器把两者合而为一了。
5.不定长协议当协议
发送端
#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <signal.h>
#ifndef _WIN32
#include <cstdlib>
#include <unistd.h>
#endif
#ifdef _WIN32
#include <WinSock2.h>
#endif
#include <memory.h>
#ifndef _WIN32
#include <netinet/in.h>
#ifdef _XOPEN_SOURCE_EXTENDED
#include <arpa/inet.h>
#endif
#include <sys/socket.h>
#endif
#include <event.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/listener.h>
#include <event2/util.h>
#include <event2/event.h>
#include "ngx_c_crc32.h"
using namespace std;
#define redis_reply_string 1
#define redis_reply_array 2
#define redis_reply_integer 3
#define redis_reply_nul 4
#define redis_reply_status 5
#define redis_reply_error 6
typedef signed char int8_t;
typedef short int16_t;
typedef int int32_t;
//typedef long long int64_t;
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
//typedef unsigned long long uint64_t;
struct rA
{
uint8_t type;
uint8_t len;
uint16_t calccrc; //crc32用来校验包体长度是否改变
};
struct db2gs
{
uint32_t toatalSize; //表示数据总长度
uint8_t nodeNum; //表示有多少个结构体
char data[0]; //柔性数组的灵魂
};
//----------------------------函数参数
static const int PORT = 8888;
static char g_szWriteMsg[256] = {0};
static char g_szReadMsg[3000] = {0};
static int g_iCnt = 0;
static void conn_writecb(struct bufferevent*,void*);
static void conn_readcb(struct bufferevent*,void*);
static void conn_eventcb(struct bufferevent*,short,void*);
//----------------------------
static void
conn_writecb(struct bufferevent* bev,void* user_data)
{
//printf("touch conn_writecb\n");
// if(0 < strlen(g_szWriteMsg))
// {
// bufferevent_write(bev,g_szWriteMsg,strlen(g_szWriteMsg));
// memset(g_szWriteMsg,0x00,sizeof(g_szWriteMsg));
// }
}
static void
conn_readcb(struct bufferevent* bev,void* user_data)
{
memset(g_szReadMsg,0,strlen(g_szReadMsg));
printf("start to read data@\n");
struct evbuffer* input = bufferevent_get_input(bev);
size_t sz = evbuffer_get_length(input);
if(0 < sz)
{
//1.recv data
bufferevent_read(bev,g_szReadMsg,sz);
printf("recv data Size:%d\n",sizeof(g_szReadMsg));
db2gs* rp = (db2gs*)g_szReadMsg;
//2.recv nodeNum and totalSize
size_t totalSize = rp->toatalSize;
size_t nodeNum = rp->nodeNum;
printf("elements:%d\n",nodeNum);
//3.start to recv flexible array
char* ptmp = (char*)(rp->data);
rA* rtemp = (rA*)ptmp;
int8_t type = 0;
uint32_t len = 0;
char tmp[500] = {0};
uint16_t calccrc_2 = 0;
char* tpm = ptmp;
for(int32_t nIndex = 0;nIndex < nodeNum;++ nIndex)
{
rtmp = (rA*)ptmp;
type = rtmp->type;
printf("Num %d CRedisRp->type:%d\n",nIndex+1.type);
len = rtmp->len;
printf("Num %d CRedisRp->len:%d\n",nIndex + 1,len);
printf("Num %d CRedisRp->calccrc:%d\n",nIndex+1.calccrc_2=rtemp->calccrc);
memset(tmp,0,sizeof(tmp));
tpm=ptmp + sizeof(rA);
memcpy(tmp,tpm,len);
printf("recv string from server:%s\n",tmp);
ptmp = ptmp + len + sizeof(rA);
}
}
else
{
printf("noting!\n");
}
}
static void
conn_eventcb(struct bufferevent* bev,short events,void* user_data)
{
if(events & BEV_EVENT_EOF)
{
printf("Connection closed.\n");
}
else if(events & BEV_EVENT_ERROR)
{
printf("Got an error on the connection\n");
}
else if(events & BEV_EVENT_CONNECTED)
{
printf("connect success!\n");
const char* msg = "hi server,how are you!";
bufferevent_write(bev,msg,strlen(msg));
return;
}
bufferevent_free(bev);
}
int
main(int argc,char** argv)
{
#ifdef _WIN32
WSADATA wsa_data;
WSAStartup(0x0201,&wsa_data);
#endif
struct event_basse* base;
struct sockaddr_in sin;
base = event_base_new();
if(!base)
{
fprintf(stderr,"Could not initialize libevnet!\n");
return 1;
}
memset(&sin,0,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(PORT);
#ifdef _WIN32
inet_pton(AF_INET,"172.17.0.48",&sin.sin_addr.s_addr);
#else
sin.sin_addr.s_addr = inet_addr("172.17.0.52");
#endif
struct bufferevent* bev = bufferevent_socket_new(base,-1,BEV_OPT_CLOSE_ON_FREE);
if(bev == NULL)
{
fprintf(stderr,"socket init failed\n");
return 1;
}
bufferevent_setcb(bev,conn_readcb,conn_writecb,conn_eventcb,NULL);
//connect to server
int flag = bufferevent_socket_connect(bev,(struct sockaddr*)&sin,sizeof(sin));
if(-1 == flag)
{
fprintf(stderr,"connect failed\n");
return 1;
}
bufferevent_enabale(bev,EV_READ|EV_WRITE);
event_base_dispatch(base);
event_base_free(base);
printf("done\n");
return 0;
}
接收端
#include <iostream>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <signal.h>
#ifndef _WIN32
#include <cstdlib>
#include <unistd.h>
#endif
#ifdef _WIN32
#include <WinSock2.h>
#endif
#include <memory.h>
#ifndef _WIN32
#include <netinet/in.h>
#ifdef _XOPEN_SOURCE_EXTENDED
#include <arpa/inet.h>
#endif
#include <sys/socket.h>
#endif
#include <event.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/listener.h>
#include <event2/util.h>
#include <event2/event.h>
#include "ngx_c_crc32.h"
using namespace std;
#define redis_reply_string 1
#define redis_reply_array 2
#define redis_reply_integer 3
#define redis_reply_nul 4
#define redis_reply_status 5
#define redis_reply_error 6
typedef signed char int8_t;
typedef short int16_t;
typedef int int32_t;
//typedef long long int64_t;
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
//typedef unsigned long long uint64_t;
struct rA
{
uint8_t type;
uint8_t len;
uint16_t calccrc; //crc32用来校验包体长度是否改变
};
struct db2gs
{
uint32_t toatalSize; //表示数据总长度
uint8_t nodeNum; //表示有多少个结构体
char data[0]; //柔性数组的灵魂
};
//----------------------------函数参数
static void initEventArray();
static int addEvent(int fd,struct event* ev);
static struct event* getEventByFd(int fd);
static void readcb(evutil_socket_t fd,short events,void* arg);
static void conncb(evutil_socket_t fd,short events,void* arg);
//----------------------------
void initEventArray()
{
int i;
for(i = 0;i<_MAX_CLIENT_;++i)
{
mFdEvents[i].fd = -1;
mFdEvents[i].ev = NULL;
}
}
int addEvent(int fd, struct event* ev)
{
int i;
for(i = 0;i < _MAX_CLIENT_;++i)
{
if(0 > mFdEvents[i].fd)
{
break;
//return -1;
}
}
if(i == _MAX_CLIENT_)
{
printf("too many clients...\n");
return -1;
}
mFdEvent[i].fd = fd;
mFdEvent[i].ev = ev;
return 0;
}
struct event* ghetEventByFd(int fd)
{
int i;
for( i = 0; i< _MAX_CLIENT_;++i)
{
if(mFdEvents[i].fd == fd)
{
return mFdEvents[i].ev;
}
}
return NULL;
}
void readcb(evutil_socket_t fd,short events,void* arg)
{
CCRC32* p_crc32 = CCRC32::GETInstance();
char buf[256] = {0};
int ret = recv(fd,buf, sizeof(buf),0);
if( 0>=ret)
{
#ifndef _WIN32
close(fd);
#else
closesocket(fd);
#endif
event_del(getEventByFd(fd));
}
else
{
int i;
for(i = 0;i < ret:++i)
{
buf[i] = toupper(buf[i]);
}
printf("客户端已经链接,接受对端字符串string:%s\n",buf);
//1.先算占多少内存
char tmp_str[50] = "strcpy123456";
size_t totalSize = sizeof(db2gs) + 3*strlen(tmp_str) + 3*sizeof(rA);
db2gs* rp = (db2gs*)malloc(totalStruct);
rp->nodeNUm = 3;
char* ptmp = (char*)(rp->data);
char* tmp = (char*)(rp->data);
size_t p = 0;
rA* ra = (rA*)ptmp;
for(int16_t nIndex = 0;nIndex < rp->nodeNum;++nIndex)
{
ra = (rA*)ptmp;
ra->type = 3;
ra->len = strlen(tmp_str);
ra->calccrc = p_crc32->Get_CRC((unsigned char* )tmp_str,ra->len);
memccpy((void*)ptmp,(void*)ra,sizeof(rA));
printf("len:%d,tmp_str:%s,str:%s\n",ra->len,tmp_str,ptmp);
ptmp += ra->len;//跳过字符串复制
}
//2.打印测试
rA* rb = (rA*)tmp;
uint8_t type_2 = rb->type;
uint8_t len_2 = rb->len;
uint16_t calccrc_2 = rb->calccrc;
printf("type = %d,len=%d,calccrc=%3\n",type_2,len_2,calccrc_2);
tmp = tmp +len_2;
rb = (rA*)tmp;
type_2 = rb->type;
len_2 = rb->len;
calccrc_2 = rb->calccrc;
//3.发送数据
send(fd,(void*)rp,toatalStuct,0);
free(rp);
}
}
void conncb(evutil_socket_t fd,short events,void* arg)
{
printf("客户度已经连接上!\n");
struct event_base* base = (struct event_base*)arg;
stuct sockaddr_in client;
socklen_t lth = sizeof(client);
int cfd = accept(fd,(struct sockaddr*)&client,&ith);
if( 0 < cfd)
{
//创建事件
struct event* readev = event_new(base,cfd,EV_READ|EV_PERSIST,readcb,base);
//注册事件
event_add(readev,NULL);
//添加到数组
addEvent(cfd,readev);
}
}
//编译指令
//g++ **.cpp -event
int main()
{
printf("hello world!\n");
//初始化CRC32单例类
CCRC32::GetInstance();
#ifdef _WIN32
WSADATA wsa_data;
WSAStartup(0x0201,&wsa_data);
#endif
//初始化CRC32单例类
struct event_base* base = event_base_new();
//创建套接字
int lfd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in recv;
bzero(&serv,sizeof(serv));
serv.sin_family = AF_INET;
serv.sin_addr.s_addr = htonl(INADDR_ANY);
serv.sin_port = htons(8888);
#ifndef _WIN32
int opt = 1;
#else
BOOL bReuseaddr = TRUE;
#endif
//设置套接字套用
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,(const char*)&opt,sizeof(opt));
if(0 > bind(lfd,(struct sockaddr*)&serv,sizeof(serv)))
{
perror("bind err";
return -1;)
}
//监听
listen(lfd,128);
//创建事件设置回调
initEventArray();//初始化事件数组
//创建事件
struct event* connev = event_new(base,lfd,EV_READ|EV_PERSIST,conncb,base);
//注册事件
event_add(connev,NULL);
//循环监听
event_base_dispatch(base);
//回收内存资源
#ifndef _WIN32
close(lfd);
#else
closesocket(lfd);
#endif
event_base(base);
return 0;
}