游戏服务器 的一些数据接收处理,处理的是接收和发送的数据包队列。
以客户端发送到逻辑服务器和逻辑服务器发送到数据服务器为例,
以一个手游服务器的代码为例,来看下底层的一些对网络数据接收的处理。
以逻辑服务器连接数据服务器(服务器之间的连接)为例。
1、网络线程例程
2、接收数据
(1)网络接收数据到接收缓存
(2)拷贝和粘包到处理缓存
3、发送数据
(1)发送缓存区数据到队列
(2)网络socket发送
1、网络线程例程
CDBManager这个是逻辑服务器作为客户端连接到数据服务器的自定义socket连接类,是个线程类,处理角色数据的存取档。
//例行函数
void CDBManager::SingleRun()//每次逻辑循环的例行逻辑处理函数
{
if (m_boStarted)
{
super::SingleRun();//自定义socket的例行处理(开始时连接过去,发送心跳包,收发数据)
ProcessLoadPlayerData();//处理加载角色数据(1、处理接收数据)
ProcessSavePlayerData();//处理保存角色数据(2、处理发送数据)
}
}
开始时连接过去,发送心跳包,收发数据
VOID CCustomWorkSocket::SingleRun()
{
//接收数据
if ( connected() )
ReadSocket();//网络接收数据到接收缓存区(m_pRecvBuffer->pBuffer接收缓存区,m_pRecvBuffer->nOffset是接收到的长度)
//处理接受到的数据包
if ( connected() )
ProcessRecvBuffers(m_pProcRecvBuffer);
//调用例行函数
OnRun();
//发送数据
if ( connected() && m_bSendData)
{
SendSocketBuffers();
}
}
2、接收数据
(1)网络接收数据到接收缓存
VOID CCustomWorkSocket::ReadSocket()
{
static const int OnceRecvSize = 4096;
int nError;
TICKCOUNT dwCurTick = _getTickCount();
while ( TRUE )
{
//增长接收缓冲区大小
if ( m_pRecvBuffer->nSize - m_pRecvBuffer->nOffset < OnceRecvSize * 2 )
{
size_t nPointer = m_pRecvBuffer->pPointer - m_pRecvBuffer->pBuffer;
m_pRecvBuffer->nSize += OnceRecvSize * 2;
m_pRecvBuffer->pBuffer = (char*)realloc(m_pRecvBuffer->pBuffer, m_pRecvBuffer->nSize);
m_pRecvBuffer->pPointer = m_pRecvBuffer->pBuffer + nPointer;
}
//从套接字读取数据
nError = recv(&m_pRecvBuffer->pBuffer[m_pRecvBuffer->nOffset], OnceRecvSize);
if ( nError <= 0 )
break;
m_pRecvBuffer->nOffset += nError;
m_pRecvBuffer->pBuffer[m_pRecvBuffer->nOffset] = 0;
m_dwMsgTick = dwCurTick;
}
}
套接字接收数据
INT CCustomSocket::recv(LPVOID buf, INT len, const INT flags)
{
int nErr = ::recv( m_nSocket, (char*)buf, len, flags );
if ( nErr == 0 )
{
close();
}
else if ( nErr < 0 )
{
if ( !m_boBlock )
{
nErr = WSAGetLastError();
if ( nErr != WSAEWOULDBLOCK )
{
SocketError( nErr );
nErr = -1;
}
else nErr = SOCKET_ERROR - 1;
}
}
return nErr;
}
(2)拷贝和粘包到处理缓存
//逻辑服务器处理存储接收的缓存数据
VOID CCustomSSTClientSocket::ProcessRecvBuffers(PDATABUFFER pDataBuffer)
{
//如果连接已断开则丢弃所有数据
if ( !connected() )
{
super::ProcessRecvBuffers(pDataBuffer);////对接受数据的默认处理是什么都不做,只清空数据
SwapRecvProcessBuffers();//交换接收和处理这两个缓冲包的队列
return;
}
if ( pDataBuffer->nOffset <= 0 )
{
SwapRecvProcessBuffers();
return;
}
INT_PTR dwRemainSize;
PNETPACKETHEADER pPackHdr;
char* pDataEnd = pDataBuffer->pBuffer + pDataBuffer->nOffset;
while ( TRUE )
{
dwRemainSize = (INT_PTR)(pDataEnd - pDataBuffer->pPointer);
//如果缓冲区中的剩余长度小于通信协议头的长度,则交换缓冲(继续粘包)并在以后继续进行处理
if ( dwRemainSize < (INT_PTR)sizeof(*pPackHdr) )
{
SwapRecvProcessBuffers();
break;
}
//只有找到包头才处理,否则就一直接收和粘包(这种做法可以防止发来的无效数据,后面还是要检查包长度的)
pPackHdr = (PNETPACKETHEADER)pDataBuffer->pPointer;
//检查包头标志是否有效,如果包头标志无效则需要遍历数据包查找包头
if ( pPackHdr->dwIdent != NETPACKETHEADER::NetPacketHeaderIdent )
{
char* sCurPtr = pDataBuffer->pPointer;
do
{
pDataBuffer->pPointer++;//一个个字节的检查,看是否有包头
pPackHdr = (PNETPACKETHEADER)pDataBuffer->pPointer;
//找到包头标记则终止查找
if ( pPackHdr->dwIdent == NETPACKETHEADER::NetPacketHeaderIdent )
break;
}
while (pDataBuffer->pPointer < pDataEnd - 1);
//如果无法查找到包头,则保留接收缓冲末尾的最后一个字符并交换接收/处理缓冲以便下次继续连接新接收的数据后重新查找包头
if ( pPackHdr->dwIdent != NETPACKETHEADER::NetPacketHeaderIdent )
{
SwapRecvProcessBuffers();
//找不到协议头标志,输出错误消息
//协议头标志使用来校验包是否是是开始的数据,只有是开始的数据才能开始处理
logError("来自服务器的数据包中没有包含有效的数据包头");
break;
}
//输出找到包头的消息
logWarn("在来自服务器的数据包中的包头前跳过了%d字节",
(int)(pDataBuffer->pPointer - sCurPtr));
}
//如果处理接收数据的缓冲中的剩余数据长度不足协议头中的数据长度,则交换缓冲并在下次继续处理
//不会处理不到包头指定长度 的数据
dwRemainSize -= sizeof(*pPackHdr);
if ( pPackHdr->wPacketSize > dwRemainSize )//包还是没到包的应有长度,就继续粘包
{
SwapRecvProcessBuffers();
break;
}
//将缓冲读取指针调整到下一个通信数据包的位置
pDataBuffer->pPointer += sizeof(*pPackHdr) + pPackHdr->wPacketSize;
//开始处理派送接到的数据
//将通信数据段保存在packet中
CDataPacketReader packet(pPackHdr + 1, pPackHdr->wPacketSize);
//分派数据包处理(对db发来的这个数据包,在派送后就是直接处理的了)
DispatchRecvPacket(packet);
}
}
拷贝数据并粘包
VOID CCustomWorkSocket::SwapRecvProcessBuffers()
{
INT_PTR dwSize;
//m_pRecvBuffer 是接收到的数据,m_pProcRecvBuffer是粘包后的数据,m_pProcRecvBuffer->nOffset 是数据包总长度
//数据包处理完毕
dwSize = (int)(m_pProcRecvBuffer->pPointer - m_pProcRecvBuffer->pBuffer);
if ( dwSize >= m_pProcRecvBuffer->nOffset )
{
PDATABUFFER pDataBuffer = m_pProcRecvBuffer;
m_pProcRecvBuffer = m_pRecvBuffer;
m_pRecvBuffer = pDataBuffer;
m_pProcRecvBuffer->pPointer = m_pProcRecvBuffer->pBuffer;
m_pRecvBuffer->pPointer = m_pRecvBuffer->pBuffer;
m_pRecvBuffer->nOffset = 0;
}
//有新的数据
else if ( m_pRecvBuffer->nOffset > 0 )
{
//将剩余数据移动到头部
dwSize = m_pProcRecvBuffer->nOffset - (INT_PTR)(m_pProcRecvBuffer->pPointer - m_pProcRecvBuffer->pBuffer);
if ( m_pProcRecvBuffer->pPointer > m_pProcRecvBuffer->pBuffer )
{
memcpy(m_pProcRecvBuffer->pBuffer, m_pProcRecvBuffer->pPointer, dwSize);
m_pProcRecvBuffer->nOffset = dwSize;
m_pProcRecvBuffer->pBuffer[m_pProcRecvBuffer->nOffset] = 0;
}
//拷贝新数据
if ( m_pProcRecvBuffer->nSize <= m_pRecvBuffer->nOffset + m_pProcRecvBuffer->nOffset )
{
m_pProcRecvBuffer->nSize += __max(8192, m_pRecvBuffer->nOffset + 1);
m_pProcRecvBuffer->pBuffer = (char*)realloc(m_pProcRecvBuffer->pBuffer, m_pProcRecvBuffer->nSize);
}
memcpy(&m_pProcRecvBuffer->pBuffer[m_pProcRecvBuffer->nOffset], m_pRecvBuffer->pBuffer, m_pRecvBuffer->nOffset);
m_pProcRecvBuffer->nOffset += m_pRecvBuffer->nOffset;
m_pProcRecvBuffer->pBuffer[m_pProcRecvBuffer->nOffset] = 0;
m_pProcRecvBuffer->pPointer = m_pProcRecvBuffer->pBuffer;
m_pRecvBuffer->nOffset = 0;
m_pRecvBuffer->pPointer = m_pRecvBuffer->pBuffer;
}
}
3、发送数据
(1)把追加数据队列的发送包添加到发送队列队列
逻辑线程提交包都是提交到追加数据队列,发送前会把追加队列里的数据包添加到发送队列,发送完之后就把发送队列的数据包放到空闲队列准备以后再次发送时使用
//数据buffer的发送(发送队列处理)
VOID CCustomWorkSocket::SendSocketBuffers()
{
if ( sendToSocket(*this) > 0 )
{
m_dwMsgTick = _getTickCount();//记录发送的时间
}
}
//从发送包队列里获取发送包并循环发送,直到连接关闭或者当前包没有发送完
//逻辑线程提交的发送包都是提交到发送队列里的追加队列,m_SendingPacketList.flush()会把追加队列里的数据包添加到发送队列(追加时需要加锁)
//网络发送完之后就到空闲队列准备以后再次发送时获取空的内存数据包(包的内存池)
size_t CSendPacketPool::sendToSocket(CCustomSocket& socket)
{
INT_PTR nCount, nAvalLength, nBytesWriten;
CDataPacket **pPacketList, *pPacket;
size_t nTotalSent = 0;
//提交追加到发送队列中的数据包(会加锁)
m_SendingPacketList.flush();
//循环发送数据包
pPacketList = m_SendingPacketList;
nCount = m_SendingPacketList.count();
for (; m_nSendingPacketIdx<nCount; ++m_nSendingPacketIdx)
{
pPacket = pPacketList[m_nSendingPacketIdx];
nAvalLength = pPacket->getAvaliableLength();
if (nAvalLength > 0)
{
//网络发送数据
nBytesWriten = socket.send(pPacket->getOffsetPtr(), (INT)nAvalLength);
if ( nBytesWriten <= 0 )
break;
}
else nBytesWriten = 0;
nTotalSent += nBytesWriten;
//该数据包中的数据是否已经发完
if (nBytesWriten >= nAvalLength)
{
//清空数据包的数据长度
pPacket->setLength(0);
}
else
{
//调整偏移到下次继续发送的位置(因为使用的是非阻塞socket发送,tcp发送要是网络阻塞就立刻返回)
pPacket->adjustOffset(nBytesWriten);
break;//发送时
}
}
//如果发送队列中的数据已经全部发送完毕,则将数据包全部移动到空闲数据包列表中
if ( m_nSendingPacketIdx >= nCount )
{
m_nSendingPacketIdx = 0;
m_FreeSendPackList.lock();
m_FreeSendPackList.addArray(m_SendingPacketList, m_SendingPacketList.count());
m_SendingPacketList.trunc(0);
m_FreeSendPackList.unlock();
}
return nTotalSent;
}
(2)网络socket发送
INT CCustomSocket::send(LPVOID buf, INT len, const INT flags)
{
int nRet, nErr;
char *ptr = (char*)buf;
nRet = 0;
while ( len > 0 )
{
nErr = ::send( m_nSocket, (char*)ptr, len, flags );
if ( nErr == 0 )
{
nRet = 0;
close();
break;
}
else if ( nErr < 0 )
{
if ( !m_boBlock )
{
nErr = WSAGetLastError();
if ( nErr != WSAEWOULDBLOCK )
{
nRet = SOCKET_ERROR;
SocketError( WSAGetLastError() );
}
else if ( nRet == 0 )
{
nRet = SOCKET_ERROR - 1;
}
}
break;
}
else
{
nRet += nErr;
ptr += nErr;
len -= nErr;
}
}
return nRet;
}