服务器的异常处理包括的内定w常广泛,本文仅就在网l封包方面出现的异常作一讨论Q希望能Ҏ(gu)从事相关工作的朋友有所帮助?/p>
关于|络包斚w的异常,M来说Q可以分Z大类Q一是封包格式出现异常;二是包内容Q即包数据Q出现异常。在包格式的异常处理方面,我们在最 底端的网l数据包接收模块便可以加以处理。而对于封包数据内容出现的异常Q只有依靠游戏本w的逻辑d以判定和验。游戏逻辑斚w的异常处理,是随每个? 戏的不同而不同的Q所以,本文随后的内容将重点阐述在网l数据包接收模块中的异常处理?/p>
为方便以下的讨论Q先明确两个概念Q这两个概念是ؓ了叙q方面,W者自行取的,q无标准可言Q:
1、逻辑包:指的是在应用层提交的数据包,一个完整的逻辑包可以表CZ个确切的逻辑意义。比如登录包Q它里面可以含有用户名字段和密码字Dc尽它看上M是一D늼冲区数据Q但q个~冲区里的各个区间是代表一定的逻辑意义的?br> 2、物理包Q指的是使用recv(recvfrom)或wsarecv(wsarecvfrom)从网l底层接收到的数据包Q这h到的一个数据包Q能不能表示一个完整的逻辑意义Q要取决于它是通过UDPcȝ“数据报协?#8221;发的包还是通过TCPcȝ“协?#8221;发的包?/p>
我们知道QTCP是流协议Q?#8220;协?#8221;?#8220;数据报协?#8221;的不同点在于Q?#8220;数据报协?#8221;中的一个网l包本n是一个完整的逻辑包,也就是说Q在应用层? sendto发送了一个逻辑包之后,在接收端通过recvfrom接收到的是刚才使用sendto发送的那个逻辑包,q个包不会被分开发送,也不会与? 它的包放在一起发送。但对于TCP而言QTCP会根据网l状况和neagle法Q或者将一个逻辑包单独发送,或者将一个逻辑包分成若q次发送,或者会? 若干个逻辑包合在一起发送出厅R正因ؓTCP在逻辑包处理方面的q种_合性,要求我们在作ZTCP的应用时Q一般都要编写相应的拼包、解包代码?/p>
因此Q基于TCP的上层应用,一般都要定义自q包格式。TCP的封包定义中Q除了具体的数据内容所代表的逻辑意义之外Q第一步就是要定以何U方式表C当前包的开始和l束。通常情况下,表示一个TCP逻辑包的开始和l束有两U方式:
1、以Ҏ(gu)的开始和l束标志表示Q比如FF00表示开始,00FF表示l束?br> 2、直接以包长度来表示。比如可以用W一个字节表C包总长度,如果觉得q样的话包比较小Q也可以用两个字节表C包长度?/p>
下面要l出的代码是以第2U方式定义的数据包,包长度以每个包的前两个字节表示。我结合着代码l出相关的解释和说明?/p>
函数中用到的变量说明Q?/p>
CLIENT_BUFFER_SIZEQ缓冲区的长度,定义为:Const int CLIENT_BUFFER_SIZE=4096?br> m_ClientDataBufQ数据整理缓冲区Q每ơ收到的数据Q都会先被复制到q个~冲区的末尾Q然后由下面的整理函数对q个~冲行整理。它的定义是Qchar m_ClientDataBuf[2* CLIENT_BUFFER_SIZE]?br> m_DataBufByteCountQ数据整理缓冲区中当前剩余的未整理字节数?br> GetPacketLen(const char*)Q函敎ͼ可以Ҏ(gu)传入的缓冲区首址按照应用层协议取出当前逻辑包的长度?br> GetGamePacket(const char*, int)Q函敎ͼ可以Ҏ(gu)传入的缓冲区生成相应的游戏逻辑数据包?br> AddToExeList(PBaseGamePacket)Q函敎ͼ指定的游戏逻辑数据包加入待处理的游戏逻辑数据包队列中Q等待逻辑处理U程对其q行处理?br> DATA_POSQ指的是除了包长度、包cd{这些标志型字段之外Q真正的数据包内容的起始位置?/p>
Bool SplitFun(const char* pData,const int &len)
{
PBaseGamePacket pGamePacket=NULL;
__int64 startPos=0, prePos=0, i=0;
int packetLen=0;
//先将本次收到的数据复制到整理~冲区尾?br> startPos = m_DataBufByteCount;
memcpy( m_ClientDataBuf+startPos, pData, len );
m_DataBufByteCount += len;
//当整理缓冲区内的字节数少于DATA_POS字节Ӟ取不到长度信息则退?br> //注意Q退出时q不|m_DataBufByteCount?
if (m_DataBufByteCount < DATA_POS+1)
return false;
//Ҏ(gu)正常逻辑Q下面的情况不可能出玎ͼ为稳妥v见,q是加上
if (m_DataBufByteCount > 2*CLIENT_BUFFER_SIZE)
{
//讄m_DataBufByteCount?Q意味着丢弃~冲Z的现有数?br> m_DataBufByteCount = 0;
//可以考虑开N误格式数据包的处理接口,处理逻辑交给上层
//OnPacketError()
return false;
}
//q原起始指针
startPos = 0;
//只有当m_ClientDataBuf中的字节个数大于最包长度时才能执行此语句
packetLen = GetPacketLen( pIOCPClient->m_ClientDataBuf );
//当逻辑层的包长度不合法Ӟ则直接丢弃该?br> if ((packetLen < DATA_POS+1) || (packetLen > 2*CLIENT_BUFFER_SIZE))
{
m_DataBufByteCount = 0;
//OnPacketError()
return false;
}
//保留整理~冲区的末尾指针
__int64 oldlen = m_DataBufByteCount;
while ((packetLen <= m_DataBufByteCount) && (m_DataBufByteCount>0))
{
//调用拼包逻辑Q获取该~冲区数据对应的数据?br> pGamePacket = GetGamePacket(m_ClientDataBuf+startPos, packetLen);
if (pGamePacket!=NULL)
{
//数据包加入执行队列
AddToExeList(pGamePacket);
}
pGamePacket = NULL;
//整理~冲区的剩余字节数和新逻辑包的起始位置q行调整
m_DataBufByteCount -= packetLen;
startPos += packetLen;
//D留~冲区的字节数少于一个正常包大小Ӟ只向前复制该包随后退?br> if (m_DataBufByteCount < DATA_POS+1)
{
for(i=startPos; i<startPos+m_DataBufByteCount; ++i)
m_ClientDataBuf[i-startPos] = m_ClientDataBuf[i];
return true;
}
packetLen = GetPacketLen(m_ClientDataBuf + startPos );
//当逻辑层的包长度不合法Ӟ丢弃该包及缓冲区以后的包
if ((packetLen<DATA_POS+1) || (packetLen>2*CLIENT_BUFFER_SIZE))
{
m_DataBufByteCount = 0;
//OnPacketError()
return false;
}
if (startPos+packetLen>=oldlen)
{
for(i=startPos; i<startPos+m_DataBufByteCount; ++i)
m_ClientDataBuf[i-startPos] = m_ClientDataBuf[i];
return true;
}
}//取所有完整的?/p>
return true;
}
以上便是数据接收模块的处理函敎ͼ下面是几点简要说明:
1、用于拼包整理的~冲?m_ClientDataBuf)应该比recv中指定的接收~冲?pData)长度(CLIENT_BUFFER_SIZE)要大Q通常前者是后者的2?2*CLIENT_BUFFER_SIZE)或更大?br>
2、ؓ避免因ؓ剩余数据前移而导致的额外开销Q徏议m_ClientDataBuf使用环Ş~冲区实现?br>
3、ؓ了避免出现无法拼装的包,我们U定每次发送的逻辑包,其单个逻辑包最大长度不可以过CLIENT_BUFFER_SIZE?倍。因为我们的?
理缓冲区只有2*CLIENT_BUFFER_SIZEq么长,更长的数据,我们无法整理。这p求在协议的设计上以及最l的发送函数的处理上要加上q?
L异常处理机制?/p>
4、对于数据包q短或过长的包,我们通常的情冉||m_DataBufByteCount?Q即舍弃当前
包的处理。如果此处不讄m_DataBufByteCount?也可Q但该客L只要发了一ơ格式错误的包,则其后发过来的包则也将q带着产生格式
错误Q如果设|m_DataBufByteCount?Q则可以比较好的避免后的包受此包的格式错误影响。更好的作法是,在此处开放一个封包格式异?
的处理接?OnPacketError)Q由上层逻辑军_对这U异常如何处|。比如上层逻辑可以对封包格式方面出现的异常q行计数Q如果错误的ơ数过
一定的|则可以断开该客L的连接?/p>
5、徏议不要在recv或wsarecv的函数后Q就紧接着作以上的处理。当recv收到一D|
据后Q生成一个结构体或对?它主要含有data和len两个内容Q前者是数据~冲区,后者是数据长度)Q将q样的一个结构体或对象放C个队列中由后?
的线E对其用SplitFun函数q行整理。这P可以最大限度地提高|络数据的接攉度Q不臛_为数据整理的原因而在此处费旉?img src ="http://www.aygfsteel.com/kuxiaoku/aggbug/109154.html" width = "1" height = "1" />