TCP,UDP和ICMP
Asio对TCP,UDP,ICMP协议提供现成的支持。
TCP客户端
使用解析器执行主机名解析,其中查找主机和服务名称并将其转换为一个或多个端点:
ip::tcp::resolver resolver(my_io_context);
ip::tcp::resolver::query query("www.boost.org", "http");
ip::tcp::resolver::iterator iter = resolver.resolve(query);
ip::tcp::resolver::iterator end; // End marker.
while (iter != end)
{
ip::tcp::endpoint endpoint = *iter++;
std::cout << endpoint << std::endl;
}
上面获得的端点列表可以同时包含 IPv4 和 IPv6 端点,因此程序应该测试每一个,直到找到一个有效的。这使客户端程序与 IP 版本无关。
为了简化协议无关程序的开发,TCP客户端可以使用自由函数connect()
和async_connect()
来建立连接。这些函数尝试链表中的每一个端口直到套接字成功连接。如下调用所示:
ip::tcp::socket socket(my_io_context);
boost::asio::connect(socket,resolver.resolve(query));
将同步尝试所有端点,直到成功连接一个。 类似地,异步连接可以通过以下方式执行:
boost::asio::async_connect(socket_, iter,
boost::bind(&client::handle_connect, this,
boost::asio::placeholders::error));
// ...
void handle_connect(const error_code& error)
{
if (!error)
{
// Start read or write operations.
}
else
{
// Handle error.
}
}
当指定端点可用时,可以创建并连接套接字:
ip::tcp::socket socket(my_io_context);
socket.connect(endpoint);
可以使用 receive()
、async_receive()
、send()
或async_send()
成员函数从连接的 TCP 套接字读取或写入数据。 但是,由于这些可能导致短写入或短读,应用程序通常会使用以下操作代替:read()
、async_read()
、write()
和 async_write()
。
TCP服务端
程序使用acceptor来接收到来的TCP连接。
ip::tcp::acceptor acceptor(my_io_context, my_endpoint);
...
ip::tcp::socket socket(my_io_context);
acceptor.accept(socket);
成功接受套接字后,就可以按上面所说的 TCP 客户端读取或写入它。
UDP
UDP的主机解析也是通过解析器:
ip::udp::resolver resolver(my_io_context);
ip::udp::resolver::query query("localhost", "daytime");
ip::udp::resolver::iterator iter = resolver.resolve(query);
...
UDP 套接字通常绑定到本地端点。 以下代码将创建一个 IPv4的 UDP 套接字并将其绑定到“any”地址上的12345端口:
ip::udp::endpoint endpoint(ip::udp::v4(), 12345);
ip::udp::socket socket(my_io_context, endpoint);
可以使用receive_from()、async_receive_from()、send_to()
或 async_send_to()
成员函数从未连接的 UDP 套接字读取或写入数据。 对于已连接的 UDP 套接字,请使用 receive()、async_receive()、send()
或 async_send()
成员函数。
ICMP
与 TCP 和 UDP 一样,ICMP 主机名解析是使用解析器执行的:
ip::icmp::resolver resolver(my_io_context);
ip::icmp::resolver::query query("localhost", "");
ip::icmp::resolver::iterator iter = resolver.resolve(query);
...
ICMP 套接字可以绑定到本地端点。 以下代码将创建一个 IPv6 的ICMP 套接字并将其绑定到“any”地址:
ip::icmp::endpoint endpoint(ip::icmp::v6(), 0);
ip::icmp::socket socket(my_io_context, endpoint);
ICMP协议不需要使用端口号。
可以使用receive_from()、async_receive_from()、send_to()
或 async_send_to()
成员函数从未连接的 ICMP 套接字读取或写入数据。
对其他协议的支持
可以通过实现协议类型要求来添加对其他套接字协议(例如蓝牙或 IRCOMM 套接字)的支持。然而,在许多情况下,这些协议也可以与 Boost.Asio 的通用协议支持一起使用。 为此,Boost.Asio 提供了以下四个类:
generic::datagram_protocol
generic::raw_protocol
generic::seq_packet_protocol
generic::stream_protocol
这些类都实现了协议类型要求,不过可以让用户在运行时指定地址族(例如AF_INET)和协议族(例如IPROTO_TCP)。例如:
boost::asio::generic::stream_protocol::socket my_socket(my_io_context);
my_socket.open(boost::asio::generic::stream_protocol(AF_INET, IPPROTO_TCP));
...
包括一个端点类模板 boost::asio::generic::basic_endpoint
以支持这些协议类。此端点可以保存任何其他端点类型,前提是其本机表示匹配sockaddr_storage
对象。 此类还将从实现端点类型要求的其他类型转换:
boost::asio::ip::tcp::endpoint my_endpoint1 = ...;
boost::asio::generic::stream_protocol::endpoint my_endpoint2(my_endpoint1);
转换是隐式的,以支持以下用例:
boost::asio::generic::stream_protocol::socket my_socket(my_io_context);
boost::asio::ip::tcp::endpoint my_endpoint = ...;
my_socket.connect(my_endpoint);//发生上面的转换
C++11移动构造
使用 C++11 时,可以从套接字(或接受器)对象执行移动构造以转换为更通用的协议的套接字(或接受器)类型。 如果协议转换有效:
Protocol1 p1 = ...;
Protocol2 p2(p1);
那么对应的套接字也允许转换:
Protocol1::socket my_socket1(my_io_context);
...
Protocol2::socket my_socket2(std::move(my_socket1));
例如,一种可能的转换是从 TCP 套接字到通用的面向流的套接字:
boost::asio::ip::tcp::socket my_socket1(my_io_context);
...
boost::asio::generic::stream_protocol::socket my_socket2(std::move(my_socket1));
这些转换也可用于移动赋值。
这些转换不限于上述通用协议类。 用户定义的协议可以通过类似protocol1 到protocol2 的有效转换来利用此功能。
接受通用套接字
为方便起见,套接字接受器的 accept()
和async_accept()
函数可以直接接受不同协议的套接字类型,前提是相应的协议转换有效。 例如,支持以下内容,因为协议 boost::asio::ip::tcp
可转换为 boost::asio::generic::stream_protocol
:
boost::asio::ip::tcp::acceptor my_acceptor(my_io_context);
...
boost::asio::generic::stream_protocol::socket my_socket(my_io_context);
my_acceptor.accept(my_socket);
socket输入输出流
Asio 含有在套接字上实现iostreams
的类。 这些隐藏了与端点解析、协议独立性等相关的复杂性。要创建连接,可以简单地编写:
ip::tcp::iostream stream("www.boost.org", "http");
if (!stream)
{
// Can't connect.
}
iostream 类还可以与接受器结合使用来创建简单的服务器。 例如:
io_context ioc;
ip::tcp::endpoint endpoint(tcp::v4(), 80);
ip::tcp::acceptor acceptor(ioc, endpoint);
for (;;)
{
ip::tcp::iostream stream;
acceptor.accept(stream.socket());
...
}
超时可以通过调用expires_at()
或 expires_from_now()
来设置截止日期。 超过截止时间发生的任何套接字操作都会将 iostream 置于“bad”状态。
例如,一个简单的客户端程序是这样的:
ip::tcp::iostream stream;
stream.expires_from_now(boost::posix_time::seconds(60));
stream.connect("www.boost.org", "http");
stream << "GET /LICENSE_1_0.txt HTTP/1.0
";
stream << "Host: www.boost.org
";
stream << "Accept: */*
";
stream << "Connection: close
";
stream.flush();
std::cout << stream.rdbuf();
如果所有套接字操作的总时间超过 60 秒,则将失败。
如果确实发生了错误,可以使用 iostream 的 error() 成员函数从最近的系统调用中检索错误代码:
if (!stream)
{
std::cout << "Error: " << stream.error().message() << "
";
}
注意:这些 iostream 模板仅支持 char,不支持 wchar_t,并且不执行任何代码转换。
BSD套接字API与Asio
Asio 库包括一个基于 BSD 套接字 API 的低级套接字接口。它还用作其他语言(如 Java)中网络 API 的基础。此低级接口旨在支持高效且可扩展的应用程序的开发。例如,它允许程序员更好地控制系统调用的数量,避免冗余数据复制,最大限度地减少线程等资源的使用等。
不包括 BSD 套接字 API 的不安全和容易出错的方面。 例如,使用 int 来表示所有套接字就缺乏类型安全性。 Boost.Asio 中的套接字表示为每个协议使用不同的类型,例如 TCP 使用 ip::tcp::socket
,UDP 使用 ip::udp::socket
。
下表示BSD的套接字API与对应Asio函数之间的映射:
BSD套接字API元素 | Asio对应内容 |
---|---|
套接字描述符-POSIX为int,wndows为SOCKET | TCP:ip::tcp::socket,ip::tcp::acceptor ;UDP: ip::udp::socket 。basic_socket,basic_stream_socket,basic_datagram_socket,basic_raw_socket 。 |
in_addr,in6_addr | ip::addres,ip::address_v4,ip::address_v6 |
socketaddr_in,sockaddr_in6 | TCP:ip::tcp::endpoint ;UDP: ip::udp::endpoint ;ip::basic_endpoint |
accept() | TCP:ip::tcp::acceptor::accept() basic_socket_acceptor::accept() |
bind() | TCP:ip::tcp::acceptor::bind(),ip::tcp::socket::bind() ; UDP: ip::udp::socket::bind() ;basic_socket::bind() |
close() | TCP:ip::tcp::acceptor::close(),ip::tcp::socket::close() ;UDP: ip::udp::socket::close() ;basic_socket::close() |
connect() | TCP: ip::tcp::socket::connect() UDP: ip::udp::socket::connect() basic_socket::connect() |
getaddrinfo(), gethostbyaddr(), gethostbyname(), getnameinfo(), getservbyname(), getservbyport() | For TCP: ip::tcp::resolver::resolve(), ip::tcp::resolver::async_resolve() For UDP: ip::udp::resolver::resolve(), ip::udp::resolver::async_resolve() ip::basic_resolver::resolve(), ip::basic_resolver::async_resolve() |
gethostname() | ip::host_name() |
getpeername() | For TCP: ip::tcp::socket::remote_endpoint() For UDP: ip::udp::socket::remote_endpoint() basic_socket::remote_endpoint() |
getsockname() | For TCP: ip::tcp::acceptor::local_endpoint(), ip::tcp::socket::local_endpoint() For UDP: ip::udp::socket::local_endpoint() basic_socket::local_endpoint() |
getsockopt() | For TCP: ip::tcp::acceptor::get_option(), ip::tcp::socket::get_option() For UDP: ip::udp::socket::get_option() basic_socket::get_option() |
inet_addr(), inet_aton(), inet_pton() |
ip::address::from_string(), ip::address_v4::from_string(), ip_address_v6::from_string() |
inet_ntoa(), inet_ntop() |
ip::address::to_string(), ip::address_v4::to_string(), ip_address_v6::to_string() |
ioctl() | For TCP: ip::tcp::socket::io_control() For UDP: ip::udp::socket::io_control() basic_socket::io_control() |
listen() | For TCP: ip::tcp::acceptor::listen() basic_socket_acceptor::listen() |
poll(), select(), pselect() |
io_context::run(), io_context::run_one(), io_context::poll(), io_context::poll_one() |
readv(), recv(), read() |
For TCP: ip::tcp::socket::read_some(), ip::tcp::socket::async_read_some(), ip::tcp::socket::receive(), ip::tcp::socket::async_receive() For UDP: ip::udp::socket::receive(), ip::udp::socket::async_receive() basic_stream_socket::read_some(), basic_stream_socket::async_read_some(), basic_stream_socket::receive(), basic_stream_socket::async_receive(), basic_datagram_socket::receive(), basic_datagram_socket::async_receive() |
recvfrom() | For UDP: ip::udp::socket::receive_from(), ip::udp::socket::async_receive_from() basic_datagram_socket::receive_from(), basic_datagram_socket::async_receive_from() |
send(), write(), writev() |
For TCP: ip::tcp::socket::write_some(), ip::tcp::socket::async_write_some(), ip::tcp::socket::send(), ip::tcp::socket::async_send() For UDP: ip::udp::socket::send(), ip::udp::socket::async_send() basic_stream_socket::write_some(), basic_stream_socket::async_write_some(), basic_stream_socket::send(), basic_stream_socket::async_send(), basic_datagram_socket::send(), basic_datagram_socket::async_send() |
sendto() | For UDP: ip::udp::socket::send_to(), ip::udp::socket::async_send_to() basic_datagram_socket::send_to(), basic_datagram_socket::async_send_to() |
setsockopt() | For TCP: ip::tcp::acceptor::set_option(), ip::tcp::socket::set_option() For UDP: ip::udp::socket::set_option() basic_socket::set_option() |
shutdown() | For TCP: ip::tcp::socket::shutdown() For UDP: ip::udp::socket::shutdown() basic_socket::shutdown() |
sockatmark() | For TCP: ip::tcp::socket::at_mark() basic_socket::at_mark() |
socket() | For TCP: ip::tcp::acceptor::open(), ip::tcp::socket::open() For UDP: ip::udp::socket::open() basic_socket::open() |
socketpair() | local::connect_pair() Note: POSIX 操作系统才有 |