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;
}
这样,我们就完整地接到了一个消息。这其中可能有些类型和变量你还不太理解,我以后会慢慢告诉你的,广告之后马上回来。