zz传奇源码分析-服务器端

LoginGate服务器

服务器端:

1.首先从LoginGate.cpp WinMain分析:

    1) CheckAvailableIOCP : 检查是不是NT,2000的系统(IOCP)

    2) InitInstance: 初始化界面,加载WSAStartup

3)  MainWndProc窗口回调函数.

2.MainWndProc.CPP中分析回调函数MainWndProc

switch (nMsg)

    {

        case _IDM_CLIENTSOCK_MSG:

        case WM_COMMAND:

        case WM_CLOSE:

    g_ssock Local    7000 游戏登陆端口

g_csock Remote   5000 发送到logsrv服务器上的套接字

1)_IDM_CLIENTSOCK_MSG 消息:处理与logsrv回调通讯事件。

调用:OnClientSockMsg,该函数是一个回调函数:

             当启动服务之后,ConnectToServer函数将(_IDM_CLIENTSOCK_MSG消息 FD_CONNECT|FD_READ|FD_CLOSE)传入WSAAsyncSelect函数。在与hWnd窗口句柄对应的窗口例程中以Windows消息的形式接收网络事件通知。函数OnClientSockMsg,主要完成与logsrv服务器之间的通信(心跳,转发客户端数据包等)

switch (WSAGETSELECTEVENT(lParam))

   {

       case FD_CONNECT:

       case FD_CLOSE:

       case FD_READ:

FD_CONNECT:(重新连接情况)

  A. CheckSocketError返回正常时:

a). ConnectToServer函数首先在服务启动的时候执行一次。回调

FD_CONNECT

   b).连接logsrv时,开启ThreadFuncForMsg线程,把从客户端发送的数据(g_xMsgQueue, FD_READ事件读到的logSrv服务器发来的数据) 投递I/O,利用IOCP模型,发送到客户端SleepEx挂起线程。至到一个I/O 完成回调函数被调用。 一个异步过程调用排队到此线程。

ThreadFuncForMsg线程检测(从logSrv收到的g_xMsgQueue数据包-心跳,处理包)。i/o 投递,利用IOCP发送给客户端。

        if (nSocket = AnsiStrToVal(pszFirst + 1)) //得到socket

       WSASend((SOCKET)nSocket, &Buf, 1, &dwSendBytes, 0, NULL,

c).终止定时器_ID_TIMER_CONNECTSERVER

KillTimer(g_hMainWnd, _ID_TIMER_CONNECTSERVER);

d).设置_ID_TIMER_KEEPALIVE定时器 (心跳数据包)

SetTimer(g_hMainWnd, _ID_TIMER_KEEPALIVE

     调用定时器回调函数OnTimerProc: 定时发关心跳数据包到logsrv服务器。SendExToServer(PACKET_KEEPALIVE);

B. 如果socket断开,设置_ID_TIMER_CONNECTSERVER定时器

ConnectToServer尝试重新连接服务器。

                   _ID_TIMER_CONNECTSERVER, (TIMERPROC)OnTimerProc);

              FD_CLOSE:

                     断开与logsrv服务器SOCKET连接,OnCommand(IDM_STOPSERVICE, 0); 回调函数处理IDM_STOPSERVICE。

              FD_READ:

                     接收logsrv服务器发送的数据包(心跳,登陆验证,selCur服务器地址),把数据加入缓冲区(g_xMsgQueue)中。

2)WM_COMMAND:

          IDM_STARTSERVICE: 启动服务(IOCP模型Server响应客户端请求)

          IDM_STOPSERVICE: 停止服务(IOCP模型Server)

    3)WM_CLOSE:

          IDM_STOPSERVICE: 停止服务(IOCP模型Server)

          WSACleanup();

PostQuitMessage(0); //WM_DESTROY消息

IDM_STARTSERVICE: 启动服务(IOCP模型Server响应客户端请求)

InitServerSocket:函数:

1) AcceptThread线程:

      Accept之后生成一个CSessionInfo对象,pNewUserInfo->sock = Accept; 客户端Socket值赋值给结构体。记录客户相关信息。

新的套接字句柄用CreateIoCompletionPort关联到完成端口,然后发出一个异步的WSASend或者WSARecv调用(pNewUserInfo->Recv();接收客户端消息),因为是异步函数,WSASend/WSARecv会马上返回,实际的发送或者接收数据的操作由WINDOWS系统去做。然后把CSessionInfo对象加入g_xSessionList中。向logsrv服务器发送用户Session信息。打包规则‘%0socket/ip$/0’         

在客户accept之后,总投递一个I/O(recv),然后把相应的数据发往logsrv服务器。

   2) CreateIOCPWorkerThread函数:

          调用CreateIoCompletionPort 并根据处理器数量,创建一个或多个ServerWorkerThread线程。

ServerWorkerThread线程工作原理:

循环调用GetQueuedCompletionStatus()函数来得到IO操作结果。阻塞函数。当WINDOWS系统完成WSASend或者WSArecv的操作,把结果发到完成端口。GetQueuedCompletionStatus()马上返回,并从完成端口取得刚完成的WSASend/WSARecv的结果。然后接着发出WSASend/WSARecv,并继续下一次循环阻塞在GetQueuedCompletionStatus()这里。

a). pSessionInfo为空或者dwBytesTransferred =0 ,在客户端close socket,发相应数据包(异常)到logsrv服务器(X命令-数据包),关闭客户端套按字。

         b). while ( pSessionInfo->HasCompletionPacket() ) 如果数据验证正确,就转发数据包(A命令-数据包) logsrv服务器。

 c). if (pSessionInfo->Recv() 继续投递I/O操作。

 总结:

我们不停地发出异步的WSASend/WSARecv IO操作,具体的IO处理过程由WINDOWS系统完成,WINDOWS系统完成实际的IO处理后,把结果送到完成端口上(如果有多个IO都完成了,那么就在完成端口那里排成一个队列)。我们在另外一个线程里从完成端口不断地取出IO操作结果,然后根据需要再发出WSASend/WSARecv IO操作。

IDM_STOPSERVICE: 停止服务(IOCP模型Server响应客户端请求)

   Close -> OnCommand(IDM_STOPSERVICE, 0L); ->g_fTerminated = TRUE; 线程退出。

    if (g_hAcceptThread != INVALID_HANDLE_VALUE)

    {

        TerminateThread(g_hAcceptThread, 0);

         WaitForSingleObject(g_hAcceptThread, INFINITE); //IOCP的Accept线程

         CloseHandle(g_hAcceptThread);

         g_hAcceptThread = INVALID_HANDLE_VALUE;

     }

     if (g_hMsgThread != INVALID_HANDLE_VALUE)

     {

         TerminateThread(g_hMsgThread, 0); //窗口例程网络事件回调线程

         WaitForSingleObject(g_hMsgThread, INFINITE);

         CloseHandle(g_hMsgThread);

         g_hMsgThread = INVALID_HANDLE_VALUE;

     }

     ClearSocket(g_ssock);

     ClearSocket(g_csock);

     CloseHandle(g_hIOCP);

总结:

LoginGate(登录网关服务器),接受客户端连接,并且把用户ID,密码直接发送到LoginSvr服务器中,由LoginSrv服务器验证之后,发送数据包返回给客户端。LoginGate之间是通过定时器,定时发送“心跳”数据。验证服务器存活的。客户端与服务器端的数据在传输中,是进行过加密的。

向loginSrv发送‘%A’+Msg+‘$0’消息: 转发客户端消息。

                 ‘%X’+Msg+‘$0’消息: 发送用户连接消息,增加到用户列表。

                 ‘%O’+Msg+‘$0’消息: 发送用户上线消息。

主要流程:

服务启动后,LoginGate启动了AcceptThread,和ServerWorkerThread线程,AcceptThread线程接收客户端连接,并把session信息发送给loginSrv服务器,ServerWorkerThread线程从完成端口取得刚完成的WSASend/WSARecv的结果后,把客户端数据转发给loginSrv服务器。服务启动时,WSAAsyncSelect模型连接到loginSrv服务器中。一旦连接成功,就启动ThreadFuncForMsg线程,该线程从g_xMsgQueue(FD_READ事件读到的loginSrv服务器发来的数据)中取出loginSrv服务器处理过的数据。投递I/O,利用IOCP模型,发送到客户端。

ServerWorkerThread转发客户端数据 -> WSAAsyncSelect的Read读loginSrv处理后返回的数据-> ThreadFuncForMsg线程,投递WSASend消息,由Windows处理(IOCP),发送数据给客户端。

LoginSvr服务器

g_gcSock Local    5500端口

1.首先从LoginSvr.cpp  WinMain分析:

   1) CheckAvailableIOCP : 检查是不是NT,2000的系统(IOCP)

   2) InitInstance: 初始化界面,加载WSAStartup

       GetDBManager()->Init( InsertLogMsg, "Mir2_Account", "sa", "prg" );

       数据库管理类,做底层数据库操作。

3) MainWndProc窗口回调函数OnCommand:

IDM_STARTSERVICE:

创建LoadAccountRecords线程

a). UPDATE TBL_ACCOUNT重置帐户验证状态。

b). 读服务器列表(TBL_SERVERINFO, selGate服务器),加入g_xGameServerList

遍历xGameServerList列表,把服务器信息加入到一个字符数组g_szServerList中。

c). 启动InitServerThreadForMsg线程。

          d). 调用InitServerSocket函数创建两个线程:

           AcceptThread线程:

           ServerWorkerThread线程:

调用InitServerSocket函数创建两个线程:

    1) AcceptThread线程:

Accept之后生成一个CGateInfo对象,CGateInfo->sock = Accept; 客户端Socket值赋值给结构体。记录客户相关信息。新的套接字句柄用CreateIoCompletionPort关联到完成端口,然后发出一个异步的WSASend或者WSARecv调用(pNewUserInfo->Recv();接收客户端消息),因为是异步函数,WSASend/WSARecv会马上返回,实际的发送或者接收数据的操作由WINDOWS系统去做。然后把CGateInfo对象加入g_xGateList中。在客户accept之后,投递一个I/O(recv)。

分析一下g_xGateList发现,每个CGateInfo里有sock; xUserInfoList,g_SendToGateQ,该网关的相关信息依次(网关对应的sock, 用户列列信息,消息队列),可以为多个LoginGate登录网关服务。

2) ServerWorkerThread线程:

ServerWorkerThread线程工作原理:

循环调用GetQueuedCompletionStatus()函数来得到IO操作结果。阻塞函数。当WINDOWS系统完成WSASend或者WSArecv的操作,把结果发到完成端口。GetQueuedCompletionStatus()马上返回,并从完成端口取得刚完成的WSASend/WSARecv的结果。然后接着发出WSASend/WSARecv,并继续下一次循环阻塞在GetQueuedCompletionStatus()这里。

a).if (g_fTerminated) 线程结束前:循环遍历g_xGateList,取出pGateInfo关闭套接字,并删除节点。dwBytesTransferred =0 ,关闭该服务器套接字。

b).while ( pGateInfo->HasCompletionPacket() ) 验证消息格式。

case '-': 发送心跳数据包到每个LoginGate服务器。

case 'A':  处理每个LoginGat服务器转发的客户端的消息增加到各自网关(CGateInfo)g_SendToGateQ队列中,然后ThreadFuncForMsg线程进行验证后再发送消息到各个LoginGate服务器。

       pGateInfo->ReceiveSendUser(&szTmp[2]);

case 'O': 处理每个网关Accept客户端后增加pUserInfo用户信息到各自网关的xUserInfoList列表中。

      pGateInfo->ReceiveOpenUser(&szTmp[2]);

case 'X': 处理每个网关收到客户端Socket关闭之后发送过来的消息。设置该网关socket相应状态。

      pGateInfo->ReceiveCloseUser(&szTmp[2]);

case 'S':  GameSvr服务器发送的消息,更新TBL_ACCOUNT,验证字段,说明用户已下线,下次登录必须先到LoginSvr服务器再次验证。

      pGateInfo->ReceiveServerMsg(&szTmp[2]);

case 'M':  GameSvr服务器发送的消息,创建一个用户的消息,把用户ID,密码,名字插入TBL_ACCOUNT表中插入成功返回SM_NEWID_SUCCESS,否则SM_NEWID_FAIL,把在信息前加#,信息后加!  不做TBL_ACCOUNTADD表的添加,只增加TBL_ACCOUNT表信息。

              ‘A’:是LoginGate 服务器转发客户端消息到g_xMsgQueue队列, 由ThreadFuncForMsg线程处理后,转发到各个loginGate服务器

继续投递I/O操作。

启动InitServerThreadForMsg 创建ThreadFuncForMsg线程。c  

收到loginGate服务器发送过来的消息之后,ServerWorkerThread经过数据包分析之后(case 'A'),把客户端的消息,写入g_SendToGateQ队列中,然后在本线程中再进行处理。

             遍历g_SendToGateQ队列中数据,验证数据包是否正确(#!字符)根据DefaultMsg.wIdent标志

case CM_IDPASSWORD:   处理登陆业务

         遍历xUserInfoList用户列表信息,到数据库表TBL_ACCOUNT中找相应信息,如果失败发送(SM_ID_NOTFOUND, SM_PASSWD_FAIL)消息,否则发送SM_PASSOK_SELECTSERVER+ g_szServerList(SelGate服务器列表消息)

SelGate服务器列表消息(对应TBL_SERVERINFO数据库表中数据),供用户选择登录的SelGate服务器。

      CM_SELECTSERVER: 选择服务器(SelGate)

             遍历xUserInfoList用户列表信息,根据socket,找到用户密钥,消息解密后,遍历g_xGameServerList列表,把用户选择的SelGate服务器转化为IP地址,发送至LoginGate服务器,再转发至客户端。设置该用户SelServer的标志状态。从该网关的xUserInfoList用户列表中删除该用户。

CM_ADDNEWUSER:  新注册用户

                    判断用户名是否已存在,失败发送SM_NEWID_FAIL消息,成功,写插入表数据,并发送SM_NEWID_SUCCESS消息到 LoginGate服务器,转发至客户端。

IDM_STOPSERVICE: 停止服务(IOCP模型Server响应客户端请求)

   Close -> OnCommand(IDM_STOPSERVICE, 0L); ->g_fTerminated = TRUE; 三个线程退出。

主要流程:

服务启动后,LoginSvr启动了AcceptThread,和ServerWorkerThread线程,AcceptThread线程接收loginGate,GameSvr服务器连接,加入g_xGateList网关列表中,ServerWorkerThread线程从完成端口取得刚完成的WSASend/WSARecv的结果后,进行分析处理两个服务器发送来的消息。服务启动同时,启动ThreadFuncForMsg线程,该线程从g_xMsgQueue(iocp读到的loginGate服务器发来的数据)中取出数据,处理数据。投递I/O,利用IOCP模型,发送到loginGate服务器。

你可能感兴趣的:(windows,timer,数据库,socket,server,服务器)