返回

ASIO-基础介绍

介绍BOOST.ASIO基本的使用方法

ASIO

Boost.Asio 是一个跨平台的、主要用于网络和其他一些底层输入/输出编程的 C++ 库。

Boost.Asio 在网络通信、COM 串行端口和文件上成功地抽象了输入输出的概念。你可以基于这些进行同步或者异步的输入输出编程。

作为一个跨平台的库,Boost.Asio 可以在大多数操作系统上使用。能同时支持数千个并发的连接。其网络部分的灵感来源于伯克利软件分发 ( BSD ) socket ,它提供了一套可以支持传输控制协议 ( TCP ) socket 、用户数据报协议 ( UDP ) socket 和 Internet 控制消息协议 ( IMCP ) socket 的 API ,而且如果有需要,你可以对其进行扩展以支持你自己的协议。

基本的使用

创建端点 endpoint

//客户端
int client_endpoint() {
	std::string raw_ip_address = "127.4.8.1";
	unsigned short port_num = 3333;
	boost::system::error_code ec;
	boost::asio::ip::address ip_address = boost::asio::ip::address::from_string(raw_ip_address, ec);
	if (ec.value() != 0) {
		std::cout << "Failed\n";
		return ec.value();
	}
	boost::asio::ip::tcp::endpoint ep(ip_address, port_num);
	return 0;
}
//服务端
int server_endpoint() {
	unsigned short port_num = 3333;
	//any表示可以接收任何v6的地址
	boost::asio::ip::address ip_address = boost::asio::ip::address_v6::any();
	boost::asio::ip::tcp::endpoint ep(ip_address, port_num);
	return 0;
}

服务端可以不指明地址,而是接收任何地址,如 tcp::v4() 、 tcp::v6() 等等。

创建 socket

boost::asio::io_context ioc;
boost::asio::ip::tcp::socket sock(ioc);

io_context ( 之前的版本是 io_service ) 对象是 asio 框架中的调度器,所有的异步事件都是通过它来分发处理的。

创建接收器 acceptor

int create_acceptor_socket() {
	boost::asio::io_context ioc;
	boost::asio::ip::tcp::acceptor acceptor(ioc,boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(),3333));
	return 0;
}

服务端打开端口,使用接收器监听,属于被动连接。

客户端通过端点进行连接

int client_connect_to_end() {
	std::string raw_ip_address = "192.168.1.124";
	unsigned short port_num = 3333;
	try {
		boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address::from_string(raw_ip_address), port_num);
		boost::asio::io_context ioc;
		boost::asio::ip::tcp::socket sock(ioc, ep.protocol());
		sock.connect(ep);
	}
	catch (boost::system::system_error& e) {
		std::cout << "Failed\n";
		return e.code().value();
	}
	return 0;
}

客户端主动连接指定端点的服务端。

客户端通过域名连接

int client_connect_to_end_by_dns() {
	//客户端不知道服务端地址,而是通过域名进行链接(少用)
	std::string host = "www.baidu.com";
	std::string port_num = "80";
	boost::asio::io_context ioc;
	//生成一个查询器
	boost::asio::ip::tcp::resolver::query query(host, port_num, boost::asio::ip::tcp::resolver::query::numeric_service);
	//生成解析器
	boost::asio::ip::tcp::resolver resolver(ioc);
	try {
		//解析 一个域名可能对应多个ip,所以解析器返回一个迭代器,指向解析到的所有ip
		boost::asio::ip::tcp::resolver::iterator it = resolver.resolve(query);
		boost::asio::ip::tcp::socket sock(ioc);
		//需要使用自由函数connect
		boost::asio::connect(sock, it);
	}
	catch (boost::system::system_error& e) {
		std::cout << "Failed\n";
		return e.code().value();
	}
}

服务端接收客户端连接

int acceptor_accept_connection() {
	//创建接收器后,接收器会监听客户端链接,未来得及处理的链接放在缓冲队列中
	//队列大小 由于tcp算法 实际数值大于30(或是30*2)
	const int BACKLOG_SIZE = 30;
	unsigned short port_num = 3333;
	boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address_v4::any(), port_num);
	boost::asio::io_context ioc;
	try {
		boost::asio::ip::tcp::acceptor acceptor(ioc, ep.protocol());
		acceptor.bind(ep);
		acceptor.listen(BACKLOG_SIZE);
		boost::asio::ip::tcp::socket sock(ioc);
		//接收器将收到的链接交给sock处理
		acceptor.accept(sock);
	}
	catch (boost::system::system_error& e) {
		std::cout << "Failed\n";
		return e.code().value();
	}
}

处理发送的数据

Boost.Asio 使用 asio::mutable_buffer 和 asio::const_buffer 这两个结构来提供 buffer ,它们是一段连续的空间,首字节存储了后续数据的长度。

asio::mutable_buffer 用于写服务,asio::const_buffer 用于读服务。但是这两个结构都没有被 Asio 的 api 直接使用。

对于 api 的 buffer 参数,Asio 提出了 MutableBufferSequence 和 ConstBufferSequence 概念,它们是由多个 asio::mutable_buffer 和 asio::const_buffer 组成的。也就是说boost::asio为了节省空间,将一部分连续的空间组合起来,作为参数交给api使用。

我们可以理解为 MutableBufferSequence 的数据结构为 std::vectorboost::asio::mutable_buffer

每个 vector 存储的都是 mutable_buffer 的地址,每个 mutable_buffer 的第一个字节表示数据的长度,后面跟着数据内容。

这么复杂的结构交给用户使用并不合适,所以 Asio 提出了 buffer() 函数,该函数接收多种形式的字节流,该函数返回 asio::mutable_buffers_1 或者 asio::const_buffers_1 结构的对象。

如果传递给 buffer() 的参数是一个只读类型,则函数返回 asio::const_buffers_1 类型对象。

如果传递给 buffer() 的参数是一个可写类型,则返回 asio::mutable_buffers_1 类型对象。

asio::const_buffers_1 和 asio::mutable_buffers_1 是 asio::mutable_buffer 和 asio::const_buffer 的适配器,提供了符合 MutableBufferSequence 和 ConstBufferSequence 概念的接口,所以它们可以作为 boost::asio 的 api 函数的参数使用。

简单概括一下,我们可以用 buffer() 函数生成我们要用的缓存存储数据。

比如 boost 的发送接口 send 要求的参数为 ConstBufferSequence 类型

template<typename ConstBufferSequence>
std::size_t send(const ConstBufferSequence & buffers);

我们需要将 hello World 转化为该类型

void use_const_buffer() {
	std::string buf{ "hello world" };
	//将字符串转化为constbuffer
	boost::asio::const_buffer asio_buf(buf.c_str(), buf.length());
	//将constbuffer存在vector里面
	std::vector<boost::asio::const_buffer> buffer_sequence;
	buffer_sequence.push_back(asio_buf);
	//这样在send read 函数就可以使用vector
	//send(buffer_sequence);
}

void use_buffer_str() {
	boost::asio::const_buffers_1 output_buf = boost::asio::buffer("hello world");
	//send(output_buf);
	//send(boost::asio::buffer("hello world")); 与上等价
}

void use_buffer_array() {
	const size_t BUF_SIZE_BYTES = 20;
	//使用智能指针管理动态内存
	std::unique_ptr<char[]> buf(new char[BUF_SIZE_BYTES]);
	auto output_buf = boost::asio::buffer(static_cast<void*>(buf.get()), BUF_SIZE_BYTES);
	//send(output_buf);
}

发送数据

void write_to_socket(boost::asio::ip::tcp::socket& sock) {
	std::string buf{ "hello world" };
	std::size_t total_bytes_written = 0;
	//循环发送
	while (total_bytes_written != buf.length()) {
		total_bytes_written += sock.write_some(boost::asio::buffer(
			buf.c_str() + total_bytes_written, buf.length() - total_bytes_written));
	}
}

接收数据

void read_from_socket(boost::asio::ip::tcp::socket& sock,std::size_t buf_len) {
	char data[1024];
    std::size_t total_bytes_written = 0;
	//循环读取
	while (total_bytes_written != buf_len) {
		total_bytes_written += sock.read_some(boost::asio::buffer(
			data + total_bytes_written, buf_len - total_bytes_written));
	}
}
Licensed under CC BY-NC-SA 4.0
Built with Hugo
Theme Stack designed by Jimmy