0%

[boost.asio] 4.Overview (2)

流、短读和短写(Streams, Short Reads and Short Writes)#

Boost.Asio中許多 I/0对象都是面向流的: - 没有消息边界。数据通过连续字节序列传输 - 读取或写入操做传输的数据可能少于请求的字节。这就叫做 短读(short read) or 短写(short write)。

提供面向流的I/O模型满足一个或多个如下要求: - SyncReadStream,使用read_some()的成员函数执行。 - AsyncReadStream,使用async_read_some()的成员函数执行。 - SyncWriteStream,使用write_some()的成员函数执行。 - AsyncWriteStream,使用async_write_some()的成员函数执行。

面向流的I/O对象有:ip::tcp::socket, ssl::stream<>, posix::stream_descriptor, windows::stream_handle 等

当短读(short read) or 短写(short write)出现时,程序必须重新启动操作,直到传输了所需的字数。Boost.Asio 提供自动执行此操作的常规函数:read()、async_read()、write()和async_write()。

Why EOF is an Error#

  • stream结束符可以导致 read, async_read, read_until or async_read_until函数终止。例如,由于 EOF,读取 N 个字节可能会提前完成。
  • EOF 错误可用于区分流的末尾和大小为 0 的成功读取。

Reactor式操作(Reactor-Style Operations)#

有时需要集成使用自定义I/0操作的第三方库。Boost.Asio 包括同步和异步操作,这些操作可用于等待socket准备好读取、准备写入或出现挂起的错误情况。下面是一个非阻塞读的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ip::tcp::socket socket(my_io_context);
...
socket.non_blocking(true);
...
socket.async_wait(ip::tcp::socket::wait_read, read_handler);
...
void read_handler(boost::system::error_code ec)
{
if (!ec)
{
std::vector<char> buf(socket.available());
socket.read_some(buffer(buf));
}
}
这些操作支持所有平台的socket,以及 POSIX 的面向流的描述符类。

基于行的操作(Line-Based Operations)#

许多常用的 internet 协议是基于行的,它们具有由字符序列“”分隔的元素,如 HTTP、SMTP 和 FTP。为了更容易地实现基于行的协议,Boost.Asio 提供了函数 read_until()和async_read_until()。 如下是在一个HTTP服务器使用 async_read_until() 的例子:

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
class http_connection
{
...

void start()
{
boost::asio::async_read_until(socket_, data_, "\r\n",
boost::bind(&http_connection::handle_request_line, this, _1));
}

void handle_request_line(boost::system::error_code ec)
{
if (!ec)
{
std::string method, uri, version;
char sp1, sp2, cr, lf;
std::istream is(&data_);
is.unsetf(std::ios_base::skipws);
is >> method >> sp1 >> uri >> sp2 >> version >> cr >> lf;
...
}
}

...

boost::asio::ip::tcp::socket socket_;
boost::asio::streambuf data_;
};
streambuf数据成员用作存储在搜索分隔符(delimiter)之前从套接字读取的数据的位置。分隔符后可能还有其他数据。剩余的数据应该留在streambuf中,以便后续的read_until()或async_read_until()处理。

分隔符可以指定为单个char、std::string或boost::regex。read_until()和async_read_until()函数还接受称为match condition的用户自定义函数对象的重载。例如,将数据读取到streambuf,直到遇到空白:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
typedef boost::asio::buffers_iterator<
boost::asio::streambuf::const_buffers_type> iterator;

std::pair<iterator, bool> match_whitespace(iterator begin, iterator end)
{
iterator i = begin;
while (i != end)
if (std::isspace(*i++))
return std::make_pair(i, true);
return std::make_pair(i, false);
}
...
boost::asio::streambuf b;
boost::asio::read_until(s, b, match_whitespace);
要将数据读取到streambuf中,直到找到匹配的字符:
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
class match_char
{
public:
explicit match_char(char c) : c_(c) {}

template <typename Iterator>std::pair<Iterator, bool> operator()(
Iterator begin, Iterator end) const
{
Iterator i = begin;
while (i != end)
if (c_ == *i++)
return std::make_pair(i, true);
return std::make_pair(i, false);
}

private:
char c_;
};

namespace boost {
namespace asio {
template <> struct is_match_condition<match_char>: public boost::true_type {};
} } // namespace boost::asio
...
boost::asio::streambuf b;
boost::asio::read_until(s, b, match_char('a'));
对于函数和typedef的嵌套result_type类型的函数对象,is_match_condition<>类型特征会自动计算为 true。对于其他类型的特征必须显示特化(explicitly specialised)。

自定义内存分配(Custom Memory Allocation)#

许多异步操作需要分配一个对象去存储操作的状态。 另外,程序中包含了易于识别的异步操作链。 半双工协议(如HTTP服务器)对每个客户端连接维护一个有单向操作链(先接收后发送)。 全双工协议有两条并行的链。程序应该能够根据这些特性,为链中的所有异步操作重用这些内存。

对于用户自定义的Handler对象的副本 h,若需要分配与该处理程序关联的内存,这将有一个使用get_associated_allocator的分配器(allocator)。例如

1
boost::asio::associated_allocator_t<Handler> a = boost::asio::get_associated_allocator(h);
默认情况下,处理程序使用标准分配器(按照::operator new()和::operator delete()实现)。可以通过指定嵌套类型allocator_type和成员函数get_allocator(),为特定处理程序类型自定义分配器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class my_handler
{
public:
// Custom implementation of Allocator type requirements.
typedef my_allocator allocator_type;

// Return a custom allocator implementation.
allocator_type get_allocator() const noexcept
{
return my_allocator();
}

void operator()() { ... }
};
在更复杂的情况下,associated_allocator模板可能直接偏特化(partially specialised):
1
2
3
4
5
6
7
8
9
10
11
12
namespace boost { namespace asio {
template <typename Allocator> struct associated_allocator<my_handler, Allocator>
{
// Custom implementation of Allocator type requirements.
typedef my_allocator type;
// Return a custom allocator implementation.
static type get(const my_handler&, const Allocator& a = Allocator()) noexcept
{
return my_allocator();
}
};
} } // namespace boost::asio
- 这保证了释放将在调用associated handler之前发生,这意味着内存已经准备好被重用于handler启动的任何新的异步操作。

  • 该实现保证,对于库中包含的异步操作,该实现不会对内存分配函数进行并发调用。如果需要从不同线程调用分配函数,将插入适当的内存屏障,以确保内存正确。