一 包头定义
#define
GET_PACKET_INDEX(a) ((a)>>24)
#define SET_PACKET_INDEX(a,index) ((a)=(((a)&0xffffff)+((index)<<24)))
#define GET_PACKET_LEN(a) ((a)&0xffffff)
#define SET_PACKET_LEN(a,len) ((a)=((a)&0xff000000)+(len))
// 消息头中包括:PacketID_t-2字节;UINT-4字节中高位一个字节为消息序列号,其余
// 三个字节为消息长度
// 通过GET_PACKET_INDEX和GET_PACKET_LEN宏,可以取得UINT数据里面的消息序列号和长度
// 通过SET_PACKET_INDEX和SET_PACKET_LEN宏,可以设置UINT数据里面的消息序列号和长度
#define PACKET_HEADER_SIZE (sizeof(PacketID_t)+sizeof(WORD)+sizeof(UINT))
#define SET_PACKET_INDEX(a,index) ((a)=(((a)&0xffffff)+((index)<<24)))
#define GET_PACKET_LEN(a) ((a)&0xffffff)
#define SET_PACKET_LEN(a,len) ((a)=((a)&0xff000000)+(len))
// 消息头中包括:PacketID_t-2字节;UINT-4字节中高位一个字节为消息序列号,其余
// 三个字节为消息长度
// 通过GET_PACKET_INDEX和GET_PACKET_LEN宏,可以取得UINT数据里面的消息序列号和长度
// 通过SET_PACKET_INDEX和SET_PACKET_LEN宏,可以设置UINT数据里面的消息序列号和长度
#define PACKET_HEADER_SIZE (sizeof(PacketID_t)+sizeof(WORD)+sizeof(UINT))
分三部分,第一是包ID,这个很重要,用于创建包;第二个不知道有何作用;第三个包的大小,客户端读取服务器发来的包时,会判断读取的大小,如果没有这个数字大,说明包还没传送玩,有些比较大的数据包,服务器可能要分几次发送
二 包 定义基类
class
Packet
{
public :
BYTE m_Index ;
BYTE m_Status ;
public :
Packet( ) ;
virtual ~ Packet( ) ;
virtual VOID CleanUp( ){} ;
virtual BOOL Read( SocketInputStream & iStream ) = 0 ;
virtual BOOL Write( SocketOutputStream & oStream ) const = 0 ;
// 返回值为:PACKET_EXE 中的内容;
// PACKET_EXE_ERROR 表示出现严重错误,当前连接需要被强制断开
// PACKET_EXE_BREAK 表示返回后剩下的消息将不在当前处理循环里处理
// PACKET_EXE_CONTINUE 表示继续在当前循环里执行剩下的消息
// PACKET_EXE_NOTREMOVE 表示继续在当前循环里执行剩下的消息,但是不回收当前消息
virtual UINT Execute( Player * pPlayer ) = 0 ;
virtual PacketID_t GetPacketID( ) const = 0 ;
virtual UINT GetPacketSize( ) const = 0 ;
virtual BOOL CheckPacket( ){ return TRUE ; }
BYTE GetPacketIndex( ) const { return m_Index ; } ;
VOID SetPacketIndex( BYTE Index ){ m_Index = Index ; } ;
BYTE GetPacketStatus( ) const { return m_Status ; } ;
VOID SetPacketStatus( BYTE Status ){ m_Status = Status ; } ;
};
{
public :
BYTE m_Index ;
BYTE m_Status ;
public :
Packet( ) ;
virtual ~ Packet( ) ;
virtual VOID CleanUp( ){} ;
virtual BOOL Read( SocketInputStream & iStream ) = 0 ;
virtual BOOL Write( SocketOutputStream & oStream ) const = 0 ;
// 返回值为:PACKET_EXE 中的内容;
// PACKET_EXE_ERROR 表示出现严重错误,当前连接需要被强制断开
// PACKET_EXE_BREAK 表示返回后剩下的消息将不在当前处理循环里处理
// PACKET_EXE_CONTINUE 表示继续在当前循环里执行剩下的消息
// PACKET_EXE_NOTREMOVE 表示继续在当前循环里执行剩下的消息,但是不回收当前消息
virtual UINT Execute( Player * pPlayer ) = 0 ;
virtual PacketID_t GetPacketID( ) const = 0 ;
virtual UINT GetPacketSize( ) const = 0 ;
virtual BOOL CheckPacket( ){ return TRUE ; }
BYTE GetPacketIndex( ) const { return m_Index ; } ;
VOID SetPacketIndex( BYTE Index ){ m_Index = Index ; } ;
BYTE GetPacketStatus( ) const { return m_Status ; } ;
VOID SetPacketStatus( BYTE Status ){ m_Status = Status ; } ;
};
定义了包的基类,其他包都继承它。其中比较重要的虚方法是Read,Write,Execute,分别执行读,写,包逻辑数据执行。
打算对第三个多讲一下。
服务器发送切换创建的包时,客户端收到之后,相应下面事件
uint
GCNotifyChangeSceneHandler::Execute( GCNotifyChangeScene
*
pPacket, Player
*
pPlayer )
{
__ENTER_FUNCTION
// AxTrace(0, 2, "GCNotifyChangeSceneHandle[%.1f,%.1f]r::Execute",
// pPacket->getTargetPos()->m_fX, pPacket->getTargetPos()->m_fZ);
// 当前流程是主流程
if (CGameProcedure::GetActiveProcedure() == (CGameProcedure * )CGameProcedure::s_pProcMain)
{
INT nSourceID = CWorldManager::GetMe() -> GetActiveScene() -> GetSceneDefine() -> nServerID;
if (nSourceID != pPacket -> getCurrentSceneID()) return PACKET_EXE_CONTINUE;
CWorldManager::GetMe() -> ChangeScene(
pPacket -> getTargetSceneID(),
fVector2(pPacket -> getTargetPos() -> m_fX, pPacket -> getTargetPos() -> m_fZ),
pPacket -> getTargetDir(),
pPacket -> getFlag());
}
return PACKET_EXE_CONTINUE ;
__LEAVE_FUNCTION
return PACKET_EXE_
{
__ENTER_FUNCTION
// AxTrace(0, 2, "GCNotifyChangeSceneHandle[%.1f,%.1f]r::Execute",
// pPacket->getTargetPos()->m_fX, pPacket->getTargetPos()->m_fZ);
// 当前流程是主流程
if (CGameProcedure::GetActiveProcedure() == (CGameProcedure * )CGameProcedure::s_pProcMain)
{
INT nSourceID = CWorldManager::GetMe() -> GetActiveScene() -> GetSceneDefine() -> nServerID;
if (nSourceID != pPacket -> getCurrentSceneID()) return PACKET_EXE_CONTINUE;
CWorldManager::GetMe() -> ChangeScene(
pPacket -> getTargetSceneID(),
fVector2(pPacket -> getTargetPos() -> m_fX, pPacket -> getTargetPos() -> m_fZ),
pPacket -> getTargetDir(),
pPacket -> getFlag());
}
return PACKET_EXE_CONTINUE ;
__LEAVE_FUNCTION
return PACKET_EXE_
ERROR ;
}
}
ChangeScene里面会再向服务端发一个包,告诉服务器要切换的场景。服务器收到消息之后,又会给客户端发送一个包,执行下面事件:
uint
GCEnterSceneHandler::Execute( GCEnterScene
*
pPacket, Player
*
pPlayer )
{
// AxTrace(0, 2, "GCEnterSceneHandler::Execute");
// 当前流程是登录流程
if (CGameProcedure::GetActiveProcedure() == (CGameProcedure * )CGameProcedure::s_pProcEnter)
{
// 允许进入
if (pPacket -> getReturn() == 0 )
{
// ------------------------------------------------------------------
// 保存自身数据
CGameProcedure::s_pVariableSystem -> SetAs_Int( " MySelf_ID " , (INT)pPacket -> getObjID());
CGameProcedure::s_pVariableSystem -> SetAs_Vector2( " MySelf_Pos " , pPacket -> getEnterPos().m_fX, pPacket -> getEnterPos().m_fZ);
// 是否是玩家城市
// -- for debug
BOOL bUserCityMode = pPacket -> getIsCity();
int nCitySceneID = pPacket -> getSceneID();
int nCityLevel = pPacket -> getCityLevel();
// -- for debug
// ------------------------------------------------------------------
// 设置要进入的场景
if (bUserCityMode)
{
CGameProcedure::s_pProcEnter -> SetSceneID(nCitySceneID, nCityLevel);
}
else
{
// 普通场景,第二个参数(城市等级)必须为-1
CGameProcedure::s_pProcEnter -> SetSceneID(pPacket -> getSceneID(), - 1 );
}
// 设置登录流程状态,使之进入下一个状态
CGameProcedure::s_pProcEnter -> SetStatus(CGamePro_Enter::ENTERSCENE_OK);
// 进入场景
CGameProcedure::s_pProcEnter -> EnterScene();
}
else
{
// 不允许进入
CGameProcedure::s_pProcEnter -> SetStatus(CGamePro_Enter::ENTERSCENE_FAILED );
CGameProcedure::s_pEventSystem -> PushEvent( GE_GAMELOGIN_SHOW_SYSTEM_INFO_CLOSE_NET, " 进入场景的请求被服务器拒绝 " );
}
}
return PACKET_EXE_CONTINUE ;
}
{
// AxTrace(0, 2, "GCEnterSceneHandler::Execute");
// 当前流程是登录流程
if (CGameProcedure::GetActiveProcedure() == (CGameProcedure * )CGameProcedure::s_pProcEnter)
{
// 允许进入
if (pPacket -> getReturn() == 0 )
{
// ------------------------------------------------------------------
// 保存自身数据
CGameProcedure::s_pVariableSystem -> SetAs_Int( " MySelf_ID " , (INT)pPacket -> getObjID());
CGameProcedure::s_pVariableSystem -> SetAs_Vector2( " MySelf_Pos " , pPacket -> getEnterPos().m_fX, pPacket -> getEnterPos().m_fZ);
// 是否是玩家城市
// -- for debug
BOOL bUserCityMode = pPacket -> getIsCity();
int nCitySceneID = pPacket -> getSceneID();
int nCityLevel = pPacket -> getCityLevel();
// -- for debug
// ------------------------------------------------------------------
// 设置要进入的场景
if (bUserCityMode)
{
CGameProcedure::s_pProcEnter -> SetSceneID(nCitySceneID, nCityLevel);
}
else
{
// 普通场景,第二个参数(城市等级)必须为-1
CGameProcedure::s_pProcEnter -> SetSceneID(pPacket -> getSceneID(), - 1 );
}
// 设置登录流程状态,使之进入下一个状态
CGameProcedure::s_pProcEnter -> SetStatus(CGamePro_Enter::ENTERSCENE_OK);
// 进入场景
CGameProcedure::s_pProcEnter -> EnterScene();
}
else
{
// 不允许进入
CGameProcedure::s_pProcEnter -> SetStatus(CGamePro_Enter::ENTERSCENE_FAILED );
CGameProcedure::s_pEventSystem -> PushEvent( GE_GAMELOGIN_SHOW_SYSTEM_INFO_CLOSE_NET, " 进入场景的请求被服务器拒绝 " );
}
}
return PACKET_EXE_CONTINUE ;
}
客户端收到这个包之后,就真正的切换场景了,包括消耗上个场景的资源,创建新场景资源等等
三 包的创建方式
包的创建是通过工厂模式创建的,基类是
class
PacketFactory
{
public :
virtual ~ PacketFactory () {}
virtual Packet * CreatePacket () = 0 ;
virtual PacketID_t GetPacketID () const = 0 ;
virtual UINT GetPacketMaxSize () const = 0 ;
};
{
public :
virtual ~ PacketFactory () {}
virtual Packet * CreatePacket () = 0 ;
virtual PacketID_t GetPacketID () const = 0 ;
virtual UINT GetPacketMaxSize () const = 0 ;
};
具体工厂定义
class
GCRetChangeSceneFactory :
public
PacketFactory
{
public :
Packet * CreatePacket() { return new GCRetChangeScene() ; }
PacketID_t GetPacketID() const { return PACKET_GC_RETCHANGESCENE ; }
UINT GetPacketMaxSize() const { return sizeof (BYTE) +
sizeof (CHAR) * IP_SIZE +
sizeof (WORD) +
sizeof (UINT) ; }
};
{
public :
Packet * CreatePacket() { return new GCRetChangeScene() ; }
PacketID_t GetPacketID() const { return PACKET_GC_RETCHANGESCENE ; }
UINT GetPacketMaxSize() const { return sizeof (BYTE) +
sizeof (CHAR) * IP_SIZE +
sizeof (WORD) +
sizeof (UINT) ; }
};
当然,还有一个工厂管理的类PacketFactoryManager,以一个二维数组保存了包ID,和包创建工厂;创建包时,传包ID,就可以获得具体的工厂,然后调用CreatePacket()创建包
class
PacketFactoryManager
{
public :
PacketFactoryManager( ) ;
~ PacketFactoryManager( ) ;
private :
VOID AddFactory( PacketFactory * pFactory ) ;
public :
// 外部调用通用接口
// 初始化接口
BOOL Init( ) ;
// 根据消息类型从内存里分配消息实体数据(允许多线程同时调用)
Packet * CreatePacket( PacketID_t packetID ) ;
// 根据消息类型取得对应消息的最大尺寸(允许多线程同时调用)
UINT GetPacketMaxSize( PacketID_t packetID ) ;
// 删除消息实体(允许多线程同时调用)
VOID RemovePacket( Packet * pPacket ) ;
VOID Lock( ){ m_Lock.Lock() ; } ;
VOID Unlock( ){ m_Lock.Unlock() ; } ;
private :
PacketFactory ** m_Factories ;
USHORT m_Size ;
MyLock m_Lock ;
public :
UINT * m_pPacketAllocCount ;
};
{
public :
PacketFactoryManager( ) ;
~ PacketFactoryManager( ) ;
private :
VOID AddFactory( PacketFactory * pFactory ) ;
public :
// 外部调用通用接口
// 初始化接口
BOOL Init( ) ;
// 根据消息类型从内存里分配消息实体数据(允许多线程同时调用)
Packet * CreatePacket( PacketID_t packetID ) ;
// 根据消息类型取得对应消息的最大尺寸(允许多线程同时调用)
UINT GetPacketMaxSize( PacketID_t packetID ) ;
// 删除消息实体(允许多线程同时调用)
VOID RemovePacket( Packet * pPacket ) ;
VOID Lock( ){ m_Lock.Lock() ; } ;
VOID Unlock( ){ m_Lock.Unlock() ; } ;
private :
PacketFactory ** m_Factories ;
USHORT m_Size ;
MyLock m_Lock ;
public :
UINT * m_pPacketAllocCount ;
};