Hostname resolvers
Usually we use a hostname to identify a remote server. However to connect to a server we need to know its IP address. To find out what IP address lies behind a particular hostname we should resolve it. There are different physical entities which can translate a given hostname into an IP address: remote DNS server, your router, /etc/hosts
file, NETBIOS service, OS DNS cache. However you shouldn't really bother yourself on that since the API we will use to do that is all the same.
A class used to resolve hostnames into IP addresses is boost::asio::ip::tcp::resolver
. The main function we need is resolver::resolve
or resolver::async_resolve
.
There can be multiple IP addresses behind the same hostname. You can pick any of them. This is a sort of simple load balancing. In the example below we will resolve google.com
hostname and output the results into stdout:
#include <iostream>
#include <boost/asio.hpp>
namespace io = boost::asio;
namespace ip = io::ip;
using tcp = ip::tcp;
using error_code = boost::system::error_code;
int main()
{
io::io_context io_context;
error_code error;
tcp::resolver resolver(io_context);
tcp::resolver::results_type results = resolver.resolve("google.com", "80", error);
for(tcp::endpoint const& endpoint : results)
{
std::cout << endpoint << "\n";
}
return 0;
}
If everything is correct then you should see something like this in the terminal window:
64.233.161.100:80
64.233.161.139:80
64.233.161.138:80
64.233.161.101:80
64.233.161.102:80
64.233.161.113:80
Resolving a hostname in an asynchronous manner would look like that:
resolver.async_resolve("google.com", "80", [&] (error_code error, tcp::resolver::results_type results)
{
if(!error)
{
for(tcp::endpoint const& endpoint : results)
{
std::cout << endpoint << "\n";
}
}
else
{
std::cerr << "Something went wrong";
}
});
Several things we should mention:
Both resolver
and endpoint
classes are located inside tcp
class scope. There are also udp::resolver
, udp::endpoint
, icmp::resolver
and icmp::endpoint
classes. However it doesn't really matter what protocol are you going to deal with — TCP, UDP or ICMP. Hostname resolution is all the same, as well as endpoints (which are just pairs of IP address and port). However Boost.Asio has a generalized and extensible architecture design, and it is assumed that different protocols may operate on types that may differ internally. Endpoint is a higher-level abstraction and it's not necessary consists of IP address and port (in general). So, to keep things nice you should use the same scope when you deal with the same protocol: use tcp::resolver::resolve
to obtain tcp::endpoint
and pass it into tcp::socket::connect
. Same for udp
and icmp
. Even if you've came from plain C and such a separation doesn't make sense to you.
Take a look at the first two arguments of resolver::resolve
. The first one is a hostname which we want to resolve: google.com
— and that's kinda obvious. The second one is a port or a service name. In fact you don't need a port to resolve a hostname into an IP address. However since resolver::resolve
returns an instance of endpoint
class which is a pair of address and port, it needs a port to put it into the endpoint
instance. You can pass an empty string as a second argument and this will work fine — in that case port value of the returned endpoint
will be set to 0.
Some ports has string aliases (which are called “services”). So, if you don't want to hardcode port values, you can write one of those aliases instead:
tcp::resolver::results_type results = resolver.resolve("google.com", "http", error);
Full list of services is configured in the OS and can be found at:
/etc/services
on Linux and Unix family systems;%WINDIR%\System32\drivers\etc\services
on Windows family systems.