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方法。方法的原型如下:
這是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方法。方法的原型如下:
這是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;
}
這樣,我們就完整地接到了一個消息。這其中可能有些類型和變量你還不太理解,我以後會慢慢告訴你的,廣告之後馬上回來。