Boost ASIO库是一个基于C++语言的开源网络编程库,该库提供了成熟、高效、跨平台的网络API接口,并同时支持同步与异步两种模式,ASIO库提供了多重I/O对象、异步定时器、可执行队列、信号操作和协程等支持,使得开发者可以轻松地编写可扩展的高性能网络应用程序,同时保持代码简洁、易于维护。
在学习ASIO
库之前,我们先来实现一个简单的地址解析功能,Boost库中提供了ip::tcp::resolver
对象,该对象可用于解析给定主机名和端口号的IP
地址,学会使用这个对象即可实现对特定主机域名地址的解析功能,如下封装实现了GetDNSAddress
该函数传入一个域名,并输出该域名所对应的IP
地址列表,并返回给std::vector
容器内,其实现原理如下所示;
#include <iostream>
#include <vector>
#include <boost/asio.hpp>
using namespace std;
using namespace boost;
using namespace boost::asio;
// 传入域名解析IP地址
std::vector<std::string> GetDNSAddress(std::string hostname)
{
std::vector<std::string> address_list;
boost::asio::io_service ioservice;
boost::asio::io_service my_io_service;
boost::asio::ip::tcp::resolver resolver(my_io_service);
boost::asio::ip::tcp::resolver::query query(hostname, "https");
boost::asio::ip::tcp::resolver::iterator iter = resolver.resolve(query);
boost::asio::ip::tcp::resolver::iterator end;
while (iter != end)
{
boost::asio::ip::tcp::endpoint endpoint = *iter++;
address_list.push_back(endpoint.address().to_string());
}
return address_list;
}
这段代码的调用很容易,只需要传入特定域名即可,如下所示代码中,我们获取www.baidu.com
域名下所有的IP地址列表,并依次循环输出ref_address_list
中的所有列表信息。
int main(int argc, char *argv[])
{
// 从字符串产生IP
ip::address addr;
addr = addr.from_string("192.168.1.1");
if (addr.is_v4())
{
std::string addr_string = addr.to_string();
std::cout << "IP地址: " << addr_string << std::endl;
}
// 根据域名获取所有DNS地址
std::vector < std::string > ref_address_list;
ref_address_list = GetDNSAddress("www.baidu.com");
for (int x = 0; x < ref_address_list.size(); x++)
{
std::cout << ref_address_list[x] << std::endl;
}
std::system("pause");
return 0;
}
读者可自行编译并运行上述代码片段,当运行后会看到特定域名下所包含的所有IP信息,如下图所示;
同步TCP模式
在同步模式下,程序发起I/O
操作时,调用相应的同步I/O
函数将操作添加到io_service
中,该请求被添加到io_service
的请求队列中等待处理。然后,io_service
就会不断地从队列中取出请求,并将请求传递给操作系统进行处理,直到该请求被处理完成。程序在此期间会一直处于阻塞等待的状态,直到操作完成或者因为某种原因导致操作失败。
当I/O
操作在操作系统完成后,操作系统会通知io_service
,io_service
接收到通知后会再次进入循环,将操作结果发送回程序进行处理。程序会在此等待操作结果,并在io_service
返回结果时继续执行其余代码。
同步网络通信的实现原理与原生Socket套接字通信原理保持一致,只是在ASIO
模型中,需要定义一个io_service
对象,在服务端环境下,我们通过ip::tcp::acceptor
来指定服务端地址与端口信息,使用ip::tcp::socket
创建一个套接字,通过acceptor.accept(socket)
则可用于同步等待一个套接字的链接,当有新套接字连入后,我们可以使用socket.write_some
函数向客户端发送一段消息。
#include <iostream>
#include <boost/asio.hpp>
using namespace boost::asio;
int main(int argc, char* argv[])
{
io_service io;
ip::tcp::acceptor acceptor(io, ip::tcp::endpoint(ip::tcp::v4(), 6666));
while (1)
{
// 创建 socket 对象
ip::tcp::socket socket(io);
// 等待客户端连接
acceptor.accept(socket);
// 显示客户端IP
std::cout << "本机地址: " << socket.local_endpoint().address() << std::endl;
std::cout << "客户端地址: " << socket.remote_endpoint().address() << std::endl;
// 向客户端发送 hello lyshark
boost::system::error_code error;
socket.write_some(buffer("hello lyshark"), error);
// 如果出错,输出错误提示
if (error)
{
std::cout << boost::system::system_error(error).what() << std::endl;
break;
}
}
system("pause");
return 0;
}
对于客户端而言我们可以使用tcp::endpoint
创建一个链接端点,当初始化结构后就可以使用socket.connect
函数连接到这个端点上,当链接被建立后,则客户端就可以使用socket.read_some
函数接收服务端传递过来的消息,此处读者需要注意接受的消息需要使用boost::array
存储,当接收到消息后就可以使用buffer.data()
方法打印出该缓冲区内的具体内容。
#include <iostream>
#include <boost/array.hpp>
#include <boost/asio.hpp>
using namespace std;
using boost::asio::ip::tcp;
int main(int argc, char* argv[])
{
try
{
// 定义Socket对象
boost::asio::io_service io;
tcp::socket socket(io);
// 尝试连接服务器
tcp::endpoint end_point(boost::asio::ip::address::from_string("127.0.0.1"), 6666);
socket.connect(end_point);
while (1)
{
boost::array<char, 1024> buffer = { 0 };
boost::system::error_code error;
// 接受数据并存入buffer
size_t len = socket.read_some(boost::asio::buffer(buffer), error);
// 判断是否出错
if (error == boost::asio::error::eof)
break;
else if (error)
throw boost::system::system_error(error);
std::cout << "接收到数据: " << buffer.data() << std::endl;
}
}
catch (std::exception& e)
{
cout << e.what() << endl;
}
system("pause");
return 0;
}
读者可自行编译并运行上述服务端与客户端程序,当运行后即可看到如下图所示的输出效果;
同步UDP模式
TCP和UDP是两种常见的Internet协议,TCP是一种可靠的、面向连接的协议,UDP则是不可靠的、无连接的协议。 TCP适合传输数据量大、对数据传输准确性要求高的应用,而UDP适合传输数据量小、传输速度快、对传输可靠性要求低的应用。
ASIO库在实现UDP
传输时其大体思路与TCP
保持一致,两者唯一的区别是在定义套接字时应使用ip::udp::
命名空间,其次在传输数据方面服务端应该采用receive_from
函数接收参数,如下是一段简单的UDP
传输服务端实现。
#include <iostream>
#include <boost/asio.hpp>
using namespace boost::asio;
using namespace boost::system;
int main(int argc, char* argv[])
{
io_service io;
ip::udp::socket sock(io, ip::udp::endpoint(ip::udp::v4(), 6666));
while (1)
{
char buf[1];
ip::udp::endpoint ep;
error_code ec;
// 接收参数
sock.receive_from(buffer(buf), ep, 0, ec);
if (ec && ec != error::message_size)
{
throw system_error(ec);
}
std::cout << "发送到: " << ep.address() << std::endl;
sock.send_to(buffer("hello lyshark"), ep);
}
system("pause");
return 0;
}
接着是客户端的实现,对于UDP
客户端通常采用sock.open()
函数打开套接字,在打开后可调用sock.send_to
向服务端发送数据,同时使用sock.receive_from
接收数据包,如下是客户端代码实现。
#include <iostream>
#include <vector>
#include <boost/asio.hpp>
using namespace boost::asio;
using namespace boost::system;
int main(int argc, char* argv[])
{
io_service io;
ip::udp::endpoint send_ep(ip::address::from_string("127.0.0.1"), 6666);
ip::udp::socket sock(io);
sock.open(ip::udp::v4());
char buf[1];
// 发送数据
sock.send_to(buffer(buf), send_ep);
std::vector<char> v(100, 0);
ip::udp::endpoint recv_ep;
// 接收数据
sock.receive_from(buffer(v), recv_ep);
std::cout << "数据来自于: " << recv_ep.address() << std::endl;
std::cout << "数据: " << &v[0] << std::endl;
system("pause");
return 0;
}
读者可自行编译并运行上述代码片段,则可输出如下图所示的效果;