XEngine-TCP网络沾包流式套接字处理方法

相信大家在学习和使用TCP进行网络通信的时候都会遇到这个问题,网络沾包,这个问题困扰大家已久.我们知道处理这种方法就是加自己的头,但是却会出现各种问题,比如,接受到的数据头不对,数据包缺少,数据多了等等情况,我们应该怎么处理?

       网上有各种各样的网络库和网络沾包解决方法,但是他们都缺少了一种,就是任务数据分发,或者流程使用上不方便.

       我们的网络包处理模块,可以解决你的这些所有痛楚.


     我们的网络数据处理模块在XEngine_HelpComponents组件下,名字是HelpComponents_Packets.通过此模块的API函数库,可以方便的实现一套标准网络沾包处理.

      这个模块里面有几套网络流式包处理函数.一般的,我们用标准处理流程的函数库.这套函数库使用了标准协议头来实现.如果大家以后熟悉了,还可以用自定义协议包处理函数流程库来做自己的协议包处理,标准协议头定义如下:


typedef struct tag_XEngine_ProtocolHdr
{
	XSHOT wHeader;                                                        //协议头头部 固定的赋值
	XNETHANDLE xhToken;                                                   //唯一标识符
	XUINT unOperatorType;                                                 //操作类型
	XUINT unOperatorCode;                                                 //操作码
	XUINT unPacketSize;                                                   //数据包大小,后续包的大小,不是长度,而是内存大小
	XBYTE byVersion;                                                      //协议版本
	XBYTE byIsReply;                                                      //是否需要回复包 0 否,1是
	XSHOT wReserve : 12;                                                  //自定义数据位或者保留
	XSHOT wCrypto : 4;                                                    //加解密标志位
	XSHOT wPacketSerial;                                                  //包序列号
	XSHOT wTail;                                                          //协议头尾部 固定的赋值
}XENGINE_PROTOCOLHDR, * LPXENGINE_PROTOCOLHDR;

      按照1字节对齐.里面的意思可以参考协议文档,里面有详细的说明.其中最重要的就是wHeader,wTail用于区分协议头,unPacketSize,这个表示后续数据包负载大小,必须填充正确.否则将导致组包失败.

      我们的包处理模块支持很多功能,除了标注的组包和拆包功能,还有包超时,包个数限制,单队列模式管理以及多任务池模式,还支持CHUNK模式等等功能,大家可以自己探索

      现在,我们可以使用这个模块进行开发来处理网络沾包功能,我们的包模块不带网络收发,你可以自己实现网络收发或者使用我们的客户端,服务端模块来实现.极大的自由度也是我们的一个优势.使用我们的模块首先需要初始化,HelpComponents_Datas_Init,其中nPoolCount表示任务池个数,每个客户端对应的数据将根据任务池大小来分别丢给不通的任务池.初始化完毕后,可以通过HelpComponents_Datas_CreateEx来为一个客户端创建一个任务池.其中nPoolIndex的带下不能超过你设置的nPoolCount大小,也可以设置0自动,最后你就可以投递数据了:HelpComponents_Datas_PostEx.通过recv得到的数据包可以直接通过此函数投递,模块内部会自动帮你处理各种问题.然后,我们可以通过另外一个函数来处理数据包了.

      在处理数据包的时候,我们一般都会单独创建一个线程或者线程池,这是因为避免阻塞网络IO,阻塞网络IO会导致严重问题,很多三方库都会有这种现象.我们的代码大多数都会单独创建任务池来处理网络数据包,也就是网络IO和业务IO是分开处理的:

//任务池
THREADPOOL_PARAMENT** ppSt_ListParam;
BaseLib_Memory_Malloc((XPPPMEM)&ppSt_ListParam, st_ServiceConfig.st_XMax.nForwardThread, sizeof(THREADPOOL_PARAMENT));
for (int i = 0; i < st_ServiceConfig.st_XMax.nForwardThread; i++)
{
	int* pInt_Pos = new int;

	*pInt_Pos = i;
	ppSt_ListParam[i]->lParam = pInt_Pos;
	ppSt_ListParam[i]->fpCall_ThreadsTask = XEngine_Forward_Thread;
}
xhForwardPool = ManagePool_Thread_NQCreate(&ppSt_ListParam, st_ServiceConfig.st_XMax.nForwardThread);
if (NULL == xhForwardPool)
{
	XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_ERROR, _X("启动Forward线程池服务失败,错误:%lX"), ManagePool_GetLastError());
	goto XENGINE_SERVICEAPP_EXIT;
}
XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("启动服务中,启动Forward线程池服务成功,启动个数:%d"), st_ServiceConfig.st_XMax.nForwardThread);

      然后我们可以在线程里面处理我们的业务了
 


XHTHREAD CALLBACK XEngine_Forward_Thread(XPVOID lParam)
{
	int nPoolIndex = *(int*)lParam;
	int nThreadPos = nPoolIndex + 1;

	while (bIsRun)
	{
		if (!HelpComponents_Datas_WaitEventEx(xhForwardPacket, nThreadPos))
		{
			continue;
		}
		int nListCount = 0;
		XENGINE_MANAGEPOOL_TASKEVENT** ppSt_ListClient;
		HelpComponents_Datas_GetPoolEx(xhForwardPacket, nThreadPos, &ppSt_ListClient, &nListCount);
		for (int i = 0; i < nListCount; i++)
		{
			int nMsgLen = 0;
			XCHAR* ptszMsgBuffer = NULL;
			XENGINE_PROTOCOLHDR st_ProtocolHdr;
			memset(&st_ProtocolHdr, '\0', sizeof(XENGINE_PROTOCOLHDR));

			if (HelpComponents_Datas_GetMemoryEx(xhForwardPacket, ppSt_ListClient[i]->tszClientAddr, &ptszMsgBuffer, &nMsgLen, &st_ProtocolHdr))
			{
				XEngine_Forward_Handle(ppSt_ListClient[i]->tszClientAddr, ptszMsgBuffer, nMsgLen, &st_ProtocolHdr);
				BaseLib_Memory_FreeCStyle((XPPMEM)&ptszMsgBuffer);
			}
		}
		BaseLib_Memory_Free((XPPPMEM)&ppSt_ListClient, nListCount);
	}
	return 0;
}

      nThreadPos是因为任务池的索引是从1开始的.我们可以通过代码HelpComponents_Datas_WaitEventEx来等待一个指定的线程任务池包组包完毕,这种方法可以避免线程空转.造成资源浪费.当有一个数据包是一个完整的包后,会触发此事件,我们继续通过HelpComponents_Datas_GetPoolEx来得到这个任务线程有多少个客户端的包组好报了.然后通过一个for循环处理HelpComponents_Datas_GetMemoryEx可以获得完整的数据包.这种方法可以避免内存二次拷贝.得到后通过我们的自定义函数XEngine_Forward_Handle来处理,我们想要做什么都是在这里面实现的.比如在这个函数处理用户验证登录:​​​​​​​
 

bool XEngine_Forward_Handle(LPCXSTR lpszClientAddr, LPCXSTR lpszMsgBuffer, int nMsgLen, XENGINE_PROTOCOLHDR* pSt_ProtocolHdr)
{
	int nSDLen = 10240;
	XCHAR tszSDBuffer[10240];
	memset(tszSDBuffer, '\0', sizeof(tszSDBuffer));
	//处理验证协议
	if (ENUM_XENGINE_COMMUNICATION_PROTOCOL_TYPE_AUTH == pSt_ProtocolHdr->unOperatorType)
	{
		if (XENGINE_COMMUNICATION_PROTOCOL_OPERATOR_CODE_FORWARD_LOGREQ == pSt_ProtocolHdr->unOperatorCode)
		{
			XENGINE_PROTOCOL_USERAUTH st_UserAuth;
			memset(&st_UserAuth, '\0', sizeof(XENGINE_PROTOCOL_USERAUTH));

			memcpy(&st_UserAuth, lpszMsgBuffer, sizeof(XENGINE_PROTOCOL_USERAUTH));
			pSt_ProtocolHdr->wReserve = 0;
			pSt_ProtocolHdr->unPacketSize = 0;
			pSt_ProtocolHdr->unOperatorCode = XENGINE_COMMUNICATION_PROTOCOL_OPERATOR_CODE_FORWARD_LOGREP;
			ModuleSession_Forward_Insert(lpszClientAddr, &st_UserAuth);
			XEngine_Network_Send(lpszClientAddr, (LPCXSTR)pSt_ProtocolHdr, sizeof(XENGINE_PROTOCOLHDR), XENGINE_CLIENT_NETTYPE_FORWARD);
			XLOG_PRINT(xhLog, XENGINE_HELPCOMPONENTS_XLOG_IN_LOGLEVEL_INFO, _X("Forward客户端:%s,设置的用户:%s,登录到服务器"), lpszClientAddr, st_UserAuth.tszUserName);
		}
	}
}

      这就是一个沾包处理流程,他为你解决了网络上面的各种沾包问题.让你无需关心网络底层问题,专注处理自己的业务代码.目前最强大的网络沾包处理模块.希望能帮到大家

      注意:UDP是包模式,不需要处理沾包

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xengine-qyt

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值