天龙源码分析 - 客户端 包

一 包头定义

 

#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))

 

 

分三部分,第一是包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 ; } ;
};

 

 

定义了包的基类,其他包都继承它。其中比较重要的虚方法是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_
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 ;
}

 

 

客户端收到这个包之后,就真正的切换场景了,包括消耗上个场景的资源,创建新场景资源等等

 

三 包的创建方式

包的创建是通过工厂模式创建的,基类是

class  PacketFactory 
{
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) ; }
};

 

 

当然,还有一个工厂管理的类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 ;

};

 

转载于:https://www.cnblogs.com/lancidie/archive/2011/04/08/2009113.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值