异常处理VS错误代码
Boost.Asio允许同时使用异常处理或者错误代码,所有的异步函数都有抛出错误和返回错误码两种方式的重载。当函数抛出错误时,它经常抛出boost::system::system_error的错误。
using boost::asio;
ip::tcp::endpoint ep;
ip::tcp::socket sock(service);
sock.connect(ep); // Line 1
boost::system::error_code err;
sock.connect(ep, err); // Line 2
在前面的代码中,sock.connect(ep)会抛出错误,sock.connect(ep, err)则会返回一个错误码。
看一下下面的代码片段:
try {
sock.connect(ep);
} catch(boost::system::system_error e) {
std::cout << e.code() << std::endl;
}
下面的代码片段和前面的是一样的:
boost::system::error_code err;
sock.connect(ep, err);
if ( err)
std::cout << err << std::endl;
当使用异步函数时,你可以在你的回调里面检查其返回的错误码。异步函数从来不抛出异常,因为这样做毫无意义。那谁会捕获到它呢?
在你的异步函数中,你可以使用异常处理或者错误码(随心所欲),但要保持一致性。同时使用这两种方式会导致问题,大部分时候是崩溃(当你不小心出错,忘记去处理一个抛出来的异常时)。如果你的代码很复杂(调用很多socket读写函数),你最好选择异常处理的方式,把你的读写包含在一个函数try {} catch块里面。
void client_session(socket_ptr sock) {
try {
...
} catch ( boost::system::system_error e) {
// handle the error
}
}
如果使用错误码,你可以使用如下的代码片段很好地看到连接是何时关闭的:
char data[512];
boost::system::error_code error;
size_t length = sock.read_some(buffer(data), error);
if (error == error::eof)
return; // Connection closed
Boost.Asio的所有错误码都包含在boost::asio::error的命名空间中(以便你创造一个大型的switch来检查错误的原因)。如果想要了解更多的细节,请参照boost/asio/error.hpp头文件。
Boost.Asio中的线程
当说到Boost.Asio的线程时,我们经常讨论:
io_service:io_service是线程安全的。几个线程可以同时调用io_service::run()。大多数情况下你可能在一个必须等待所有异步操作完成之后才能继续执行的单线程函数中调用io_service::run()。然而,你其实可以在多个线程中调用io_service::run()。这会阻塞所有调用io_service::run()的线程。只要线程中的任何一个调用了io_service::run(),所有的回调都会同时被调用;这也就意味着,当你在一个线程中调用io_service::run()时,所有的回调都被调用了。
socket:socket类不是线程安全的。所以,你要避免在某个线程里读一个socket时,同时在另外一个线程里面对其进行写入操作。(通常来说这种操作都是不推荐的,更别说Boost.Asio)。
utility:就utility来说,因为它不是线程安全的,所以通常也不提倡在多个线程里面同时使用。里面的方法经常只是在很短的时间里面使用一下,然后就释放了。
除了你自身的线程,Boost.Asio本身也包含几个线程。但是能保证那些线程不会调用你的代码。这也意味着,只有调用了io_service::run()方法的线程才会调用回调函数。
不仅仅是网络通信
除了网络通信,Boost.Asio还包含了其他的I/O功能。
Boost.Asio支持信号量,比如SIGTERM(软件终止)、SIGINT(中断信号)、SIGSEGV(段错误)等等。
你可以创建一个signal_set实例,指定异步等待的信号量,然后当这些信号量产生时,就会调用你的异步处理程序:
void signal_handler(const boost::system::error_code & err, int signal)
{
// log this, and terminate application
}
boost::asio::signal_set sig(service, SIGINT, SIGTERM);
sig.async_wait(signal_handler);
如果SIGINT产生,你就能在你的signal_handler回调中捕获到它。
你可以使用Boost.Asio轻松地连接到一个串行端口。在Windows上端口名称是COM7,在POSIX平台上是/dev/ttyS0。
io_service service;
serial_port sp(service, "COM7");
打开端口后,你就可以使用下面的代码设置一些端口选项,比如端口的波特率、奇偶校验和停止位。
serial_port::baud_rate rate(9600);
sp.set_option(rate);
打开端口后,你可以把这个串行端口看做一个流,然后基于这点,使用自由函数对串行端口进行读/写操作。比如async_read(), write, async_write(), 就像下面的代码片段:
char data[512];
read(sp, buffer(data, 512));
Boost.Asio也可以连接到Windows的文件,然后同样使用自由函数,比如read(), asyn_read()等等,像下面的代码片段:
HANDLE h = ::OpenFile(...);
windows::stream_handle sh(service, h);
char data[512];
read(h, buffer(data, 512));
对于POXIS文件描述符,比如管道,标准I/O和各种设备(但不包括普通文件)你也可以这样做,就像下面的代码所做的一样:
posix::stream_descriptor sd_in(service, ::dup(STDIN_FILENO));
char data[512];
read(sd_in, buffer(data, 512));
计时器
一些I/O操作需要一个完成截止时间。你只能在异步操作上进行应用(同步意味着阻塞,因此没有截止时间)。例如,你的下一条信息必须在100毫秒内从你的同伴传递给你。
bool read = false;
void deadline_handler(const boost::system::error_code &) {
std::cout << (read ? "read successfully" : "read failed") <<
std::endl;
}
void read_handler(const boost::system::error_code &) {
read = true;
}
ip::tcp::socket sock(service);
…
read = false;
char data[512];
sock.async_read_some(buffer(data, 512));
deadline_timer t(service, boost::posix_time::milliseconds(100));
t.async_wait(&deadline_handler);
service.run();
在上一个代码片段中,如果你在截止时间之前读完了数据,read则被设置成true,如此我们的伙伴就及时地通知了我们。否则,当deadline_handler被调用时,read还是false,也就意味着我们没有满足我们的截止时间。
Boost.Asio也支持同步计时器,当时他们通常和一个简单的sleep操作是一样的。boost::this_thread::sleep(500);这段代码和下面的代码片段完成了同一件事情:
deadline_timer t(service, boost::posix_time::milliseconds(500));
t.wait();