asio网络编程

asio网络编程

基于C++11,boost1.87版本。boost手册:官网参考手册

  • 定义

    • Asio 是一个跨平台的 C++ 库,用于网络和低级别 I/O 编程。它提供了一个基于事件的编程模型和异步 I/O 操作来开发可扩展的网络应用程序。Asio 库最初是作为 Boost.Asio 开发的,后来也被纳入了 C++ 标准库(C++20)的网络库提案中。
  • 主要用途

    • 它主要用于开发高性能的网络服务器和客户端。例如,可以用于构建 Web 服务器、聊天服务器、游戏服务器等。同时,它也可以用于处理文件 I/O、定时器等低级别 I/O 操作。
  • 主要特性

    • 跨平台
    • 异步I/O模型
    • 基于事件的编程
    • 丰富的网络协议支持
  • 核心组件

    • I/O服务(io_context​)
    • 套接字(socket​)
    • 定时器
    • 缓冲区

asio socket的创建和使用

#include<boost/asio.hpp>
#include<iostream>
using namespace boost;

/*	创建端点endpoint,该对象相当于linux socket中的struct sockaddr
   	接收ip地址和端口号作为参数	*/
int client_end_point()
{
	std::string raw_ip_address = "127.0.0.1";
	unsigned short port_num = 3333;
	boost::system::error_code ec;
	asio::ip::address ip_address = asio::ip::make_address(raw_ip_address);
	if (ec.value() != 0)
	{
		std::cout<< "Failed to parse the IP address. Error code = " << ec.value()
			<< "Message is" << ec.message() << std::endl;
	}

	asio::ip::tcp::endpoint ep(ip_address, port_num);

	return 0;
}

int server_end_point()
{
	unsigned short port_num = 3333;
	asio::ip::address ip_address = asio::ip::address_v6::any();
	asio::ip::tcp::endpoint ep(ip_address, port_num);

	return 0;
}

/* 	创建tcp socket,任何需要IO操作的对象都需要io_context对象作为参数,
   	个人理解其相当于linux socket中的文件描述符
   	不绑定地址的话,只需要io_context和ip协议版本就可以创建完成一个socket	*/
int create_tcp_socket()
{
	asio::io_context ioc;
	asio::ip::tcp protocol = asio::ip::tcp::v4();
	asio::ip::tcp::socket sock(ioc);
	boost::system::error_code ec;
	sock.open(protocol, ec);
	if (ec.value() != 0)
	{
		std::cout << "Failed to open the socket. Error code = " << ec.value()
			<< "Message is" << ec.message() << std::endl;
		return ec.value();
	}

	return 0;
}

/* 	创建acceptor socket,acceptor相当于linux socket中的监听socket
   	acceptor创建需要io_context,ip协议版本,ip地址和端口四个要素
   	ip地址和端口以endpoint对象的形式存在	*/
int create_acceptor_socket_old()
{
	
	asio::io_context ioc;
	asio::ip::tcp::acceptor acceptor(ioc);
	asio::ip::tcp protocol = asio::ip::tcp::v4();
	boost::system::error_code ec;
	acceptor.open(protocol, ec);
	if (ec.value() != 0)
	{
		std::cout << "Failed to open the acceptor socket. Error code = " << ec.value()
			<< ". Message is" << ec.message() << std::endl;
		return ec.value();
	}

	unsigned short port_num = 3333;
	asio::ip::address ip_address = asio::ip::address_v6::any();
	asio::ip::tcp::endpoint ep(ip_address, port_num);
	acceptor.bind(ep, ec);
	if (ec.value() != 0)
	{
		std::cout << "Failed to bind the acceptor socket. Error code = " << ec.value()
			<< "Message is" << ec.message() << std::endl;
		return ec.value();
	}
	return 0;
}

/* 新版boost库中提供了一步到位创建acceptor的构造函数	*/
int create_acceptor_socket()
{
	asio::io_context ioc;
	asio::ip::tcp::acceptor a(ioc, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), 3333));

	return 0;
}

/* 	asio中的socket对象拥有成员函数connect,调用该函数,
	并且以endpoint作为参数就可以将socket连接到对应ip地址和端口	*/
int connect_to_end()
{
	std::string raw_ip_address = "192.168.1.122";
	unsigned short port_num = 3333;
	try 
	{
		asio::ip::tcp::endpoint ep(asio::ip::make_address(raw_ip_address), port_num);
		asio::io_context ioc;
		asio::ip::tcp::socket sock(ioc, ep.protocol());
		sock.connect(ep);
	}
	catch(system::system_error& e)
	{
		std::cout << "Error occured! Error code = " << e.code()
			<< ". Message: " << e.what() << std::endl;
	}
}

/*	有时可能存在要从通过域名+端口号去连接的情况,可以通过resolver对象来完成从
	域名和端口号中解析出ip地址和端口号
	实际上基本没有该需求*/
int dns_connect_to_end()
{
	std::string host = "xu3.com";
	std::string port_num = "3333";
	asio::io_context ioc;
	asio::ip::tcp::resolver resolver(ioc);
	try
	{
		auto endpoint = resolver.resolve(host, port_num);
		asio::ip::tcp::socket sock(ioc);
		asio::connect(sock, endpoint);
	}
	catch (system::system_error& e)
	{
		std::cout << "Error occured! Error code = " << e.code()
			<< ". Message: " << e.what() << std::endl;
	}
}

/*	与任何其他的socket编程一样,服务端监听到客户端连接后,需要创建一个新的
	socket来与客户端连接,asio中通过acceptor对象的成员函数accept接收一个
	新的socket对象作为参数,来完成将该socket与客户端连接*/
int accept_new_connection()
{
	const int BACKLOG_SIZE = 30;
	unsigned short port_num = 3333;
	asio::ip::tcp::endpoint ep(asio::ip::address_v4::any(), port_num);
	asio::io_context ioc;
	try
	{
		asio::ip::tcp::acceptor acceptor(ioc, ep.protocol());
		acceptor.bind(ep);
		acceptor.listen(BACKLOG_SIZE);
		asio::ip::tcp::socket sock(ioc);
		acceptor.accept(sock);
	}
	catch (system::system_error& e)
	{
		std::cout << "Error occured! Error code = " << e.code()
			<< ". Message: " << e.what() << std::endl;
	}

	return 0;
} 

/*	asio的socket读写数据只能使用ConstBufferSequence结构,该结构实质上是一个存储了
	1个或多个const_buffer和mutable_buffer两种数据结构的vector
	前者是只读类型,后者是可写类型,以下是将一个字符串转换成可用的buffer的过程*/
void use_const_buffer()
{
	std::string buf = "hello world";
	asio::const_buffer asio_buf(buf.c_str(), buf.length());
	std::vector<asio::const_buffer> buffers_sequence;
	buffers_sequence.push_back(asio_buf);
}

/*	asio中提供了便捷的全局函数来转换字符串为可用的buffer类型,注意:
	asio::buffer自适应的返回const_buffer或是mutable_buffer*/
void use_buffer_str()
{
	auto output_buf = asio::buffer("hello world");
}

/*	字符数组作为asio::buffer函数参数时,需要转换成void*指针*/
void use_buffer_array()
{
	const size_t BUF_SIZE_BYTES = 20;
	std::unique_ptr<char[]> buf(new char[BUF_SIZE_BYTES]);
	auto input_buf = asio::buffer(static_cast<void*>(buf.get()), BUF_SIZE_BYTES);
}

/*	write_some能将buffer类型的数据写入socket,
	并且返回该次实际写入的字节数,需要注意
	的是write_some并不记录写到哪了,再次调用
	并不会在之前的基础上接着写,因此我们需要根据
	每次调用write_some的返回值自己调整下一次
	调用write_some时输入的参数。
	函数调用将阻塞,直到一个或多个数据字节已成功写入,或者直到发生错误。。*/
void write_to_socket(asio::ip::tcp::socket& sock)
{
	std::string buf = "hello world";
	std::size_t total_bytes_written = 0;
	//write_some返回每次写入的字节数
	while (total_bytes_written != buf.length())
	{
		// write_some返回值为成功写入的字节数
		total_bytes_written += sock.write_some(asio::buffer(buf.c_str()+total_bytes_written, buf.length()-total_bytes_written));
	}
}

int send_data_by_write_some()
{
	std::string raw_ip_address = "192.168.3.122";
	unsigned short port_num = 3333;
	try
	{
		asio::ip::tcp::endpoint ep(asio::ip::make_address(raw_ip_address), port_num);
		asio::io_context ioc;
		asio::ip::tcp::socket sock(ioc, ep.protocol());
		sock.connect(ep);
		write_to_socket(sock);
	}
	catch (system::system_error& e)
	{
		std::cout << "Error occured! Error code = " << e.code()
			<< ". Message: " << e.what() << std::endl;
	}

	return 0;
}

/*	它会阻塞直到数据全部写完才返回*/
int send_data_by_send()
{
	std::string raw_ip_address = "192.168.3.122";
	unsigned short port_num = 3333;
	try
	{
		asio::ip::tcp::endpoint ep(asio::ip::make_address(raw_ip_address), port_num);
		asio::io_context ioc;
		asio::ip::tcp::socket sock(ioc, ep.protocol());
		sock.connect(ep);
		std::string buf = "hello world";
		int send_length = sock.send(asio::buffer(buf.c_str(), buf.length()));  
		// send会阻塞直到发送完成,返回值:-1-报错,0-对端关闭,>0-发送成功且值等于缓冲区长度
		if (send_length <= 0)
		{
			std::cout << "发送失败" << std::endl;
			return -1;
		}
	}
	catch (system::system_error& e)
	{
		std::cout << "Error occured! Error code = " << e.code()
			<< ". Message: " << e.what() << std::endl;
	}

	return 0;
}

/*	write与send的功能一样,区别在于write是全局函数,send是socket的成员函数*/
int send_data_by_write()
{
	std::string raw_ip_address = "192.168.3.122";
	unsigned short port_num = 3333;
	try
	{
		asio::ip::tcp::endpoint ep(asio::ip::make_address(raw_ip_address), port_num);
		asio::io_context ioc;
		asio::ip::tcp::socket sock(ioc, ep.protocol());
		sock.connect(ep);
		std::string buf = "hello world";
		int send_length = asio::write(sock, asio::buffer(buf.c_str(), buf.length()));
		// write会阻塞直到发送完成,返回值:-1-报错,0-对端关闭,>0-发送成功且值等于缓冲区长度
		if (send_length <= 0)
		{
			std::cout << "发送失败" << std::endl;
			return -1;
		}
	}
	catch (system::system_error& e)
	{
		std::cout << "Error occured! Error code = " << e.code()
			<< ". Message: " << e.what() << std::endl;
	}

	return 0;
}

/*	read_some的功能是阻塞直到读取数据到buffer中,
	返回读取到的数据字节数*/
std::string read_from_socket(asio::ip::tcp::socket& sock)
{
	const unsigned char MESSAGE_SIZE = 7;
	char buf[MESSAGE_SIZE];
	std::size_t total_bytes_read = 0;
	while (total_bytes_read != MESSAGE_SIZE)
	{
		total_bytes_read += sock.read_some(asio::buffer(buf + total_bytes_read, MESSAGE_SIZE - total_bytes_read));
	}

	return std::string(buf, total_bytes_read);
}

int read_data_by_read_some()
{
	std::string raw_ip_address = "127.0.0.1";
	unsigned short port_num = 3333;
	try
	{
		asio::ip::tcp::endpoint ep(asio::ip::make_address(raw_ip_address), port_num);
		asio::io_context ioc;
		asio::ip::tcp::socket sock(ioc, ep.protocol());
		sock.connect(ep);
		read_from_socket(sock);
	}
	catch (system::system_error& e)
	{
		std::cout << "Error occured! Error code = " << e.code()
			<< ". Message: " << e.what() << std::endl;
	}

	return 0;
}

/*	receive会阻塞直到读完全部数据,返回读取到的字节数*/
int read_data_by_receive()
{
	std::string raw_ip_address = "127.0.0.1";
	unsigned short port_num = 3333;
	try
	{
		asio::ip::tcp::endpoint ep(asio::ip::make_address(raw_ip_address), port_num);
		asio::io_context ioc;
		asio::ip::tcp::socket sock(ioc, ep.protocol());
		sock.connect(ep);
		const unsigned char BUFF_SIZE = 7;
		char buffer_receive[BUFF_SIZE];
		int receive_length = sock.receive(asio::buffer(buffer_receive, BUFF_SIZE));
		if (receive_length <= 0)
			std::cout << "receive failed" << std::endl;
	}
	catch (system::system_error& e)
	{
		std::cout << "Error occured! Error code = " << e.code()
			<< ". Message: " << e.what() << std::endl;
	}

	return 0;
}

/*	read跟receive的功能相同,区别在于read是全局函数*/
int read_data_by_read()
{
	std::string raw_ip_address = "127.0.0.1";
	unsigned short port_num = 3333;
	try
	{
		asio::ip::tcp::endpoint ep(asio::ip::make_address(raw_ip_address), port_num);
		asio::io_context ioc;
		asio::ip::tcp::socket sock(ioc, ep.protocol());
		sock.connect(ep);
		const unsigned char BUFF_SIZE = 7;
		char buffer_receive[BUFF_SIZE];
		int receive_length = asio::read(sock, asio::buffer(buffer_receive, BUFF_SIZE));
		if (receive_length <= 0)
			std::cout << "receive failed" << std::endl;
	}
	catch (system::system_error& e)
	{
		std::cout << "Error occured! Error code = " << e.code()
			<< ". Message: " << e.what() << std::endl;
	}

	return 0;
}

同步读写Server和Client

以下是同步读写的demo示例,服务端实现了回写的功能。

Server

#include<iostream>
#include<boost/asio.hpp>
#include<set>
#include<memory>
#include<thread>


using boost::asio::ip::tcp;
const int max_length = 1024;
using socket_ptr = std::shared_ptr<tcp::socket>;
std::set<std::shared_ptr<std::thread>> thread_set;
using namespace std;

void session(socket_ptr sock)
{
	try
	{
		for (;;)
		{
			char data[max_length];
			memset(data, '\0', max_length);  // 字符'\0'等于整数0,字符'0'等于整数48
			boost::system::error_code error;
			size_t length = sock->read_some(boost::asio::buffer(data, max_length), error);
			if (error == boost::asio::error::eof)
			{
				cout << "connection closed by peer" << endl;
				break;
			}
			else if (error)
			{
				throw boost::system::system_error(error);
			}

			cout << "receive from " << sock->remote_endpoint().address().to_string() << endl;
			cout << "receive messege is " << data << endl;
			boost::asio::write(*sock, boost::asio::buffer(data, length));
		}
	}
	catch (std::exception& e)
	{
		std::cerr << "Exception in thread: " << e.what() << "\n" << endl;
	}
}

void server(boost::asio::io_context& io_context, unsigned short port)
{
	tcp::acceptor a(io_context, tcp::endpoint(tcp::v4(), port));
	for (;;)
	{
		socket_ptr socket(new tcp::socket(io_context));
		a.accept(*socket);
		auto t = std::make_shared<std::thread>(session, socket);
		thread_set.insert(t);
	}
}

int main()
{
	try
	{
		boost::asio::io_context ioc;
		server(ioc, 8000);
	}
	catch (std::exception& e)
	{
		cerr << "Exception " << e.what() << endl;
	}
	return 0;
}

Client

#include<boost/asio.hpp>
#include<iostream>
using namespace boost::asio::ip;
using namespace std;
const int MAX_LENGTH = 1024;

int main()
{
	try
	{
		// 创建上下文服务
		boost::asio::io_context ioc;
		// 构造endpoint
		tcp::endpoint remote_ep(make_address("127.0.0.1"), 8000);
		tcp::socket sock(ioc);
		boost::system::error_code error = boost::asio::error::host_not_found;
		sock.connect(remote_ep, error);
		if (error)
		{
			cout << "connect failed, code is " << error.value() << "error msg is "
				<< error.message() << endl;
			return 0;
		}

		cout << "Eneer message: ";
		char request[MAX_LENGTH];
		std::cin.getline(request, MAX_LENGTH);
		size_t request_length = strlen(request);
		boost::asio::write(sock, boost::asio::buffer(request, request_length));

		char reply[MAX_LENGTH];
		size_t reply_length = boost::asio::read(sock, boost::asio::buffer(reply, request_length));
		cout << "Reply is: ";
		cout.write(reply, reply_length);
		cout << endl;
	}
	catch(std::exception& e)
	{
		std::cerr << "Exception: " << e.what() << endl;
	}
}

异步API介绍

以下以通过实现一个Session类的读和写函数来展示asio的异步API。

Session.h

#pragma once
#include<memory>
#include<boost/asio.hpp>
#include<iostream>
#include<string>
#include<queue>

using namespace std;
using namespace boost;

const int RECVSIZE = 1024;

class MsgNode
{
public:
	MsgNode(const char* msg, int total_len):_total_len(total_len),_cur_len(0)
	{ 
		_msg = new char[total_len];
		memcpy(_msg, msg, total_len);
	}

	MsgNode(int total_len) :_total_len(total_len), _cur_len(0)
	{
		_msg = new char[total_len];
	}

	~MsgNode()
	{
		delete[] _msg;
	}

public:
	int _total_len;
	int _cur_len;
	char* _msg;
};

class Session
{
public:
	Session(std::shared_ptr<asio::ip::tcp::socket> socket);
	void Connect(const asio::ip::tcp::endpoint& ep);
	void WriteCallBackErr(const boost::system::error_code& ec, std::size_t bytes_transferred,
		std::shared_ptr<MsgNode>);
	void WriteToSocketErr(const std::string buf);
	void WriteCallBack(const boost::system::error_code& ec, std::size_t bytes_transferred);
	void WriteToSocket(const std::string& buf);

	void WriteAllToSocket(const std::string& buf);
	void WriteAllCallBack(const boost::system::error_code& ec, std::size_t bytes_transferred);

	void ReadFromSocket();
	void ReadCallBack(const boost::system::error_code& ec, std::size_t bytes_transferred);
	
	void ReadAllFromSocket();
	void ReadAllCallBack(const boost::system::error_code& ec, std::size_t bytes_transferred);
private:
	std::queue<std::shared_ptr<MsgNode>> _send_queue;
	std::shared_ptr<asio::ip::tcp::socket> _socket;
	std::shared_ptr<MsgNode> _send_node;
	bool _send_pending;

	std::shared_ptr<MsgNode> _recv_node;
	bool _recv_pending;
};

Session.cpp

#include"Session.h"

Session::Session(std::shared_ptr<asio::ip::tcp::socket> socket):_socket(socket), _send_pending(false)
{
}

void Session::Connect(const asio::ip::tcp::endpoint& ep)
{
}

void Session::WriteCallBackErr(const boost::system::error_code& ec, std::size_t bytes_transferred, std::shared_ptr<MsgNode> msg_node)
{
	if (bytes_transferred + msg_node->_cur_len < msg_node->_total_len)
	{
		_send_node->_cur_len += bytes_transferred;
		this->_socket->async_write_some(asio::buffer(_send_node->_msg + _send_node->_cur_len,
			_send_node->_total_len - _send_node->_cur_len),
			std::bind(&Session::WriteCallBackErr, this, std::placeholders::_1, std::placeholders::_2,
				_send_node));
	}
}

void Session::WriteToSocketErr(const std::string buf)
{
	auto _send_node = make_shared<MsgNode>(buf.c_str(), buf.length());
	this->_socket->async_write_some(asio::buffer(_send_node->_msg, _send_node->_total_len),
		std::bind(&Session::WriteCallBackErr, this, std::placeholders::_1, std::placeholders::_2,
			_send_node));
}

void Session::WriteCallBack(const boost::system::error_code& ec, std::size_t bytes_transferred)
{
	if (ec.value() != 0)
	{
		std::cout << "Error, code is " << ec.value() << " . Message is" << ec.message() << endl;
		return;
	}

	auto& send_data = this->_send_queue.front();
	send_data->_cur_len += bytes_transferred;
	if (send_data->_cur_len < send_data->_total_len)
	{
		this->_socket->async_write_some(asio::buffer(send_data->_msg + send_data->_cur_len,
			send_data->_total_len - send_data->_cur_len),
			std::bind(&Session::WriteCallBack, this, std::placeholders::_1, std::placeholders::_2));
	}
	else
	{
		_send_queue.pop();

		if (_send_queue.empty())
			_send_pending = false;

		if (!_send_queue.empty())
		{
			auto& send_data = _send_queue.front();
			this->_socket->async_write_some(asio::buffer(send_data->_msg, send_data->_total_len),
				std::bind(&Session::WriteCallBack, this, std::placeholders::_1, std::placeholders::_2));
		}
	}
}

void Session::WriteToSocket(const std::string& buf)
{
	_send_queue.emplace(new MsgNode(buf.c_str(), buf.length()));
	if (_send_pending)
		return;

	this->_socket->async_write_some(asio::buffer(buf),
		std::bind(&Session::WriteCallBack, this, std::placeholders::_1, std::placeholders::_2));
	_send_pending = true;
}

void Session::WriteAllToSocket(const std::string& buf)
{
	_send_queue.emplace(new MsgNode(buf.c_str(), buf.length()));
	if (_send_pending)
		return;
	this->_socket->async_send(asio::buffer(buf.c_str(), buf.length()),
		std::bind(&Session::WriteAllCallBack, this, std::placeholders::_1, std::placeholders::_2));
	_send_pending = true;
}

void Session::WriteAllCallBack(const boost::system::error_code& ec, std::size_t bytes_transferred)
{
	if (ec.value() != 0)
	{
		std::cout << "Error occured! Error code = "
			<< ec.value()
			<< ". Message: " << ec.message();
	}
	else
	{
		_send_queue.pop();
		if (_send_queue.empty())
		{
			_send_pending = false;
		}
		else
		{
			auto& send_data = _send_queue.front();  // 使用auto&来推导智能指针,因为智能指针通常不允许拷贝,而auto&推导出的是引用
			this->_socket->async_send(asio::buffer(send_data->_msg, send_data->_total_len),
				std::bind(&Session::WriteAllCallBack, this, std::placeholders::_1, std::placeholders::_2));
		}
	}
}

void Session::ReadFromSocket()
{
	if (_recv_pending)
		return;
	_recv_node = std::make_shared<MsgNode>(RECVSIZE);
	_socket->async_read_some(asio::buffer(_recv_node->_msg, _recv_node->_total_len),
		std::bind(&Session::ReadCallBack, this, std::placeholders::_1, std::placeholders::_2));
	_recv_pending = true;
}

void Session::ReadCallBack(const boost::system::error_code& ec, std::size_t bytes_transferred)
{
	if (ec.value() != 0)
	{
		cout << "Error occured! Error code = " << ec.value()
			<< ". Message is " << ec.message()<<endl;
	}
	_recv_node->_cur_len += bytes_transferred;
	if (_recv_node->_cur_len < _recv_node->_total_len)
	{
		_socket->async_read_some(asio::buffer(_recv_node->_msg+_recv_node->_cur_len,
			_recv_node->_total_len-_recv_node->_cur_len),
			std::bind(&Session::ReadCallBack, this, std::placeholders::_1, std::placeholders::_2));
	}
	else
	{
		_recv_pending = false;
		_recv_node = nullptr;
	}
}

void Session::ReadAllFromSocket()
{
	if (_recv_pending)
		return;
	_recv_node = make_shared<MsgNode>(RECVSIZE);
	_socket->async_receive(asio::buffer(_recv_node->_msg, _recv_node->_total_len),
		std::bind(&Session::ReadAllCallBack, this, std::placeholders::_1, std::placeholders::_2));
	_recv_pending = true;
}

void Session::ReadAllCallBack(const boost::system::error_code& ec, std::size_t bytes_transferred)
{
	if (ec.value() != 0)
	{
		cout << "Error occured! Error code = " << ec.value()
			<< " . Message is " << ec.message() << endl;
	}
	_recv_node->_cur_len += bytes_transferred;
	_recv_pending = false;
	_recv_node = nullptr;
}

posted @ 2025-05-31 16:43  重光拾  阅读(148)  评论(0)    收藏  举报