ACE開發遊戲服務器筆記 (來自於 http://blog.donews.com/xtion/archive/2005/09/29/571881.aspx )

ACE開發遊戲服務器筆記一

我的插件式遊戲平臺已經基本成型了,遊戲的服務端使用的是ACE作爲網絡引擎,在開發中遇到了這樣或那樣的問題,現在有些空了,準備把它們整理一下寫出來,希望對初學者們有些幫助。
        今天講講怎麼開始使用ACE吧。
        從哪兒下載我就不說了,現在多得更米一樣。
        我的是5.3的版本,解開包後,你會發現有N多的文件和目錄,其它先不管,我們先看ace這個目錄,ACE的主要的文件都在這個裏面了。
       新建一個config.h文件,然後寫入以下的文字。

#define ACE_HAS_STANDARD_CPP_LIBRARY 1
#include "config-win32.h"

第一行表示,編譯的庫我們需要使用STL
第二行表示,我們將在win32平臺上使用這個庫。

然後你就可以編譯了,打開工程文件,然後就buildAll吧。幸運的話,就可以得到了。
這面的配置指編譯DLL方式的庫,當然你還可以編譯靜態庫,不過我不喜歡,那樣編譯出來的程序太大了。再說程序裏多幾個DLL也現得專業一些啊。

ACE開發遊戲服務器筆記二
上一篇只寫了怎麼編譯ACE,技術難度零星。今天我們先來搭個框架。
在win32平臺上最有效率的IO模型,莫過於完成端口了。CSDN上到處都是關於完成端口的問題。在ACE中對win32平臺的完成端口有着非常好的封裝。ACE中前攝式框架的win32實現就是使用的完成端口。我們先來看看這個框架有哪些組成部分。
ACE_Proactor  前攝器,真怪異的名字。叫異步事件分配者多好啊。
ACE_Service_Handler 事件處理器。處理所有異步操作的結果。
ACE_Asynch_Accept 異步連接接受器。用來監聽來自客戶的連接請求。
ACE_Asynch_Read_Stream 異步讀取器。發起異步讀操作的請求。
ACE_Asynch_Write_Stream 異步寫入器。發起異步寫操作的請求。

嗯!東西差不多齊了。不要看到上面的東西就害怕,其實很簡單,請相信我。

我們先來講一下,運行的流程。
首先,我們會使用Accept的open()方法,,監聽一個端口。
    ACE_INET_Addr localhost;
    localhost.set(8888,“127.0.0.1“);
    acceptor.open(localhost);
此時,當有客戶端的連接請求到達時,前攝器會自動的調用acceptor 的make_handle()方法,來創建一個事件處事器,處理這個連接的用戶。
//我定義的用戶事件處理器類,繼承於ACE_Service_Handler 
 CTG_GS_User_Handler *pUser_Handler;  
//使用ACE的創建宏,分配一個空間。      
 ACE_NEW_NORETURN(pUser_Handler,CTG_GS_User_Handler(this));

 

 if (pUser_Handler == NULL)
 {
  std::cout << "系統爲一個用戶分配空間時出錯,該用戶不能正常登錄";
  return 0;
 }
 return pUser_Handler;

前攝器通過調用這個方法後,得到了pUser_Handler的句柄,並將socket與這個句柄定到一起。
隨後,前攝器會緊接着調用 CTG_GS_User_Handler的open方法。方法的原型如下:

void ACE_Service_Handler::open (  ACE_HANDLE    new_handle,     ACE_Message_Block &    message_block   )
這是ACE_Service_Handler的一個虛方法,需要我們來繼承,以完成我們的事件處理器的一些初始化準備。
上面我們定義的CTG_GS_User_Handler類,是一個很重要的組成部分,處理絕大部分的IO事件。他繼承於ACE_Service_Handler,實現了以以下的三個方法。
 virtual void handle_time_out(const ACE_Time_Value &tv,const void *p);
定時器回調函數
 virtual void handle_read_stream(const ACE_Asynch_Read_Stream::Result &result);
讀操作回調函數
 virtual void handle_write_stream(const ACE_Asynch_Write_Stream::Result &result);
寫操作回調函數

比如,當我們發起一個異步的讀操作時。
//reader_是一個上面提到的異步讀取器
reader_.read(*mblk_,sizeof(CCmdMessageHead));

當讀操作完成,或部分完成時,會回調handle_read_stream方法。
我們要做如下的處理。
void CTG_GS_User_Handler::handle_read_stream(const ACE_Asynch_Read_Stream::Result &result)
{
 //更新最後接收時間
  if (!result.success()||result.bytes_transferred()==0)
  {
           //如果傳輸不成功,或傳輸了0字節時返回。客戶斷開連接時也在這裏處理。
            return;
  }
  else if (result.bytes_transferred() < result.bytes_to_read())
   //如果沒有接收完成,繼續接收。
   reader_.read(*mblk_,result.bytes_to_read()-result.bytes_transferred());
  else if (mblk_->length() == sizeof(CCmdMessageHead)){
   //接收頭完成,擴大消息體。
   CCmdMessageHead* pMsgHead;
   pMsgHead = (CCmdMessageHead*)mblk_->rd_ptr();
   pMsgHead->ulSID = this->m_UserSID;   
   //如果沒有附加信息,則直接提交。
   if (pMsgHead->ulMsgLength == 0){
    if (ALY_THREAD_POOL::instance ()->putq (mblk_) == -1)
    {
     mblk_->release ();
     return;
    }
    ACE_NEW_NORETURN
     (mblk_, ACE_Message_Block (ACE_DEFAULT_CDR_BUFSIZE));
    ACE_CDR::mb_align (mblk_);
    reader_.read (*mblk_, sizeof(CCmdMessageHead));
   }
   else
   {
   mblk_->size (pMsgHead->ulMsgLength+sizeof(CCmdMessageHead));
   //將新的消息頭放與消息體。
   reader_.read (*mblk_, pMsgHead->ulMsgLength);
   }
  }
  else
  {
   //完整接收的消息放到接收線程池的接收消息列表。
   if (ALY_THREAD_POOL::instance ()->putq (mblk_) == -1)
   {
    mblk_->release ();
    return;
   }
   ACE_NEW_NORETURN(mblk_, ACE_Message_Block (ACE_DEFAULT_CDR_BUFSIZE));
   ACE_CDR::mb_align (mblk_);
   reader_.read (*mblk_, sizeof(CCmdMessageHead));
  }
 return; 
}

這樣,我們就完整地接到了一個消息。這其中可能有些類型和變量你還不太理解,我以後會慢慢告訴你的,廣告之後馬上回來。

 if (pUser_Handler == NULL)
 {
  std::cout << "系統爲一個用戶分配空間時出錯,該用戶不能正常登錄";
  return 0;
 }
 return pUser_Handler;

前攝器通過調用這個方法後,得到了pUser_Handler的句柄,並將socket與這個句柄定到一起。
隨後,前攝器會緊接着調用 CTG_GS_User_Handler的open方法。方法的原型如下:

void ACE_Service_Handler::open (  ACE_HANDLE    new_handle,     ACE_Message_Block &    message_block   )
這是ACE_Service_Handler的一個虛方法,需要我們來繼承,以完成我們的事件處理器的一些初始化準備。
上面我們定義的CTG_GS_User_Handler類,是一個很重要的組成部分,處理絕大部分的IO事件。他繼承於ACE_Service_Handler,實現了以以下的三個方法。
 virtual void handle_time_out(const ACE_Time_Value &tv,const void *p);
定時器回調函數
 virtual void handle_read_stream(const ACE_Asynch_Read_Stream::Result &result);
讀操作回調函數
 virtual void handle_write_stream(const ACE_Asynch_Write_Stream::Result &result);
寫操作回調函數

比如,當我們發起一個異步的讀操作時。
//reader_是一個上面提到的異步讀取器
reader_.read(*mblk_,sizeof(CCmdMessageHead));

當讀操作完成,或部分完成時,會回調handle_read_stream方法。
我們要做如下的處理。
void CTG_GS_User_Handler::handle_read_stream(const ACE_Asynch_Read_Stream::Result &result)
{
 //更新最後接收時間
  if (!result.success()||result.bytes_transferred()==0)
  {
           //如果傳輸不成功,或傳輸了0字節時返回。客戶斷開連接時也在這裏處理。
            return;
  }
  else if (result.bytes_transferred() < result.bytes_to_read())
   //如果沒有接收完成,繼續接收。
   reader_.read(*mblk_,result.bytes_to_read()-result.bytes_transferred());
  else if (mblk_->length() == sizeof(CCmdMessageHead)){
   //接收頭完成,擴大消息體。
   CCmdMessageHead* pMsgHead;
   pMsgHead = (CCmdMessageHead*)mblk_->rd_ptr();
   pMsgHead->ulSID = this->m_UserSID;   
   //如果沒有附加信息,則直接提交。
   if (pMsgHead->ulMsgLength == 0){
    if (ALY_THREAD_POOL::instance ()->putq (mblk_) == -1)
    {
     mblk_->release ();
     return;
    }
    ACE_NEW_NORETURN
     (mblk_, ACE_Message_Block (ACE_DEFAULT_CDR_BUFSIZE));
    ACE_CDR::mb_align (mblk_);
    reader_.read (*mblk_, sizeof(CCmdMessageHead));
   }
   else
   {
   mblk_->size (pMsgHead->ulMsgLength+sizeof(CCmdMessageHead));
   //將新的消息頭放與消息體。
   reader_.read (*mblk_, pMsgHead->ulMsgLength);
   }
  }
  else
  {
   //完整接收的消息放到接收線程池的接收消息列表。
   if (ALY_THREAD_POOL::instance ()->putq (mblk_) == -1)
   {
    mblk_->release ();
    return;
   }
   ACE_NEW_NORETURN(mblk_, ACE_Message_Block (ACE_DEFAULT_CDR_BUFSIZE));
   ACE_CDR::mb_align (mblk_);
   reader_.read (*mblk_, sizeof(CCmdMessageHead));
  }
 return; 
}

這樣,我們就完整地接到了一個消息。這其中可能有些類型和變量你還不太理解,我以後會慢慢告訴你的,廣告之後馬上回來。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章