相信大家在学习和使用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是包模式,不需要处理沾包