A client (at last!)
The key difference between clients and servers is this: a client connects to something on its own while a server waits for an incoming connection instead. After the connection is established, both of them use the same classes and functions to work with this connection. However, usually there are more distinctions between real life clients and servers:
Servers are designed to serve many connections from different clients simultaneously. Clients usually serve one connection. A client may establish multiple connections to the same server for better performance, or connect to multiple different servers to do several different tasks in parallel though.
A server additionally may act like a client. For example, a web server usually connects to a database server while processing user's request. At this point a web server is a database client at the same time.
A client is usually designed to interact with a human (it has a user interface, etc), while a server is usually a sort of standalone service working on its own.
So, since this time we're talking about clients, let's see how to connect to something on our own. To do that we need to call socket::connect
or socket::async_connect
function and pass an instance of endpoint
class as a parameter. The same endpoint
which we learned about in the “Learning further” lesson.
To construct an endpoint object we need to know its both IP address and port. Port is just a number. IP address is an instance of boost::asio::ip::address
class. If you know the value of the IP address then you can construct it from a string:
boost::asio::ip::address address = boost::asio::ip::make_address("127.0.0.1");
Now, having an address object, we can construct the endpoint and try to connect to it:
#include <iostream>
#include <boost/asio.hpp>
namespace io = boost::asio;
namespace ip = io::ip;
using tcp = io::ip::tcp;
using error_code = boost::system::error_code;
int main()
{
io::io_context io_context;
tcp::socket socket(io_context);
ip::address address = ip::make_address("127.0.0.1");
tcp::endpoint endpoint(address, 80);
error_code error;
socket.connect(endpoint, error);
if(!error)
{
std::cout << "The connection has been established!";
}
else
{
std::cerr << "Something went wrong :(";
}
return 0;
}
This will success if there is some application waiting (or listening) for an incoming connection on 127.0.0.1:80
and there are no barrier (like a firewall or an ill-formed routing table) for our client to connect to that application.
As you may know, there are two IP address standards: IPv4 and IPv6. There are special classes and functions for both of these standards: ip::address_v4
, ip::make_address_v4
, ip::address_v6
and ip::make_address_v6
. Also there is a generic class and function which supports both of the standards and which I used in the example above: ip::address
and ip::make_address
. You should use them when you don't really care which standard you want to deal with. So, you can pass IPv6 string representation into make_address
function and it will work just fine:
ip::address address = ip::make_address("2001:4860:4860::8888");
When the connection is established, we can use the same read and write functions which we learned about when we discussed how to write servers.
Usually in real life we don't use IP addresses directly. Instead, we use hostnames, or domain names, like google.com
. To connect to a server by its hostname, first thing we need to do is find out what IP address lies behind that hostname. And this is the subject of the next lesson.