FFXI-darkstar源码阅读

来源:互联网 发布:科勒淘宝授权 编辑:程序博客网 时间:2024/06/03 17:31
  Darkstar是最终幻想11的开源模拟服务器,项目托管于github平台:地址https://github.com/DarkstarProject/darkstar。相比wow的开源服务器源码,总体上来讲代码实现还是比较简单的,适合入门。

    darkStar总共有三个进程,分别对应查找服务器,登录服务器和地图服务器。darkStarFFXI最终幻想11的模拟服务器,它的游戏逻辑主要采用lua编写。其他相关的低层连接之类工作的有使用zmq进行的,也有直接使用低层套接字进行的。

1. search服务器

    首先是search服务器,它是直接使用tcp socket进行通信的。它为每个连接请求都开了一个线程。这里要注意的一点就是它开线程的方式是使用std标准库的std::thread方法来创建的线程。该方法由c++11提供了支持,c99是不行的。这里一个线程就是一个连接,通过每个线程单独的socket fd和客户端通信。这里值得注意的是,它对于每一个收到的报文会进行blowfish解密。当然,客户端发送数据时,自然也是进行了blowfish加密。

int32 CTCPRequestPacket::ReceiveFromSocket()

{

    int8recvbuf[DEFAULT_BUFLEN];

 

    m_size =recv(*m_socket, recvbuf, DEFAULT_BUFLEN, 0);//recv函数

    if (m_size== -1)

    {

#ifdef WIN32

       ShowError(CL_RED"recv failed with error: %d\n" CL_RESET,WSAGetLastError());

#else

       ShowError(CL_RED"recv failed with error: %d\n" CL_RESET,errno);

#endif

        return0;

    }

    if (m_size== 0)

    {

       //ShowError("TCP Connection closing...\n");

        return0;

    }

    if (m_size!= RBUFW(recvbuf, (0x00)) || m_size < 28)   {

       ShowError(CL_RED"Search packetsize wrong. Size %d should be%d.\n" CL_RESET, m_size, RBUFW(recvbuf, (0x00)));

        return0;

    }

    delete[]m_data;

    m_data = newuint8[m_size];

 

   memcpy(&m_data[0], &recvbuf[0], m_size);

    WBUFL(key,(16)) = RBUFL(m_data, (m_size - 4));

 

    returndecipher();//blowfish解密

}

int32 CTCPRequestPacket::decipher()//报文数据解密

{

    //key :由该类初始化时,已经得知md5函数,主要是为了得到hash

   md5((uint8*)(key), blowfish.hash, 20);

 

   blowfish_init((int8*)blowfish.hash, 16, blowfish.P, blowfish.S[0]);

 

    uint8 tmp =(m_size - 12) / 4;

    tmp -= tmp %2;

 

    for (uint8 i= 0; i < tmp; i += 2)

    {

       blowfish_decipher((uint32*)m_data + i + 2, (uint32*)m_data + i + 3,blowfish.P, blowfish.S[0]);

    }

    WBUFL(key,(20)) = RBUFL(m_data, (m_size - 0x18));

 

    returnCheckPacketHash();

}

decipher函数首先通过,CTCPRequestPacket类创建时设置的key来通过MD5,加密得到blowfish加解密算法要使用的key。然后通过blowfish解密。

    还有就是它自己实现了一个全局单例的任务管理器:CTaskMgr。主要是基于以下函数的循环:

uint32 CTaskMgr::DoTimer(uint32 tick)

{

    int32 diff =1000;

 

    while(!m_TaskList.empty() )

    {

        CTask *PTask = m_TaskList.top();

        diff =PTask->m_tick - tick;

 

        if( diff> 0 ) break; // no more expired timers to process

 

        m_TaskList.pop();

 

        if( PTask->m_func)

        {

            PTask->m_func((diff < -1000 ? tick : PTask->m_tick),PTask);

        }

 

        switch(PTask->m_type )

        {

            caseTASK_INTERVAL:

            {

                PTask->m_tick= PTask->m_interval + (diff < - 1000 ? tick : PTask->m_tick);

                m_TaskList.push(PTask);

            }

                break;

            caseTASK_ONCE:

            caseTASK_REMOVE:

            default:

            {

                deletePTask; // suppose that all tasks were allocated by new

            }

                break;

        }

        diff =dsp_cap(diff, 50, 1000);

    }

    return diff;

}

Search服务器主要是起了个任务管理线程,用来完成任务。

std::thread(TaskManagerThread).detach();

void TaskManagerThread()

{

    int next;

    while (true)

    {

        next =CTaskMgr::getInstance()->DoTimer(gettick_nocache());

       std::this_thread::sleep_for(std::chrono::milliseconds(next / 1000));

    }

}

2.login服务器

这里从代码上来看,login服务器和map服务器都是公用了kernel.cpp的代码。就是说login服务器和map服务器整体流程其实是一样的:

   

    do_init(argc,argv);

    fd_set rfd;

    {// Mainruntime cycle

        int next;

 

        while(runflag)

        {

            next= CTaskMgr::getInstance()->DoTimer(gettick_nocache());

            do_sockets(&rfd,next);

        }

    }

 

do_final(EXIT_SUCCESS);

 

先看login服务器,首先是:

    login_fd           =makeListenBind_tcp(login_config.uiLoginAuthIp,login_config.usLoginAuthPort,connect_client_login);

    ShowStatus("The login-server-auth is" CL_GREEN"ready" CL_RESET" (Server is listening on theport %u).\n\n", login_config.usLoginAuthPort);

    login_lobbydata_fd =makeListenBind_tcp(login_config.uiLobbyDataIp,login_config.usLobbyDataPort,connect_client_lobbydata);

    ShowStatus("The login-server-lobbydatais " CL_GREEN"ready" CL_RESET" (Server is listening on theport %u).\n\n", login_config.usLobbyDataPort);

    login_lobbyview_fd =makeListenBind_tcp(login_config.uiLobbyViewIp,login_config.usLobbyViewPort,connect_client_lobbyview);

    ShowStatus("The login-server-lobbyviewis " CL_GREEN"ready" CL_RESET" (Server is listening on theport %u).\n\n", login_config.usLobbyViewPort);

开了三条tcp连接,用于登录验证,大厅数据传输,大厅数据展示等等。这里在makeListenBind_tcp函数中,login服务器会缓存所有已连接的socket fd到全局的session数组中,该数组会存储该fd收到数据时的回调函数。当收到fd时,会自动调用回调函数。当然它主要的实现原理实际上就是kernel.cpp中的while循环:

while (runflag)

        {

            next= CTaskMgr::getInstance()->DoTimer(gettick_nocache());

            do_sockets(&rfd,next);

        }

主要就是调用了do_sockets,在do_sockets调用select来复用socket fd

 

3.map服务器

该服务器大部分和login服务器类似,比较特别的一点就是它使用lua来实现的游戏逻辑,且通过zmq来进行通信。这里面在c++中调用lua函数是以后要专研的点。

原创粉丝点击