从C++类成员函数作为回调函数说起
blog.csdn/mpforwd/article/details/5772639
在⽹络消息处理中经常要⽤到回调机制。
例如处理异步⽹络操作的前摄器设计模式(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++中要将⼀个普通类成员函数⽤于回调却并⾮易事。原因参见我的⽂章
当然,时代在进步,新的技术为我们提供了完善的解决⽅案。从《Modern C++ Design》中的探索实践。到boost fuction 和boost bind的标准化⽅案,以上想法已经被很多库实现。
下⾯介绍⼀个例⼦,⽤boost function 和boost bind 来将类成员函数⽤于回调机制。
[cpp]
1. #include <boost/bind.hpp>
2. #include <boost/function.hpp>
3. #include <iostream>
4.
5. // 函数对象接受⼀个参数,返回值类型为 void
6. typedef boost::function<void(const std::string &)> callback;
7.
8. class A
9. {
10. public:
11.    void print(const std::string &msg)
12.    {
13.        std::cout << msg << std::endl;
14.    }
15. };
16.recv函数
17.
18. class B
19. {
20. public:
21.    //设置回调函数对象
22.    void set_callback(callback cb)
23.    {
24.        m_cb = cb;
25.    }
26.
27.    //调⽤回调函数对象
28.    void do_callback()
29.    {
30.        m_cb("message");
31.    }
32.
33. private:
34.    callback m_cb;
35. };
36.
37. void regular_function(const std::string &msg)
38. {
39.    std::cout << msg << std::endl;
40. }
41.
42. int main()
43. {
44.    A a;
45.    B b;
46.
47.    //成员函数⽤于回调
48.    b.set_callback(boost::bind(&A::print, &a, _1));
49.    b.do_callback();
50.
51.    // 普通函数指针同样适⽤
52.    b.set_callback(regular_function);
53.    b.do_callback();
54.    return 0;
55. }
已知应⽤:boost asio使⽤更现代的C++的⽅法实现前摄式设计模式(Proactor)。可以将类成员函数通过boost bind适配为完成事件处理函数。从⽽解除了对公共基类的依赖。
如下是利⽤ boost asio 写的⼀个udp的例⼦。在这个例⼦中假设我们的⾓⾊是⼀个udp客户端。实际上从协议上看upd通信的双⽅是对等的。没有客户端与服务器之分。
[cpp]
1. #include "stdafx.h"
2.
3. #include <boost/array.hpp>
4. #include <boost/bind.hpp>
5. #include <boost/shared_ptr.hpp>
6. #include <boost/asio.hpp>
7. #include <iostream>
8. #include <string>
9.
10. using namespace boost;
11. using namespace boost::asio;
12. using boost::asio::ip::udp;
13. using std::string;
14.
15. const size_t UDP_BUFFER_SIZE = 1560;
16. const unsigned short SERVER_PORT = 8451;
17.
18. class UdpClient
19. {
20. public:
21.    UdpClient(io_service& io_srv, const string &server_ip);
22.
23. protected:
24. private:
25.
26.    void open_local_port_with_server();
27.    void start_send_to_server();
28.    void start_receive_from_server();
29.    void handle_receive_from_server(const boost::system::error_code& error, size_t bytes_transferred);
30.    void handle_send_to_server(boost::shared_ptr<string> message, const boost::system::error_code& error, size_t bytes_transferred);
31.
32. private:
33.    udp::socket  socket_;
34.    udp::endpoint server_remote_endpoint_temp_;
35.    udp::endpoint server_remote_endpoint_;
36.    boost::array<char, UDP_BUFFER_SIZE> recv_buffer_from_server_;
37. };
38.
39. UdpClient::UdpClient(io_service& io_srv,  const string &server_ip)
40. : socket_(io_srv),
41. server_remote_endpoint_(udp::endpoint(asio::ip::address(asio::ip::address_v4::from_string(server
_ip)), SERVER_PORT))
42. {
43.    open_local_port_with_server();
44.    start_send_to_server();
45.    start_receive_from_server();
46. }
47.
48. void UdpClient::open_local_port_with_server()
49. {
50.    socket_.open(udp::v4());
51. }
52.
53. void UdpClient::start_send_to_server()
54. {
55.    boost::shared_ptr<std::string> packet(new string("how are you doing?"));
56.    //send packet to server
57.    socket_.async_send_to
58.    (
59.        asio::buffer(*packet),
60.        server_remote_endpoint_,
61.        boost::bind(&UdpClient::handle_send_to_server, this, packet, asio::placeholders::error, asio::placeholders::bytes_transferred)
62.    );
63. }
64.
65. void UdpClient::start_receive_from_server()
66. {
67.    socket_.async_receive_from
68.    (
69.        asio::buffer(recv_buffer_from_server_),
70.        server_remote_endpoint_temp_,
71.        boost::bind(&UdpClient::handle_receive_from_server, this, asio::placeholders::error, asio::placeholders::bytes_transferred)
72.    );
73. }
74.
75. void UdpClient::handle_receive_from_server(const boost::system::error_code& error, std::size_t bytes_transferred)
76. {
77.    if ( (!error) || (asio::error::message_size == error) )
78.    {
79.        boost::shared_ptr<std::string> packet(new string(recv_buffer_from_server_.data(), bytes_transferred));
80.        std::cout<<"Receive packet from server: "<<string(recv_buffer_from_server_.data(), bytes_transferred)<<std::endl;
81.
82.        start_receive_from_server();
83.        start_send_to_server();
84.    }
85.    else
86.    {
87.        std::cout<<"Error in function:  "<<__FUNCTION__
88.            <<"with message: "<&ssage()<<std::endl;
89.    }
90. }
91.
92. void UdpClient::handle_send_to_server(boost::shared_ptr<std::string> message, const boost::system::error_code& error, std::size_t bytes_transferred)
93. {
94.    std::cout<<"Send packet to server: "<<*message<<std::endl;
95. }
96.
97.
98.
99. int main(int argc, char **argv)
100. {
101.    try
102.    {
103.        boost::asio::io_service io_srv;
104.        UdpClient client(io_srv, "192.168.1.103");
105.        io_srv.run();
106.    }
107.    catch (std::exception& e)
108.    {
109.        std::cerr << e.what() << std::endl;
110.    }
111.
112.    return 0;
113. }

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。