Asio与shared_ptr的一些注意事项
经过一段时间的Asio使用,理清楚了一些基本的概念,这里和shared_ptr指针一起总结一下
1、Asio中,不管写(Write)还是读(Read)都需要等待相应的事件完成后再发起下一次写或者读。读操作比较好办,在handle_read事件中直接进行下一次async_read操作就可以,但是写的话得自己管理一个deque队例,在写入操作完成后则自动把最顶的数据包弹出,然后开始写下一个(如果在缓冲队列中还有剩余的数据包);
#define _WIN32_WINNT 0x0501
#include <iostream>
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/asio.hpp>
#include <boost/log/trivial.hpp>
#include <windows.h>
using namespace std;
using namespace boost::asio;
using boost::asio::ip::tcp;
//错误代码处理;
void print_error(const boost::system::error_code& error)
{
return;
if(error == boost::asio::error::bad_descriptor)
{
cout<<"在一个已经关闭了的套接字上执行async_receive()"<<std::endl;
return;
}
if(error == boost::asio::error::operation_aborted)
{
cout<<"正在async_receive()异步任务等待时,本端关闭套接字"<<std::endl;
return;
}
if(error == boost::asio::error::connection_reset)
{
cout<<"正在async_receive()异步任务等待时,远端的TCP协议层发送RESET终止链接,暴力关闭套接字"<<std::endl;
return;
}
if(error == boost::asio::error::eof)
{
cout<<"正在async_receive()异步任务等待时,远端关闭套接字,这个是远端正常关闭套接字"<<std::endl;
return;
}
std::cout <<"其他错误;"<< boost::system::system_error(error).what() << std::endl;
}
//io_service智能指针;
typedef boost::shared_ptr<boost::asio::io_service> io_service_ptr;
//work守护智能指针;
typedef boost::shared_ptr<boost::asio::io_service::work> work_ptr;
//线程智能指针;
typedef boost::shared_ptr<boost::thread> thread_ptr;
//监控对象池;
class io_service_pool: public boost::noncopyable
{
public:
//显式定义构造函数,不允许隐式转换;
explicit io_service_pool(size_t size):m_next_service_index(0)
{
for(size_t i= 0; i< size; ++i)
{
//创建监控组对象;
io_service_ptr service(new io_service());
m_io_services.push_back(service);
//创建组守护对象;
work_ptr work(new io_service::work(*service));
m_guard_works.push_back(work);
}
}
//启动事件监控;
void start()
{
size_t nLen = m_io_services.size();
for(size_t i = 0; i < nLen; ++i)
{
boost::shared_ptr<boost::thread> _thread(new boost::thread(
boost::bind(&boost::asio::io_service::run,m_io_services[i])));
m_threads.push_back(_thread);
}
}
//事件监控等待;
void join()
{
size_t nLen = m_threads.size();
for(size_t i= 0;i< nLen; ++i)
{
m_threads[i]->join();
}
}
//事件监控结束;
void stop()
{
size_t nLen = m_io_services.size();
for(size_t i = 0; i < nLen; ++i)
{
m_io_services[i]->stop();
}
}
//获取事件监控对象;
io_service& get_io_service()
{
boost::mutex::scoped_lock lock(m_mutex);
io_service& service = *m_io_services[m_next_service_index];
++ m_next_service_index;
if(m_next_service_index == m_io_services.size())
{
m_next_service_index = 0;
}
return service;
}
private:
//互斥对象;
boost::mutex m_mutex;
//事件监控组;
std::vector<io_service_ptr> m_io_services;
//守护对象组;
std::vector<work_ptr> m_guard_works;
//线程组;
std::vector<thread_ptr> m_threads;
//下一个可用索引;
size_t m_next_service_index;
};
//定义读写数据包大小;
#define MAX_PACKET_LEN 1024
//客户端连接会话;
class session: public boost::enable_shared_from_this<session>
{
public:
//构造函数;
session(io_service& service):m_client_socket(service),m_monitor(service)
{
}
//析构函数;
virtual ~session()
{
}
//获取套接字;
tcp::socket& socket()
{
return m_client_socket;
}
//开启读写;
void start()
{
//监视客户端操作;
m_client_socket.async_read_some(buffer(m_data,MAX_PACKET_LEN),
boost::bind(&session::read_handler,shared_from_this(),boost::asio::placeholders::error,boost::asio::placeholders::bytes_transferred));
//五秒钟内没有操作,踢掉客户端;
m_monitor.expires_from_now(boost::posix_time::seconds(5));
m_monitor.async_wait(boost::bind(&session::time_out, shared_from_this()));
}
//读事件处理器;
void read_handler(const boost::system::error_code& err,size_t bytes)
{
//返回错误;
if(err)
{
//销毁会话;
print_error(err);
//std::cout <<"其他错误;"<< boost::system::system_error(err).what() << std::endl;
return;
}
//返回读取的数据;
//async_write(m_client_socket,buffer(m_data,bytes),boost::bind(&session::write_handler,shared_from_this(),boost::asio::placeholders::error));
}
//写事件处理器;
void write_handler(const boost::system::error_code& err)
{
//返回错误;
if(err)
{
//销毁会话;
print_error(err);
return;
}
start();
}
//客户端超时处理;
void time_out()
{
try
{
m_client_socket.shutdown(tcp::socket::shutdown_both);
m_client_socket.close();
//BOOST_LOG_TRIVIAL(warning)<< "close the connection";
}
catch (std::exception& e)
{
//BOOST_LOG_TRIVIAL(warning) << "thread id: " << boost::this_thread::get_id() << " " << e.what();
}
}
private:
//客户端通信套接字;
tcp::socket m_client_socket;
//数据包大小;
char m_data[MAX_PACKET_LEN];
//定时器;
deadline_timer m_monitor;
};
typedef boost::shared_ptr<session> session_ptr;
//otco服务器;
class otco_server
{
public:
otco_server(short port,int threads):m_service_pool(threads)
,m_acceptor(m_service_pool.get_io_service(),tcp::endpoint(tcp::v4(),port))
{
//创建一个会话;
session_ptr pSession(new session(m_service_pool.get_io_service()));
//等待连接;
m_acceptor.async_accept(pSession->socket(),boost::bind(&otco_server::accept_handler,this,pSession,boost::asio::placeholders::error));
}
//连接处理;
void accept_handler(session_ptr pSession,const boost::system::error_code& err)
{
//条件判断;
if(err)
{
//销毁会话;
print_error(err);
return;
}
//读取事件;
pSession->start();
//开启新的会话,进行监听;
pSession.reset(new session(m_service_pool.get_io_service()));
//重新监听客户端;
m_acceptor.async_accept(pSession->socket(),boost::bind(&otco_server::accept_handler,this,pSession,boost::asio::placeholders::error));
}
//开启全部事件循环;
void run()
{
m_service_pool.start();
m_service_pool.join();
}
private:
//事件监控池;
io_service_pool m_service_pool;
//客户连接器;
tcp::acceptor m_acceptor;
};
int main(int argc, char* argv[])
{
try
{
//监听2001端口
otco_server server(2001,1);
//开启事件循环;
server.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
2、Asio中,不管任何的函数调用,若有未涉及error_code和涉及error_code的相同功能函数存在,则使用涉及error_code的函数调用,并且处理错误信息,否则io_service会因为异常而退出消息循环;
3、若session或其它的类是使用shared_ptr来包装的,则需要将该类继承于enable_shared_from_this,否则会有可能在该对象已经被删除的情况下,该对象内的异步回调函数被调用,这样会导致程序崩溃退出;
4、要注意,当类继承了enable_shared_from_this后,在构造函数中千万不要调用shared_from_this()函数,否则程序会抛掷异常;
5、如果类中有方法暴露在外,而有可能是非线程安全调用的,则使用io_service::post函数来调用asio中的函数,以保证asio的回调是线程安全的;
示例代码如下:(注:因为只是代码片断,随便手写,而且只是为了说明问题,所以并未检查过编译是否通过)
#include <deque>
#include <boost/asio.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
using namespace boost::asio;
using namespace boost::asio::ip;
typedef boost::shared_ptr<tcp::socket> socket_ptr;
typedef std::pair<void*, std::size_t> buffer_type;
typedef std::deque<buffer_type> buffer_deque;
class session : public boost::enable_shared_ptr_from_this<session>
{
public:
session(io_service& ios, socket_ptr sp)
: ios_(ios)
, sp_(sp)
{
}
void start_read()
{
async_read_until(*sp_, sb_, '\n', boost::bind(&session::handle_read, shared_from_this(), placeholders::error);
}
void send(void const* p, std::size_t size)
{
bool need_write = buffers_.empty();
buffers_.push_back(std::make_pair(p, size));
if (need_write) ios_.post(boost::bind(&session::do_send, shared_from_this()));
}
void close()
{
boost::system::error_code ec;
sp_->shutdown(tcp::socket::shutdown_both, ec);
if (ec) std::cout << ec.message().c_str() << std::endl;
sp_->close(ec);
if (ec) std::cout << ec.message().c_str() << std::endl;
}
private: // do functions
void do_send()
{
async_write(*sp, buffer(buffers_.begin()->first, buffers_.begin()->second), boost::bind(&sessions::handle_write, shared_from_this(), placeholders::error));
}
private: // handlers
void handle_read(boost::system::error_code const& ec)
{
if (!ec)
{
std::istream is(&sb_);
std::string cmd;
std::getline(is, cmd);
// todo: handle command
start_read(); // start next round
}
else
{
std::cout << ec.message().c_str() << std::endl;
close();
}
}
void handle_write(boost::system::error_code const& ec)
{
if (!ec)
{
buffers_.pop_front();
if (!buffers_.empty()) do_send();
}
else
{
std::cout << ec.messages().c_str() << std::endl;
close();
}
}
private:
io_service& ios_;
socket_ptr sp_;
streambuf sb_;
buffer_deque buffers_;
};
2010年09月14日
asio中异步调用优雅的传值
在asio库中的很多异步函数是需要调用者保持数据内存有效性一直到该异步方法被handle,这样如果按照正常的做法应该把该数据作为类的成员变量(这样在整个对象的生存期间该数据都有效了)。但是这样做却很不优雅,今天看到有这样的写法
class cmd_writer
{
public:
template <typename C>
void write_command(boost::asio::ip::tcp::socket& socket, int const iden, C const& cmd)
{
cmd_container_ptr p(new cmd_container(iden, cmd)); // 内存在这里被分配
boost::asio::async_write(socket, boost::asio::buffer(p->get(), p->size()),
boost::bind(&cmd_writer::handle_write, this,
boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred,
p // 在这里将该智能指针对象传递下去,直至handle函数启用后才删除
)
);
}
private:
void handle_write(boost::system::error_code const& err, std::size_t const bytes_transferred,
cmd_container_ptr p)
{
if (!err)
{
p.reset(); // 最后到了这里才释放内存
}
}
};
这种写法使用boost:bind函数将需要保持的对象智能指针对象从异步调用函数内传递给该异步调用函数的handle函数,完美解决了异步调用中保持数据有效性的问题而却又不增加多余的成员函数。
关于asio中的io_service::run函数在没有任务时退出的问题
io_service::run函数在没有任何任务的时候将会自动返回,这对于WTL的项目来说并不方便,之前我有一篇文章讲到使用一个循环来运行该run函数,那是十分不优雅的。今天在网上再次查找办法,终于找到io_service::work类可以使io_service::run函数在没有任务的时候仍然不返回,直至work对象被销毁。
boost::asio::io_service ios; boost::asio::io_service::work work(ios); // 使用work对象 ios.run(); // 就算是当前没有任务,ios.run()也不会马上返回
或者,下面从我的程序中直接拷贝出来的例子,该程序使用WTL框架
boost::asio::io_service m_ios;
std::shared_ptr<boost::thread> m_ios_thread;
std::shared_ptr<tm_client> m_client_ptr;
std::shared_ptr<boost::asio::io_service::work> m_work;
m_work.reset(new boost::asio::io_service::work(m_ios));
m_ios_thread.reset(new boost::thread(boost::bind(&boost::asio::io_service::run, &m_ios)));
// resolve the hostname
boost::asio::ip::tcp::resolver resolver(m_ios);
boost::asio::ip::tcp::resolver::query q(hostname, boost::lexical_cast<std::string>(x::tl::PORT_TMSRV));
boost::asio::ip::tcp::resolver::iterator res_begin = resolver.resolve(q), res_end;
if (res_begin != res_end)
{
// constructor client connection object
m_client_ptr.reset(new tm_client(m_ios, tm_window_notifier(*this)));
m_client_ptr->connect(*res_begin);
}
本文探讨了Asio库中使用shared_ptr的最佳实践方法,包括如何管理读写操作、处理错误、确保线程安全及维持数据的有效性等问题。


被折叠的 条评论
为什么被折叠?



