C++之Boost Asio相关

Boost Asio相关

Boost是一个C++可移植库,是对标准库的后备拓展,也是C++标准化进程的开发引擎之一。

Boost Asio是Boost库中的一个部分,Asio的全称为Asynchronous input and output(异步输入输出)的缩写。结合Boost的特点,Asio提供了一套和平台无关的异步数据处理能力,当然它也支持同步数据处理。

使用Asio只需要引入一个头文件即可。

#include<boost/asio.hpp>

对于所有使用Asio的程序,都必须包含至少包含一个io_service对象。对于Asio这个Boost库而言,它抽象了诸如网络、串口通信等等这些概念,并将其统一为规为IO操作,所以io_service这个类提供了访问了I/O的功能,因此使用Asio时,必须定义:

boost::asio::io_service io;

HTTP连接

既然网络相关概念已经被抽象成IO,我们就只需要关系从IO流中获取消息,因此我们本质上还是在进行IO流中获取消息,因此我们本质上还是在进行IO操作,只是这些操作需要具备一些基本的网络概念。

HTTP 和 HTTPS 的底层实际上是使用的 TCP 可靠连接,通过 Socket 技术进行通信,而一个 Socket 由 IP 地址及端口构成。无例外地,Asio 同样也需要建立一个和 socket 有关的对象,那就是 boost::asio::ip::tcp::socket。可想而知,Socket 既然是网络通信的基础,那么自然的我们要进行的 IO 操作也就必须在这里完成,因此,我们定义的 boost::asio::ip::tcp::socket 对象,必须由 io_service 来进行构造,即:

boost::asio::ip::tcp::socket socket(io)

有了 socket 对象是不够的。在网络通信中,网络 IO 就入口串口一样,是以流的方式进行的。所以这个 socket 对象只能用来做我们日后进行 IO 操作时的一个必要属性。

不难看出,一个普通的 boost::asio::ip::tcp::socket 对象,实际上就是一个 HTTP 的 Socket 连接,因此我们在日后进行代码编写时,甚至于可以使用 typedef 将这个类型直接定义为 HTTP:

typedef boost::asio::ip::tcp::socket HTTP;

然而,作为服务端,我们可能构建很多很多的连接从而响应并发,所以当我们需要建立连接时候,就需要使用一个叫做 acceptor 的对象。

而 boost::asio::ip::tcp::acceptor 从名字上就可以看出,这个对象应该被用于建立连接。在 Boost 中,我们需要初始化一个 acceptor 对象时,必须提供一个 io_service 对象和一个 endpoint 对象。

那么 endpoint 又是什么?事实上,socket 是一个端到端的连接,所谓 endpoint 就是 socket 位于服务端的一个端点,我们知道,socket 是由 IP 地址和端口号组成的,那么当我们需要为其建立一个 IPv4 的网络,首先可以建立一个 boost::asio::ip::tcp::endpoint 对象:

unsigned short port = 8080;

boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port);

其中 boost::asio::ip::tcp::v4() 用于初始化一个 IPv4 的网络。最后在使用这个 endpoint 对象来初始化 acceptor:

boost::asio::ip::tcp::acceptor acceptor(io, endpoint);

至此,我们讨论了如何使用 Asio 建立一个普通的网络操作对象 acceptor,以及在进行普通 HTTP 网络操作时需要的 socket 对象。

HTTPS连接

讨论完了 Asio 里 HTTP 连接,我们再来看看 Asio 中的 HTTPS 是如何建立连接的。Asio 是一个开源的库,所以它也不可避免的在处理不擅长的逻辑时需要添加对别的框架的依赖。Asio 的 HTTPS 相关的 SSL 操作,就依赖了 OpenSSL 库。

要使用 SSL 相关的操作,还需要额外引入一个头文件:

#include <boost/asio/ssl.hpp>

我们在上一小节里讨论了 boost::asio::ip::tcp::socket 产生的 Socket 对象实际上就是普通的 HTTP 对象。对于 HTTPS 而言,实际上就是对这个 socket 所产生的通道进行一个一层封装和加密。在 Boost Asio 中,加密 socket 的方式就是使用 boost::asio::ssl::stream,并将 boost::asio::ip::tcp::socket 作为模板参数传入给这个对象,即:

typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> HTTPS;

而当我们要构造一个 HTTPS 的 socket 对象时,Boost Asio 要求必须为这个 socket 建立一个 boost::asio::ssl::context 对象。而一个 context 可以有很多不同的类型,最常用的,就是boost::asio::ssl::context::sslv23。构造好了 context 对象之后这还不够,因为一个 https 的服务器需要提供证书文件和秘钥文件,所以还需要使用 use_certificate_chain_file() 和 use_private_key_file() 这两个方法来进行进一步的配置:

context.use_certificate_chain_file(cert_file);

context.use_private_key_file(private_key_file, boost::asio::ssl::context::pem);

中的 boost::asio::ssl::context::pem 是指定的证书类型。因此相较于 HTTP 而言,HTTPS 的建立其实就是增加了对证书的配置、和 socket 加密的环节,对比如下:

// http

boost::asio::ip::tcp::socket http_socket(io);

// https

boost::asio::ssl::context context(boost::asio::ssl::context::sslv23);

context.use_certificate_chain_file(cert_file);

context.use_private_key_file(private_key_file, boost::asio::ssl::context::pem);

boost::asio::ssl::stream<boost::asio::ip::tcp::socket> https_socket(io, context);

I/O操作

上面我们讨论了如何建立连接,现在我们再来看看如何进行 IO 操作。

当我们有了 socket 对象之后,就可以从里面读取网络流数据了。读取数据时,我们需要定义一个流缓冲 boost::asio::streambuf 对象,用于逐行读取 socket 中的数据:

boost::asio::streambuf read_buffer;

另外,很多网络协议其实都是基于行实现的,也就是说这些协议元素是由 \r\n 符号进行界定,HTTP 也不例外,所以在 Boost Asio 中,读取使用分隔符的协议,可以使用 async_read_untile() 方法:

boost::asio::async_read_until(socket, readbuffer, "\r\n\r\n", read_handler);

其中 socket 就是我们的 socket 连接,而 readbuffer 就是根据界定符读取到的一行数据,"\r\n\r\n" 就是分隔符,而对于 read_handler 我们还需要再进一步讨论。

read_handler 是一个无返回类型的函数对象,它接受两个参数,一个是 boost::system::error_code,另一个是 size_t(bytes_transferred):

 
void read_handler(
    const boost::system::error_code& ec,
    std::size_t bytes_transferred)

{
  ...
}

boost::system::error_code 用来描述操作是否成功,而 size_t bytes_transferred 则是用来确定接受到的字节数,通常情况下,我们可以用 std::bind 来将参数绑定到我们的某个函数传入,但实际上我们还有更好的做法,那就是 lambda 表达式,因为 Lambda 表达式还具有另外的一个功能,那就是进行值捕获,对于这一点,我们在后面实现框架的时候再详细讨论。

在这个 read_handler 中,我们实际上是在不断的读取 socket 里面的内容,因此我们还需要使用 boost::asio::async_read 对后面的内容进行进一步的读取,而它的用法 和 boost::asio::async_read_until 几乎一样,唯一的区别就是在 read_handler 这个参数之前,需要指定 读取的长度,通常我们可以使用 boost::asio::transfer_exactly 进行指定,故这里不再详细赘述,我们在后面实现框架的时候,再详细讨论。

最后,我们完成了读取的操作,就只剩下最后一步了,那就是服务器响应请求,回写请求的资源供给客户端,这时候我们就需要使用另一个方法:boost::asio::async_write。从名字上可以看出,这个方法和 boost::asio::async_read 属于同一个方法家族,可想而知用法也完全类似,我们还是留到后面的实际代码中再进行讨论。

猜你喜欢

转载自www.cnblogs.com/wanghao-boke/p/12242539.html
今日推荐