番外篇17:柔性数组理解及当做不定长协议讲解(附libevent跨平台实现的不定长数组代码)

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;
}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值