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) - 定时器
- 缓冲区
- I/O服务(
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;
}

浙公网安备 33010602011771号