boost入门(二):Asio的简介

前言

经过上一篇文章《boost入门:boost简介》,我们编译了boost库。下面我们学习下Asio。

Asio是不需要编译成lib文件,直接在程序中引入头文件即可。Asio依赖的库比较多,我们可以看下它的依赖:

  1. Boost.System(必须)

  2. Boost.Coroutine(可选),如果用spawn()启动协程就需要它

  3. Boost.Regex(可选),如果使用带参数的read_until()或async_read_until()重载boost::regex

  4. OpenSSL(可选),如果你使用Boost.Asio的SSL支持

  5. Boost.Thread

  6. Boost.Date_Time

  7. Boost.Serialization

是不是很多,上面我们编译整个boost的时候已经生成了绝大多数,只剩下一个OpenSSL。

OpenSSL是不是编译很让人头疼,有个网址可以提供编译好的lib文件和dll文件。我也是偶尔间发现的,请看网址:Win32OpenSSL

上面是OpenSSL64位的安装包,下面是OpenSSL 32位的安装包。带Light是精简版本,建议下载不带Light的版本。下面是我本地电脑的安装目录:

lib文件存放着可链接的lib,我一般用静态链接:

运行示例,测试环境

我们演示两种,一种是自动链接到其他boost库的,比如:Boost.Coroutine,一种需要链接OpenSSL。

首先我们先链接Boost.Coroutine,无需再项目上添加链接库,直接将以下代码拷进项目里面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
//
// echo_server.cpp
// ~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2020 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/write.hpp>
#include <iostream>
#include <memory>

using boost::asio::ip::tcp;

class session : public std::enable_shared_from_this<session>
{
public:
explicit session(boost::asio::io_context& io_context, tcp::socket socket)
: socket_(std::move(socket)),
timer_(io_context),
strand_(io_context.get_executor())
{
}

void go()
{
auto self(shared_from_this());
boost::asio::spawn(strand_,
[this, self](boost::asio::yield_context yield)
{
try
{
char data[128];
for (;;)
{
timer_.expires_from_now(std::chrono::seconds(10));
std::size_t n = socket_.async_read_some(boost::asio::buffer(data), yield);
boost::asio::async_write(socket_, boost::asio::buffer(data, n), yield);
}
}
catch (std::exception& e)
{
socket_.close();
timer_.cancel();
}
});

boost::asio::spawn(strand_,
[this, self](boost::asio::yield_context yield)
{
while (socket_.is_open())
{
boost::system::error_code ignored_ec;
timer_.async_wait(yield[ignored_ec]);
if (timer_.expires_from_now() <= std::chrono::seconds(0))
socket_.close();
}
});
}

private:
tcp::socket socket_;
boost::asio::steady_timer timer_;
boost::asio::strand<boost::asio::io_context::executor_type> strand_;
};

int main(int argc, char* argv[])
{
try
{
if (argc != 2)
{
std::cerr << "Usage: echo_server <port>\n";
return 1;
}

boost::asio::io_context io_context;

boost::asio::spawn(io_context,
[&](boost::asio::yield_context yield)
{
tcp::acceptor acceptor(io_context,
tcp::endpoint(tcp::v4(), std::atoi(argv[1])));

for (;;)
{
boost::system::error_code ec;
tcp::socket socket(io_context);
acceptor.async_accept(socket, yield[ec]);
if (!ec)
{
std::make_shared<session>(io_context, std::move(socket))->go();
}
}
});

io_context.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}

return 0;
}

可以看到编译成功!

我们再尝试链接OpenSSL,首先在项目中设置openSSL的头文件目录:

设置链接库目录路径:

设置链接库:

然后拷贝代码到cpp文件中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
//
// client.cpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2020 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//

#include <cstdlib>
#include <cstring>
#include <functional>
#include <iostream>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>

using boost::asio::ip::tcp;
using std::placeholders::_1;
using std::placeholders::_2;

enum { max_length = 1024 };

class client
{
public:
client(boost::asio::io_context& io_context,
boost::asio::ssl::context& context,
const tcp::resolver::results_type& endpoints)
: socket_(io_context, context)
{
socket_.set_verify_mode(boost::asio::ssl::verify_peer);
socket_.set_verify_callback(
std::bind(&client::verify_certificate, this, _1, _2));

connect(endpoints);
}

private:
bool verify_certificate(bool preverified,
boost::asio::ssl::verify_context& ctx)
{
// The verify callback can be used to check whether the certificate that is
// being presented is valid for the peer. For example, RFC 2818 describes
// the steps involved in doing this for HTTPS. Consult the OpenSSL
// documentation for more details. Note that the callback is called once
// for each certificate in the certificate chain, starting from the root
// certificate authority.

// In this example we will simply print the certificate's subject name.
char subject_name[256];
X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256);
std::cout << "Verifying " << subject_name << "\n";

return preverified;
}

void connect(const tcp::resolver::results_type& endpoints)
{
boost::asio::async_connect(socket_.lowest_layer(), endpoints,
[this](const boost::system::error_code& error,
const tcp::endpoint& /*endpoint*/)
{
if (!error)
{
handshake();
}
else
{
std::cout << "Connect failed: " << error.message() << "\n";
}
});
}

void handshake()
{
socket_.async_handshake(boost::asio::ssl::stream_base::client,
[this](const boost::system::error_code& error)
{
if (!error)
{
send_request();
}
else
{
std::cout << "Handshake failed: " << error.message() << "\n";
}
});
}

void send_request()
{
std::cout << "Enter message: ";
std::cin.getline(request_, max_length);
size_t request_length = std::strlen(request_);

boost::asio::async_write(socket_,
boost::asio::buffer(request_, request_length),
[this](const boost::system::error_code& error, std::size_t length)
{
if (!error)
{
receive_response(length);
}
else
{
std::cout << "Write failed: " << error.message() << "\n";
}
});
}

void receive_response(std::size_t length)
{
boost::asio::async_read(socket_,
boost::asio::buffer(reply_, length),
[this](const boost::system::error_code& error, std::size_t length)
{
if (!error)
{
std::cout << "Reply: ";
std::cout.write(reply_, length);
std::cout << "\n";
}
else
{
std::cout << "Read failed: " << error.message() << "\n";
}
});
}

boost::asio::ssl::stream<tcp::socket> socket_;
char request_[max_length];
char reply_[max_length];
};

int main(int argc, char* argv[])
{
try
{
if (argc != 3)
{
std::cerr << "Usage: client <host> <port>\n";
return 1;
}

boost::asio::io_context io_context;

tcp::resolver resolver(io_context);
auto endpoints = resolver.resolve(argv[1], argv[2]);

boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23);
ctx.load_verify_file("ca.pem");

client c(io_context, ctx, endpoints);

io_context.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}

return 0;
}

编译该项目,下图显示编译成功:

这样完成了我们对环境的检测,说明Asio的环境没有问题。下面我们就接着去了解Asio库。

Boost.Asio库介绍

官网说的比较抽象:

Most programs interact with the outside world in some way, whether it be via a file, a network, a serial cable, or the console. Sometimes, as is the case with networking, individual I/O operations can take a long time to complete. This poses particular challenges to application development.

Boost.Asio provides the tools to manage these long running operations, without requiring programs to use concurrency models based on threads and explicit locking.

我的理解是Asio是一个 I\O 库,I\O通常指数据的输入和输出。

网络也可以当做一个输入与输出设备,Asio的重点在于网络,但是也可以用在串行端口、文件描述符等。

Linux提出一切皆文件,那么我们也可以认为一切皆I\O。(当然没有输入和输出,那就不是了)

Asio支持 I\O 上进行同步和异步操作。

Asio同步的原理

首先看图:

由图可以看出Asio的同步比较简单:

  1. 程序调用 I\O 对象
  2. I\O 对象调用 I\O 执行上下文
  3. I\O 对象调用操作系统
  4. 操作系统把结果返回给 I\O 执行上下文
  5. I\O 执行上下文如果有错误信息就会处理错误信息,然后把结果返回给 I\O 对象
  6. I\O对象会接收执行结果,如果有错误返回就会接收错误,并且不会有异常抛出

下面用代码介绍下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include <boost/asio.hpp>
int main()
{
// 同步操作
boost::asio::io_context io;// I\O执行上下文
// I\O对象,这是一个计时器的I\O对象
// 将在5秒后wait才会返回
boost::asio::steady_timer t(io, boost::asio::chrono::seconds(5));
//调用I\O对象的函数
t.wait();
std::cout << "Hello, world!" << std::endl;
return 0;
}

Asio异步的原理

还是老样子,看图:

由图可以看出:

  1. 程序调用I\O对象

  2. I\O对象调用I\O执行上下文

  3. I\O对象向操作系统发出信号,表示异步连接

  4. 操作系统完成该操作后,会把结果放在队列中

  5. 程序必须调用检索函数,来触发I\O上下文在队列中拾取结果

  6. I\O执行上下文拾取结果会使操作结果出队,将结果传给回调函数并调用回调函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include <boost/asio.hpp>
// 回调函数
void print(const boost::system::error_code& /*e*/)
{
std::cout << "Hello, world!" << std::endl;
}

int main()
{
//I\O执行上下文
boost::asio::io_context io;
//I\O对象
boost::asio::steady_timer t(io, boost::asio::chrono::seconds(5));
//调用I\O对象函数,并将回调函数地址传进去
t.async_wait(&print);

std::cout << "wait ..." << std::endl;

// 调用I\O执行上下文,检索结果是否完成,此处会阻塞当前线程
io.run();

std::cout << "over ..." << std::endl;
return 0;
}

如果我想要向回调函数传参呢?可以使用boost :: bind方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>

void print(const boost::system::error_code& /*e*/,
boost::asio::steady_timer* t, int* count)
{
if (*count < 5)
{
std::cout << *count << std::endl;
++(*count);
// 定时器过期时间往前移一秒
t->expires_at(t->expiry() + boost::asio::chrono::seconds(1));
// 重新调用I\O对象,并重新绑定回调函数和参数
t->async_wait(boost::bind(print,
boost::asio::placeholders::error, t, count));
}
}

int main()
{
// I\O执行上下文
boost::asio::io_context io;

int count = 0;
// I\O对象
boost::asio::steady_timer t(io, boost::asio::chrono::seconds(1));
//调用I\O对象函数,同时传参(绑定回调函数和参数)
t.async_wait(boost::bind(print,
boost::asio::placeholders::error, &t, &count));
//检索执行结果
io.run();

std::cout << "Final count is " << count << std::endl;

return 0;
}

结尾

此篇简单介绍了Asio,相信对Asio有了一点印象。接下来让我们继续深入Asio!

评论