从C++类成员函数作为回调函数说起

本文探讨了C++中网络编程的回调机制,特别是异步操作的前摄器设计模式(Proactor)。介绍了ACE_Proactor的实现原理及其局限性,并展示了如何使用Boost.Asio实现更现代的回调机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在网络消息处理中经常要用到回调机制。

例如处理异步网络操作的前摄器设计模式(Proactor),(可以参考 《C++ 网络编程 卷2》中关于ACE Proactor模式实现 )。

异步的 Web 服务器将这样来利用前摄器模式:首先让 Web 服务器向 OS 发出异步操作,并将回调方法登记到 Completion Dispatcher(完成分派器),后者将在操作完成时通知 Web 服务器。于是 OS 代表 Web 服务器执行操作,并随即在一个周知的地方将结果排队。Completion Dispatcher 负责使完成通知出队,并执行适当的、含有应用特有的 Web 服务器代码的回调。

使用前摄器模式的主要优点是可以启动多个并发操作,并可并行运行,而不要求应用必须拥有多个线程。操作被应用异步地启动,它们在 OS 的 I/O 子系统中运行直到完成。发起操作的线程现在可以服务 另外的请求了。

 

在ACE中,可以通过ACE_Proactor实现前摄器模式。实现方式如下。

1。创建服务处理器:

Proactor框架中服务处理器均派生自ACE_Service_Handler,它和Reactor框架的事件处理器非常类似。当发生IO操作完成事件时,会触发相应的事件完成会调函数。

2。实现服务处理器IO操作

Proactor框架中所有的IO操作都由相应的异步操作类来完成,这些异步操作类都继承自ACE_Asynch_Operation。常用的有以下几种。

  1. ACE_Asynch_Read_Stream, 提供从TCP/IP socket连接中进行异步读操作.
  2. ACE_Asynch_Write_Stream, 提供从TCP/IP socket连接中进行异步写操作.

使用这些操作类的一般方式如下:

  1. 初始化
    将相关的操作注册到服务处理器中,一般可通过调用其open方法实现。
  2. 发出IO操作
    发出异步IO操作请求,该操作不会阻塞,具体的IO操作过程由操作系统异步完成。
  3. IO操作完成回调处理
    异步IO操作完成后,OS会触发服务处理器中的相应回调函数,可通过该函数的ACE_Asynch_Result参数获取相应的返回值。

3。使用连接器或接受器和远端进行连接

ACE为Proactor框架提供了两个工厂类来建立TCP/IP连接。

  1. ACE_Asynch_Acceptor, 用于被动地建立连接
  2. ACE_Asynch_Connector 用于主动地建立连接

当远端连接建立时,连接器或接受器便会创建相应的服务处理器,从而可以实现服务处理。

4。启动Proactor事件分发处理

启动事件分发处理只需如下调用:

    while(true)
        ACE_Proactor::instance ()->handle_events ();
  

 

本次话题的重点是 ACE_Service_Handler 如何向 ACE_Proactor 注册回调函数。

ACE的做法是利用虚函数所得到的多态性质。

ACE_Service_Handler 作为网络事件处理器的基类,它通过虚函数规范了事件处理的接口函数。在客户开发自己的网络程序时,需要写自己的事件处理器,它必须从ACE_Service_Handler 继承这些接口,并根据情况改写它们。

网络程序在连接建立时将ACE_Service_Handler的派生类实体注册到ACE_Proactor,在网络事件到达时,ACE_Proactor通过约定的接口可以将事件处理转接到已注册的ACE_Service_Handler的派生类实体。

 

上面实现的弊端是:

1. 使用继承和虚函数增大了客户程序与ACE框架的耦合性。客户程序的事件处理器必须继承自ACE_Service_Handler。众所周知继承是仅次于友元的耦合关系。

2. 用于使用继承,客户类的行为不完全可控,可能导致在资源管理时陷入两难的境地。用过ACE写过网络程序的都知道,ACE Proactor本身是一个半成熟的框架,一种情况是ACE_Service_Handler可能同时运行在ACE_Proactor所在的线程,以及另外一个发送端控制线程上,在控制线程可能发起正常连接释放,也有可能在事件处理中检测到网络故障而释放,这两种释放可能在两个不同的线程中,要做到资源没有泄露并非易事。

3. 虚函数的运行开销。

 

那么是否有一种回调机制使得事件处理器(Event Handler)不必从一个限定的基类派生,而是可以将自己符合接口定义的任一成员函数作为回调函数直接注册到完成分派器(Completion Dispatcher)。

 

在C语言中将一个全局函数用于回调非常容易。然而在C++中要将一个普通类成员函数用于回调却并非易事。原因参见我的文章http://blog.youkuaiyun.com/MPForwd/archive/2010/07/28/5772462.aspx

 

当然,时代在进步,新的技术为我们提供了完善的解决方案。从《Modern C++ Design》中的探索实践。到boost fuction 和boost bind的标准化方案,以上想法已经被很多库实现。

下面介绍一个例子,用boost function 和boost bind 来将类成员函数用于回调机制。

 

 

已知应用:boost asio使用更现代的C++的方法实现前摄式设计模式(Proactor)。可以将类成员函数通过boost bind适配为完成事件处理函数。从而解除了对公共基类的依赖。

如下是利用 boost asio 写的一个udp的例子。在这个例子中假设我们的角色是一个udp客户端。实际上从协议上看upd通信的双方是对等的。没有客户端与服务器之分。

 

 

 

 

如qsort 等函数需要函数指针才能回调 用此函数库可以将成员函数指针转为普通函数指针 测试代码如下 #include <stdio.h> #include <algorithm> #include <vector> #include <string> #include <iostream> #include <math.h> using cmpfunc = int(__cdecl*)(const void*, const void*); using DebugArrayFunc = void(__stdcall *)(std::string &out;); #include "thunk.h" class MySort { public: int Rule; MySort(int a):Rule(a){} // 回调函数 template<typename T> int __cdecl sort(const void* a, const void* b); }; class Test { public: std::vector<int> mm; void Sort(int (*comp)(const void *,const void *)) { return qsort(mm._Myfirst,mm.size(),sizeof(int),comp); } void Entry(DebugArrayFunc func) { std::string string; cmpfunc comp; TemplateThunk athunk; // 正序 comp = (cmpfunc)athunk.GetCall(&MySort;::sort<int>, &MySort;(0)); Sort(comp); func(string); std::cout << string << std::endl; // 逆序 comp = (cmpfunc)athunk.GetCall(&MySort;::sort<int>, &MySort;(1)); Sort(comp); func(string); std::cout << string << std::endl; } }; class CallBack { public: std::vector<int> *pthis; CallBack(std::vector<int> *ff):pthis(ff){} void __stdcall DebugArray(std::string &out;) { char buff[100]; char *aa = buff; for each (auto a in *pthis) { aa += sprintf(aa, "%d ", a); } out.assign(buff); } }; void main() { TemplateThunk athunk; Test tt; tt.mm = { 1, 3, 7, 8, 5, 6, 4, 2, 3, 10 }; tt.Entry(athunk.GetCall(&CallBack;::DebugArray,&CallBack;(&tt;.mm))); } template <typename T> int __cdecl MySort::sort(const void* a, const void* b) { return Rule ? *static_cast<const T*>(a)-*static_cast<const T*>(b) : *static_cast<const T*>(b)-*static_cast<const T*>(a); }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值