注:本文仅对使用ACE进行网络通信进行演示说明。本文中的代码皆使用doxgen的注释风格。本文中使用的事件机制,其原理与实现请参考[ 基于C++的事件机制设计[2.0]]一文。
ACE的Proactor对Epoll和IOCP进行了良好包装,因此,使用ACE来进行网络开发是相当的便利,性能也不差。闲言少叙,看代码。
这里以TCP协议进行流式通信。我们需要解析流,得出每次接收到的数据包大小和包含的数据域,假定我们的包结构如下:
包序列号(32Bit) | 长度(16Bit) | 数据域(大小为长度所表示的字节)... | (下一包)
通过分析由包序列号和长度组成的包头来解决半包,粘包等问题,许多其它文章也有描述,这里就省略了。
这样可以确定我们的包头结构如下:
#pragma pack(push) #pragma pack(1) /** * @brief Tcp包头结构 */ typedef struct tag_TTcpPackHeader { unsigned int seq; //< 包序号 unsigned short len; //< 包长度 }TTcpPackHeader; #pragma pack(pop) /// 包头尺寸宏 #define TCP_PACK_HEADER_SIZE sizeof(tag_TTcpPackHeader) .
需要注意的是,要求在字节边界对齐。
现在来看看通过ACE来实现TCP通信需要哪些东西:
INET_Addr 用于地址访问
Task_Base 用于线程模型
Message_Block 用于消息传递和数据容器
Asynch_IO 异步通信
Proactor IOCP架构
并且,要建立这样的通信架构,我们需要:
一个Acceptor:用于接受连接
一个Handler:对应于每个连接句柄,并用于数据的发送/接收。
一个事件分发线程:以事件的形式将接收到数据分发出去,并在对应的句柄上进行数据发送。
本示例并没有采用在接收到数据时立即进行处理的方式,而是通过创建一个额外的事件分发线程的形式,将数据包投递到该线程的消息队列中,由该线程向外派送。因此,数据处理与网络层是隔离的,且网络层能专注于通信,最大的发挥效用。
好了,下面来看看实现:
先看Handler,参考ACE_Service_Handler,我们需要重载open(),addresses(), handle_read_stream(),handle_write_stream(),以在连接打开时进行读写流对象的初始化、获取客户端地址,处理输入/输入流。
注:以T作为类的开头而不是C,是出于对曾经伟大的BORLAND的深刻怀念。
注:成员又以m_开头,是出于对现而今仍伟大的MS的深刻怨念。
/** * @class TTcpHandler * @brief Tcp连接句柄 */ class TTcpHandler : public ACE_Service_Handler { public: /** * @brief 客户端连接事件类型定义 * @param [in] ACE_UINT32 客户端地址 * @param [in] ACE_UINT16 客户端端口 * @param [in] TTcpHandler* 连接句柄 */ typedef TEvent TOnClientConnect; /** * @brief 客户端断开连接事件类型定义 * @param [in] ACE_UINT32 客户端地址 * @param [in] ACE_UINT16 客户端端口 */ typedef TEvent TOnClientDisconnect; /** * @brief 客户端连接验证事件 * @param [in] ACE_UINT32 客户端地址 * @param [in] ACE_UINT16 客户端端口 * @return bool * - true 验证通过 * - false 验证失败 */ typedef TEvent TOnClientValidate; /** * @brief 接收到客户端数据事件类型定义 * @param [in] ACE_UINT32 客户端地址 * @param [in] ACE_UINT16 客户端端口 * @param [in] unsigned int 数据包序列号 * @param [in] const char* 数据区域指针 * @param [in] size_t 数据长度 */ typedef TEvent TOnDataReceive; /** * @brief 成功发送客户端数据事件类型定义 * @param [in] ACE_UINT32 客户端地址 * @param [in] ACE_UINT16 客户端端口 * @param [in] unsigned int 数据包序列号 * @param [in] const char* 数据区域指针 * @param [in] size_t 数据长度 */ typedef TEvent TOnDataSendSucceeded; /** * @brief 失败发送客户端数据事件类型定义 * @param [in] ACE_UINT32 客户端地址 * @param [in] ACE_UINT16 客户端端口 * @param [in] unsigned int 数据包序列号 * @param [in] const char* 数据区域指针 * @param [in] size_t 数据长度 */ typedef TEvent TOnDataSendFailed; private: ACE_Asynch_Read_Stream m_Reader; //< 异步读数据流 ACE_Asynch_Write_Stream m_Writer; //< 异步写数据流 ACE_Message_Block* m_CurDataMB; //< 当前读取数据 ACE_INET_Addr m_ClientAddr; //< 客户端地址 public: /** * @name 事件句柄 * @{ */ DECL_PROP(TOnClientConnect, OnClientConnect) //< 客户端连接事件句柄 DECL_PROP(TOnClientDisconnect, OnClientDisconnect) //< 客户端断开事件句柄 DECL_PROP(TOnDataReceive, OnDataReceive) //< 接收到数据的事件句柄 DECL_PROP(TOnDataSendSucceeded, OnDataSendSucceeded) //< 成功发送数据的事件句柄 DECL_PROP(TOnDataSendFailed, OnDataSendFailed) //< 发送数据失败的事件句柄 /** * @} */ public: /// ctor TTcpHandler(); /// dtor ~TTcpHandler(); /** * @brief 发送数据 * @param [in] unsigned int 数据包序列号 * @param [in] const char* 要发送的数据区域指针 * @param [in] size_t 要发送的数据长度 * @return int * - 0 成功 * - 1 失败 */ int send(unsigned int seq, const char* data, unsigned short size); /** * @brief 打开句柄 * @see ACE_Service_Handler */ virtual void open(ACE_HANDLE h, ACE_Message_Block& mb); /** * @brief 获取地址 * @see ACE_Service_Handler */ virtual void addresses (const ACE_INET_Addr &remote_address, const ACE_INET_Addr &local_address); /** * @brief 读取流数据 * @see ACE_Service_Handler */ virtual void handle_read_stream(const ACE_Asynch_Read_Stream::Result& result); /** * @brief 写入流数据 * @see ACE_Service_Handler */ virtual void handle_write_stream(const ACE_Asynch_Write_Stream::Result& result); /** * @brief 初始化当前数据接收缓冲事件 */ void initCurDataMB(); }; // class TTcpHandler
而相应滴,Acceptor在接受连接时,产生出的Handler应该是TTcpHandler类型,其定义如下:
注意,为了将事件句柄与连接句柄(TTcpHandler)挂钩,这里重载了make_handler()。而重载validate_connection则是为了让连接验证事件能够在恰当的时机被激发。
/** * @class TTcpAcceptor * @brief TCP接受器 * @see ACE_Asynch_Acceptor * @see TTcpHandler */ class TTcpAcceptor : public ACE_Asynch_Acceptor { public: /** * @name TCP事件句柄 * @see TTcpHandler * @{ */ DECL_PROP(TTcpHandler::TOnClientConnect, OnClientConnect) DECL_PROP(TTcpHandler::TOnClientDisconnect, OnClientDisconnect) DECL_PROP(TTcpHandler::TOnClientValidate, OnClientValidate) DECL_PROP(TTcpHandler::TOnDataReceive, OnDataReceive) DECL_PROP(TTcpHandler::TOnDataSendSucceeded, OnDataSendSucceeded) DECL_PROP(TTcpHandler::TOnDataSendFailed, OnDataSendFailed) /** * @} */ protected: /** * @brief 连接验证 * @note 激发 OnClientValidate 事件 @see TOnClientValidate * @see ACE_Asynch_Acceptor */ virtual int validate_connection (const ACE_Asynch_Accept::Result& result, const ACE_INET_Addr &remote, const ACE_INET_Addr& local); /** * @brief 创建连接句柄事件 * @see ACE_Asynch_Acceptor */ virtual TTcpHandler* make_handler(void); }; // class TTcpAcceptor
有了Acceptor和Handler,还需要使之运行于Proactor模式下,因此有了以下线程:
/** * @class TTcpNetThread * @brief TCP网络线程 * @see ACE_Task_Base * @see ACE_Proactor */ class TTcpNetThread : public ACE_Task_Base { public: /** * @name TCP事件句柄 * @see TTcpHandler * @{ */ DECL_PROP(TTcpHandler::TOnClientConnect, OnClientConnect) DECL_PROP(TTcpHandler::TOnClientDisconnect, OnClientDisconnect) DECL_PROP(TTcpHandler::TOnClientValidate, OnClientValidate) DECL_PROP(TTcpHandler::TOnDataReceive, OnDataReceive) DECL_PROP(TTcpHandler::TOnDataSendSucceeded, OnDataSendSucceeded) DECL_PROP(TTcpHandler::TOnDataSendFailed, OnDataSendFailed) /** * @} */ /// 运行 int open(); /// 停止运行 int close(); protected: /// 线程函数 virtual int svc(); };
最后再看看事件分发线程,该线程也是对上述实现的聚合和封装,对外暴露事件和发送方法:
注意,该类也负责响应TTcpNetThread所激发的事件,所以需要派生自TObject。
/** * @class TTcp * @brief TCP接收和事件处理代理线程 */ class TTcp : public TObject, public ACE_Task { public: /** * @name 重定义事件类型 * @see TTcpHandler * @{ */ typedef TTcpHandler::TOnClientConnect TOnClientConnect; typedef TTcpHandler::TOnClientDisconnect TOnClientDisconnect; typedef TTcpHandler::TOnClientValidate TOnClientValidate; typedef TTcpHandler::TOnDataReceive TOnDataReceive; typedef TTcpHandler::TOnDataSendSucceeded TOnDataSendSucceeded; typedef TTcpHandler::TOnDataSendFailed TOnDataSendFailed; /** * @} */ private: /** * @name 成员变量 * @{ */ ACE_Recursive_Thread_Mutex m_Lock; //< 线程锁 hash_map m_AddrMap; //< 地址/句柄映射 TTcpNetThread* m_TcpNetThd; /** * @} */ public: /** * @name TCP事件句柄 * @see TTcpHandler * @{ */ DECL_PROP(TTcpHandler::TOnClientConnect, OnClientConnect) DECL_PROP(TTcpHandler::TOnClientDisconnect, OnClientDisconnect) DECL_PROP(TTcpHandler::TOnClientValidate, OnClientValidate) DECL_PROP(TTcpHandler::TOnDataReceive, OnDataReceive) DECL_PROP(TTcpHandler::TOnDataSendSucceeded, OnDataSendSucceeded) DECL_PROP(TTcpHandler::TOnDataSendFailed, OnDataSendFailed) /** * @} */ public: /// ctor TTcp(); /// dtor ~TTcp(); /// 运行 void open(); /// 停止 void close(); /// 发送数据 int send(ACE_UINT32 ip, ACE_UINT16 port, unsigned int seq, const char* buf, unsigned short len); private: /// 线程函数 virtual int svc(); private: /** * @name TTcpNetThread 事件处理方法 * 关于事件原型的定义,请参考 @see TTcpHandler * @{ */ void tcpNetThread_OnClientConnect(ACE_UINT32 ip, ACE_UINT16 port, TTcpHandler* handler); void tcpNetThread_OnClientDisconnect(ACE_UINT32 ip, ACE_UINT16 port); void tcpNetThread_OnDataReceive(ACE_UINT32 ip, ACE_UINT16 port, unsigned int seq, const char* data, unsigned short size); void tcpNetThread_OnDataSendSucceeded(ACE_UINT32 ip, ACE_UINT16 port, unsigned int seq, const char* data, unsigned short size); void tcpNetThread_OnDataSendFailed(ACE_UINT32 ip, ACE_UINT16 port, unsigned int seq, const char* data, unsigned short size); /** * @} */ }; // class TTcp
现在基本格调已经确定,需要做的是编写具体实现代码了。
此乃末技。
应用ACE来作为底层通信的框架,已经是许多年前的技术了,这里纯粹是凑字数,骗更新滴。这样的老东西,确实是相当的让人无语。