"Boost.Asio C ++ Network Programming." Chapter 2: Boost.Asio Basics . Part 1

  • Tutorial
Hello!
I continue to translate John Torjo's book Boost.Asio C ++ Network Programming. The second chapter turned out to be large, so I will break it into two parts. In this part, we will talk specifically about the basics of Boost.Asio, and in the second part we will talk about asynchronous programming.

Content:


In this chapter, we will cover what you need to know using Boost.Asio. We will delve into asynchronous programming, which is much more complicated than synchronous and much more interesting.


Network API


This section shows what you need to know in order to write a network application using Boost.Asio.

Boost.Asio Namespaces

Everything in Boost.Asio is in the namespace boost::asio or its subspace, consider them:
  • boost::asio : This is where all the main classes and functions are located. The main classes are io_service and streambuf . Here are functions such as read, read_at, read_until their asynchronous copies, as well as recording functions and their asynchronous copies.
  • boost::asio::ip : This is the place where the network part of the library is located. The main classes of this address, endpoint, tcp, udp, icmp , and the main function of this connect and async_connect . Note that socket in boost::asio::ip::tcp::socket this just typedef within the class boost::asio::ip::tcp .
  • boost::asio::error : This namespace contains error codes that you may get when calling the I / O routine
  • boost::asio::ssl : This namespace contains classes dealing with SSL.
  • boost::asio::local : This namespace contains POSIX-specific classes
  • boost::asio::windows : This namespace contains Windows-specific classes


IP addresses

Boost.Asio provides ip::address, ip::address_v4 and classes for working with IP addresses ip::address_v6 .
They provide many features. Here are the most important ones:
  • ip::address(v4_or_v6_address) : This function converts v4 or v6 address to ip::address
  • ip::address:from_string(str) : This function creates an address. из IPv4 адреса (разделенных точками) или из IPv6 (шестнадцатиричный формат)
  • ip::address::to_string() : This function returns the address representation in a favorable string form.
  • ip::address_v4::broadcast([addr, mask]) : This function creates an broadcast address.
  • ip::address_v4::any() : This function returns an address that represents any address.
  • ip::address_v4::loopback(), ip_address_v6::loopback() : This function returns a loop of addresses (from the v4 / v6 protocol)
  • ip::host_name() : This function returns the name of the current host as a string

Most likely you will use the function ip::address::from_string :

ip::address addr = ip::address::from_string("127.0.0.1");

If you need to connect to the host name, read on. The following code will not work:

// throws an exception
ip::address addr = ip::address::from_string("www.yahoo.com");


Endpoints

The endpoint is the connection address along with the port. Each type of socket has its own endpoint class, for example,, ip::tcp::endpoint, ip::udp::endpoint and ip::icmp::endpoint .
If you want to connect to localhost port 80, then you need to write the following:

ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 80);

You can create an endpoint in three ways:
  • endpoint() : default constructor and can sometimes be used for UDP / ICMP sockets
  • endpoint(protocol, port) : commonly used on server sockets to accept new connections
  • endpoint(addr, port) : creating an endpoint by address and port

Here are some examples:

ip::tcp::endpoint ep1;
ip::tcp::endpoint ep2(ip::tcp::v4(), 80);
ip::tcp::endpoint ep3( ip::address::from_string("127.0.0.1), 80);

If you want to connect to the host (not the IP address), then you need to do the following:

 // outputs "87.248.122.122"
io_service service;
ip::tcp::resolver resolver(service);
ip::tcp::resolver::query query("www.yahoo.com", "80");
ip::tcp::resolver::iterator iter = resolver.resolve( query);
ip::tcp::endpoint ep = *iter;
std::cout << ep.address().to_string() << std::endl;

You can replace tcp with the type of socket you need. First, create a query with the name you want to connect to, this can be implemented using the function resolve() . If successful, at least one record will be returned.
Having received the endpoint, you can get the address, port and IP protocol (v4 or v6) from it:

std::cout << ep.address().to_string() << ":" << ep.port() << "/" << ep.protocol() << std::endl;


Sockets

Boost.Asio includes three types of classes sockets: ip::tcp, ip::udp and ip::icmp , and, of course, is expanding. You can create your own socket class, although this is rather complicated. If you do decide to make it look boost/ asio/ip/tcp.hpp , boost/asio/ip/udp.hpp and boost/asio/ip/icmp.hpp . All of them are rather small classes with internal typedef keywords.
You can think of classes ip::tcp, ip::udp, ip::icmp as placeholders; they make it easy to get to other classes / functions, which are defined as follows:
  • ip :: tcp :: socket, ip :: tcp :: acceptor, ip :: tcp :: endpoint, ip :: tcp :: resolver, ip :: tcp :: iostream
  • ip :: udp :: socket, ip :: udp :: endpoint, ip :: udp :: resolver
  • ip :: icmp :: socket, ip :: icmp :: endpoint, ip :: icmp :: resolver

The class socket creates the corresponding socket. You always pass the instance io_service to the constructor:

io_service service;
ip::udp::socket sock(service)
sock.set_option(ip::udp::socket::reuse_address(true));

Each socket name has typedef :
  • ip::tcp::socket= basic_stream_socket
    ip::udp::socket= basic_datagram_socket<ud p>
    ip::icmp::socket= basic_raw_socket


    Synchronous Function Error Codes


    All synchronous functions have overloads that throw exceptions or return an error code, as shown below:

    sync_func( arg1, arg2 ... argN); // throws
    boost::system::error_code ec;
    sync_func( arg1 arg2, ..., argN, ec); // returns error code
    

    In the remainder of the chapter, you will see many synchronous functions. To keep things simple, I omitted the display of overloads that return an error code, but they exist.

    Socket Functions

    All functions are divided into several groups. Not all features are available for each type of socket. The list at the end of this section will show you what functions belong to which class of sockets.
    Note that all asynchronous functions respond instantly, while their synchronous counterparts respond only after the operation has been completed.

    Connective Binding Functions

    These are the functions that connect or connect to the socket, disconnect it and make a connection request, whether it is active or not:
    • assign(protocol,socket) : This function assigns a raw (natural) socket to a socket instance. Use it when working with inherited code (that is, when raw sockets are already created).
    • open(protocol) : This function opens a socket with the specified IP protocol (v4 or v6). You will use it mainly for UDP / ICMP sockets or server sockets
    • bind(endpoint) : This function is associated with this address.
    • connect(endpoint) : This function is synchronously connected to this address.
    • async_connect(endpoint) : This function connects asynchronously to this address.
    • is_open() : this function returns true if the socket is open.
    • close() : This function closes the socket. Any asynchronous operations on this socket immediately stop and return an error::operation_aborted error code.
    • shutdown(type_of_shutdown) : This function disables the operation send , receive or both immediately after the call.
    • cancel() : This function cancels all asynchronous operations on this socket. All asynchronous operations on this socket will be completed immediately and will return an error::operation_aborted error code.

    Here is a small example:

    ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 80);
    ip::tcp::socket sock(service);
    sock.open(ip::tcp::v4());
    sock.connect(ep);
    sock.write_some(buffer("GET /index.html\r\n"));
    char buff[1024]; sock.read_some(buffer(buff,1024));
    sock.shutdown(ip::tcp::socket::shutdown_receive);
    sock.close();
    


    Read / write functions

    These are functions that perform input / output on a socket.
    For asynchronous functions, the handler has the following signature void handler(const boost::system::error_code& e, size_t bytes); . But the functions themselves:
    • async_receive(buffer, [flags,] handler) : This function starts the asynchronous operation of receiving data from the socket.
    • async_read_some(buffer,handler) : This function is equivalent async_receive(buffer, handler) .
    • async_receive_from(buffer, endpoint[, flags], handler) : This function starts asynchronously receiving data from a specific address.
    • async_send(buffer [, flags], handler) : this function starts the asynchronous transfer of data from the buffer
    • async_write_some(buffer, handler) : This function is equivalent async_send(buffer, handler) .
    • async_send_to(buffer, endpoint, handler) : This function starts the asynchronous transfer of data from the buffer to a specific address.
    • receive(buffer [, flags]) : This function synchronously receives data into the buffer. The function is blocked until data begins to arrive or if an error occurs.
    • read_some(buffer) : This function is equivalent receive(buffer) .
    • receive_from(buffer, endpoint [, flags]) : This function synchronously receives data from a specific address in a given buffer. The function is blocked until the data began to arrive or if an error occurred.
    • send(buffer [, flags]) : This function sends data from the buffer synchronously. Функция заблокирована пока идет отправка данных или если произошла ошибка.
    • write_some(buffer) : This function is equivalent send(buffer) .
    • send_to(buffer, endpoint [, flags]) : This function synchronously transfers data from the buffer to this address. Функция заблокирована пока идет отправка данных или если произошла ошибка.
    • available() : This function returns the number of bytes that can be read synchronously without blocking.

    We will talk about buffers in the near future. Let's look at the flags. The default value for flags is 0, but combinations are possible:
    • ip::socket_type::socket::message_peek : This flag only peeks into the message. It will return the message, but the next time you call it, you will need to reread it to read the message.
    • ip::socket_type::socket::message_out_of_band : This flag processes out-of-band data. OOB data is data that is marked as more important than regular data. A discussion of OOB data is beyond the scope of this book.
    • ip::socket_type::socket::message_do_not_route : This flag indicates that the message should be sent without using routing tables.
    • ip::socket_type::socket::message_end_of_record: this flag indicates that the data is marked with a marker about the end of the recording. This is not supported on Windows.

    Most likely you used message_peek it if you ever wrote the following code:

    char buff[1024]; 
    sock.receive(buffer(buff), ip::tcp::socket::message_peek ); 
    memset(buff,1024, 0); 
    // re-reads what was previously read 
    sock.receive(buffer(buff) );
    

    The following are examples that give directions to read both synchronously and asynchronously to different types of sockets:
    • Example 1: synchronous read and write to a TCP socket:
      ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 80); 
      ip::tcp::socket sock(service); 
      sock.connect(ep); 
      sock.write_some(buffer("GET /index.html\r\n")); 
      std::cout << "bytes available " << sock.available() << std::endl; 
      char buff[512]; 
      size_t read = sock.read_some(buffer(buff));
      

    • Example 2: synchronous read and write to a UDP socket:
      ip::udp::socket sock(service); 
      sock.open(ip::udp::v4()); 
      ip::udp::endpoint receiver_ep("87.248.112.181", 80); 
      sock.send_to(buffer("testing\n"), receiver_ep); 
      char buff[512]; 
      ip::udp::endpoint sender_ep; 
      sock.receive_from(buffer(buff), sender_ep);
      

      Note that when reading from a UDP socket using receive_from , you need to use the default constructor of the endpoint, as shown in the previous example.
    • Example 3: asynchronously reading from a UDP server socket:
      using namespace boost::asio;
      io_service service;
      ip::udp::socket sock(service);
      boost::asio::ip::udp::endpoint sender_ep;
      char buff[512];
      void on_read(const boost::system::error_code & err, std::size_t
      read_bytes)
       {
      	std::cout << "read " << read_bytes << std::endl;
      	sock.async_receive_from(buffer(buff), sender_ep, on_read);
      }
      int main(int argc, char* argv[]) 
      {
      	ip::udp::endpoint ep( ip::address::from_string("127.0.0.1"),
      	8001);
      	sock.open(ep.protocol());
      	sock.set_option(boost::asio::ip::udp::socket::reuse_
      	address(true));
      	sock.bind(ep);
      	sock.async_receive_from(buffer(buff,512), sender_ep, on_read);
      	service.run();
      }
      



    Socket management

    These functions work with additional socket parameters:
    • get_io_service() : this function returns the instance io_service that was accepted in the constructor.
    • get_option(option) : this function returns the socket parameter
    • set_option(option) : this function sets the socket parameter
    • io_control(cmd) : This function executes the I / O command on the socket.

    The following parameters you can get / set to the socket:
    Name Definition A type
    broadcast If true , then allows broadcast messages bool
    debug If true , then it allows debugging at the socket level bool
    do_not_route If true , then it prevents routing and uses only local interfaces bool
    enable_connection_aborted If true , then reconnects the disconnected connection bool
    keep_alive If true , then sends keep-alives bool
    linger If true , then the socket is delayed by close() if there is no
    data close() stored
    bool
    receive_buffer_size Receive Buffer Size int
    receive_low_watemark Provides the minimum number of bytes when processing an input socket int
    reuse_address If true , then the socket may be associated with an address that is already in use bool
    send_buffer_size Send buffer size int
    send_low_watermark Provides the minimum number of bytes to send on the output socket. int
    ip::v6_only If true , then only IPv6 communications can be used. bool

    Each name represents an internal typedef socket or class. Here's how to use them:

    ip::tcp::endpoint ep( ip::address::from_string("127.0.0.1"), 80); 
    ip::tcp::socket sock(service); 
    sock.connect(ep); 
    // TCP socket can reuse address 
    ip::tcp::socket::reuse_address ra(true); 
    sock.set_option(ra); 
    // get sock receive buffer size 
    ip::tcp::socket::receive_buffer_size rbs; 
    sock.get_option(rbs); 
    std::cout << rbs.value() << std::endl; 
    // set sock's buffer size to 8192 
    ip::tcp::socket::send_buffer_size sbs(8192); 
    sock.set_option(sbs);
    

    The socket must be open for the previous function to work, otherwise an exception will be thrown.

    TCP vs UDP and ICMP

    As I said, not all member functions are available for all socket classes. I have compiled a list where member functions differ. If there is no member function here, then this means that it is present in all socket classes:
    Name TCP UDP ICMP
    async_read_some Yes - -
    async_write_some Yes - -
    async_send_to - Yes Yes
    read_some Yes - -
    receive_from - Yes Yes
    write_some Yes - -
    send_to - Yes Yes


    Other functions

    Other functions related to connection or input / output:
    • local_endpoint() : this function returns the address if the socket is connected locally.
    • remote_endpoint() : This function returns the remote addresses where the socket was connected.
    • native_handle() : This function returns a clean socket. It should be used only when you want to use functions for working with clean sockets that are not supported by Boost.Asio.
    • non_blocking() : this function returns true if the socket is non-blocking, otherwise false .
    • native_non_blocking() : this function returns true if the socket is non-blocking, otherwise false . However, it will invoke a pure API for a natural socket. As a rule, you do not need this ( non_blocking() always caches this result); You should use it only when you are directly dealing with native_handle() .
    • at_mark() : this function returns true if you are going to read data in the OOB socket. She is needed very rarely.


    Other considerations

    And lastly, the socket instance cannot be copied, since the copy constructor is operator= not available.

    ip::tcp::socket s1(service), s2(service);
    s1 = s2; // compile time error
    ip::tcp::socket s3(s1); // compile time error
    

    This makes a lot of sense, since each instance stores and manages resources (the natural socket itself). If we used the copy constructor, then in the end we had two instances of the same socket; they would have to somehow manage the ownership right (either one copy has the ownership right, or reference counting is used, or some other method). In Boost.Asio, it was customary to prohibit copying (if you want to create copies, just use the generic ( shared ) pointer).

    typedef boost::shared_ptr<ip::tcp::socket> socket_ptr;
    socket_ptr sock1(new ip::tcp::socket(service));
    socket_ptr sock2(sock1); // ok
    socket_ptr sock3;
    sock3 = sock1; // ok
    


    Socket buffers


    When reading or writing to a socket, you need a buffer that will contain incoming or outgoing data. Buffer memory must survive I / O operations; You must make sure that until it is released, it will not go out of scope while the I / O operations continue.
    It is very simple for synchronous operations; Of course, I buff must survive both operations receive and send :

    char buff[512];
    ...
    sock.receive(buffer(buff));
    strcpy(buff, "ok\n");
    sock.send(buffer(buff));
    

    And this is not so easy for asynchronous operations, as shown in the following snippet:

    // very bad code ...
    void on_read(const boost::system::error_code & err, std::size_t read_
    bytes)
    { ... }
    void func() 
    {
    	char buff[512];
    	sock.async_receive(buffer(buff), on_read);
    }
    

    After the call async_receive() , it buff will go out of scope, thus its memory will be freed. When we are going to actually get some data on a socket, we need to copy it into memory that no longer belongs to us; it can either be freed or redistributed over the code for other data, while everyone has memory corruption.
    There are several solutions to this problem:
    • Use global buffer
    • Create a buffer and destroy it when the operation completes
    • Have a communication object to support the socket and additional data such as buffer (s).

    The first solution is not very convenient, since we all know that global variables are bad. Also, what happens if two handlers use the same buffer?
    Here's how you can implement the second solution:

    void on_read(char * ptr, const boost::system::error_code & err,
    std::size_t read_bytes) 
    {
    	delete[] ptr;
    }
    ....
    char * buff = new char[512];
    sock.async_receive(buffer(buff, 512), boost::bind(on_
    read,buff,_1,_2));
    

    If you want the buffer to automatically go out of scope when the operation completes, then use the generic pointer ( shared pointer ):

    struct shared_buffer 
    {
    	boost::shared_array<char> buff;
    	int size;
    	shared_buffer(size_t size) : buff(new char[size]), size(size)
    	 {}
    	mutable_buffers_1 asio_buff() const
    	 {
    		return buffer(buff.get(), size);
    	}
    };
    // when on_read goes out of scope, the boost::bind object is released,
    // and that will release the shared_buffer as well
    void on_read(shared_buffer, const boost::system::error_code & err,
    std::size_t read_bytes) {}
    ...
    shared_buffer buff(512);
    sock.async_receive(buff.asio_buff(), boost::bind(on_read,buff,_1,_2));
    

    The class shared_buffer contains within itself shared_array<> , which is a copy of the instance shared_buffer , so it shared_array <> will remain alive; when the latter goes out of scope, it shared_array <> will automatically collapse exactly what we wanted.
    This works as expected, as Boost.Asio will keep a copy of the terminating handler that is called when the operation completes. This copy is a functor boost::bind that internally stores a copy of our instance shared_buffer . This is very neat!
    The third option is to use a communication object that supports the socket and contains additional data, such as buffers, this is usually the right decision, but rather complicated. It will be discussed at the end of this chapter.

    Buffer function wrapper


    In the code that we saw earlier, we always needed a buffer for read / write operations, the code turned into an object of a real buffer, to call buffer() and transfer its function:

    char buff[512];
    sock.async_receive(buffer(buff), on_read
    

    Basically, any buffer that we have in the class is wrapped, which allows functions from Boost.Asio to iterate through the buffer. Say you are using the following code:

    sock.async_receive(some_buffer, on_read);
    

    The instance some_buffer must satisfy some requirements, namely ConstBufferSequence or MutableBufferSequence (you can look at them in more detail in the documentation on Boost.Asio). The details of creating your own class to meet these requirements are quite complex, but Boost.Asio already contains some classes that model these requirements. You do not access them directly; you use a function buffer() .
    Suffice it to say that you can wrap all of the following in a function buffer() :
    • constant array of characters
    • void* and size in characters
    • line std::string
    • константный массив POD[] (POD подходит для старых данных, то есть конструктор и деструктор ничего не делают)
    • an array std::vector of any POD
    • an array boost::array of any POD
    • an array std::array of any POD

    The following code is working:

    struct pod_sample { int i; long l; char c; };
    ...
    char b1[512];
    void * b2 = new char[512];
    std::string b3; b3.resize(128);
    pod_sample b4[16];
    std::vector<pod_sample> b5; b5.resize(16);
    boost::array<pod_sample,16> b6;
    std::array<pod_sample,16> b7;
    sock.async_send(buffer(b1), on_read);
    sock.async_send(buffer(b2,512), on_read);
    sock.async_send(buffer(b3), on_read);
    sock.async_send(buffer(b4), on_read);
    sock.async_send(buffer(b5), on_read);
    sock.async_send(buffer(b6), on_read);
    sock.async_send(buffer(b7), on_read);
    

    In general, instead of creating your own class to meet the requirements, ConstBufferSequence or MutableBufferSequence you can create a class that will contain a buffer as long as necessary and return an instance mutable_ buffers_1 , this is the same as we did in the class shared_buffer earlier.

    Independent read / write / connect functions


    Boost.Asio gives you independent I / O functions. I divided them into four groups.

    Connectivity features

    These functions connect a socket or end address:
    • connect(socket, begin [, end] [, condition]) : This function attempts to synchronously connect each end address in a sequence, starting with begin and ending with end . The iterator begin is the result of a call socket_type::resolver::query (if you want, you can look at the Endpoints section again). Specifying the final iterator is optional, you can forget about it. You can specify the condition function that is called before each connection attempt. This is a signature Iterator connect_condition(const boost::system::error_code & err, Iterator next); . You can choose a different iterator to return than the next one, this allows you to skip some end addresses.
    • async_connect(socket, begin [, end] [, condition], handler): this function performs an asynchronous connection and, at the end, calls the termination handler. The processor signature is as follows void handler(const boost::system::error_code & err, Iterator iterator); . The second parameter passed to the handler is the successfully connected end address (or the end iterator otherwise).

    We give the following example:

    using namespace boost::asio::ip;
    tcp::resolver resolver(service);
    tcp::resolver::iterator iter = resolver.resolve(tcp::resolver::query("www.yahoo.com","80"));
    tcp::socket sock(service);
    connect(sock, iter);
    

    Hostname can contain more than one address, thereby connect and async_connect free you from the burden of checking each address, in order to understand which one is available; they do it for you.

    Read / write functions

    These are the functions of reading or writing to a stream (which can be either a socket or any other class that behaves like a stream):
    • async_read(stream, buffer [, completion] ,handler) : This function reads asynchronously from a stream. Upon completion, the handler is called. It has the following signature void handler(const boost::system::error_code & err, size_t bytes); . If necessary, you yourself can set the completion function. Completion the function is called after each successful operation read , and tells Boost.Asio if the function has async_read completed (if not, the function will continue to read). The next parameter is this size_t completion (const boost::system::error_code& err, size_t bytes_transfered) . When this trailing function returns 0, we assume that the read operation has completed; if a non-zero value is returned, then this means that the maximum number of bytes will be read the next time the operation is called async_read_some in the stream. An example will be considered below for a better understanding.
    • async_write(stream, buffer [, completion], handler) : This is a function of writing asynchronously to a stream. The argument list is similar to async_read .
    • read(stream, buffer [, completion]) : This is a synchronous read from stream function. The argument list is similar to async_read . /li@1>
    • write(stream, buffer [, completion]) : This is a function of synchronous recording to a stream. The argument list is similar to async_read .
    • async_read(stream, stream_buffer [, completion], handler)
    • async_write(strean, stream_buffer [, completion], handler)
    • write(stream, stream_buffer [, completion])
    • read(stream, stream_buffer [, completion])

    First, note that instead of the socket, the first argument is the stream. A socket can be transferred here, but this is not limited to this. For example, instead of a socket, you can use a Windows file.
    Each read or write operation will end when one of the following conditions is true:
    • The provided buffer will be filled (for reading) or all data in the buffer will be written (for writing).
    • Completion the function will return 0 (if you provided one of these functions).
    • If an error has occurred.

    The following code reads asynchronously until it encounters '\ n':

    io_service service;
    ip::tcp::socket sock(service);
    char buff[512];
    int offset = 0;
    size_t up_to_enter(const boost::system::error_code &, size_t bytes) 
    {
    	for ( size_t i = 0; i < bytes; ++i)
    		if ( buff[i + offset] == '\n')
    			return 0;
    	return 1;
    }
    void on_read(const boost::system::error_code &, size_t) {}
    ...
    async_read(sock, buffer(buff), up_to_enter, on_read);
    

    In addition, Boost.Asio provides several completion functions to help you completion :
    • transfer_at_least (n)
    • transfer_exactly (n)
    • transfer_all ()

    This is illustrated in the following example:

    char buff[512];
    void on_read(const boost::system::error_code &, size_t) {}
    // read exactly 32 bytes
    async_read(sock, buffer(buff), transfer_exactly(32), on_read);
    

    The last four functions, instead of the usual buffer, use the function stream_buffer from Boost.Asio, this is a derivative of std::streambuf . The streams themselves and their buffers from STL are very flexible, here is an example:

    io_service service;
    void on_read(streambuf& buf, const boost::system::error_code &,
    size_t) 
    {
    	std::istream in(&buf);
    	std::string line;
    	std::getline(in, line);
    	std::cout << "first line: " << line << std::endl;
    }
    int main(int argc, char* argv[]) 
    {
    	HANDLE file = ::CreateFile("readme.txt", GENERIC_READ, 0, 0,
    	OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,0);
    	windows::stream_handle h(service, file);
    	streambuf buf;
    	async_read(h, buf, transfer_exactly(256),
    	boost::bind(on_read,boost::ref(buf),_1,_2));
    	service.run();
    }
    

    Here I showed you what you can call async_read (or something similar) for Windows files. We read the first 256 characters and save them to the buffer. When the read operation is completed, it will be called on_read , I create a std::istream buffer, read the first line ( std::getline ) and print it to the console.

    Functions read_until/async_read_until

    These functions read until a certain condition is met:
    • async_read_until(stream, stream_buffer, delim, handler) : This function starts the asynchronous read operation. The read operation will stop as soon as the delimiter ( delim ) is read delim . The delimiter can be any character, std::string or boost::regex . The processor signature is as follows void handler(const boost::system::error_code & err, size_t bytes); .
    • async_read_until(stream, stream_buffer, completion, handler) : this function is the same as the previous one, but instead of a separator, we have a final function. It has the following signature pair<iterator,bool> completion(iterator begin, iterator end); when the iterator is buffers_iterator<streambuf::const_buffers_type> . You need to remember that an iterator is of the random access iterator type. You look in the range ( begin, end ) and you decide whether the read operation should complete or not. You have a couple coming back; the first member of which is an iterator indicating the last character read by the function; the second term is true , otherwise, if the read operation should stop, then false .
    • read_until(stream, stream_buffer, delim) : This function performs a synchronous read operation. The parameter values ​​are the same as y async_read_until .

    In the following example, we will read up to punctuation marks:

    typedef buffers_iterator<streambuf::const_buffers_type> iterator;
    std::pair<iterator, bool> match_punct(iterator begin, iterator end) 
    {
    	while ( begin != end)
    	if ( std::ispunct(*begin))
    	return std::make_pair(begin,true);
    	return std::make_pair(end,false);
    }
    void on_read(const boost::system::error_code &, size_t) {}
    ...
    streambuf buf;
    async_read_until(sock, buf, match_punct, on_read);
    

    If we wanted to read to the space, we would change the last line to:

    async_read_until(sock, buff, ' ', on_read);
    


    Functions * _at

    These are functions of random read / write operations to the stream. You indicate where the read / write operations should begin (specify the offset):
    • async_read_at(stream, offset, buffer [, completion], handler) : This function starts the asynchronous read operation. в данном потоке, начиная со смещения offset . When the operation is completed, a handler will be called handler (const boost::system::error_code& err, size_t bytes); . A buffer can be either a regular binding buffer() or a function streambuf . If you set the completion function, it will be called after each successful read and tells Boost.Asio if the operation has async_read_at operation completed (if not, the reading will continue). The signature of the final function is as follows size_t completion(const boost::system::error_code& err, size_t bytes); . When this function returns 0, we assume that the read operation has completed; if a nonzero value is returned, this means that the maximum number of bytes will be read on the next call async_read_some_at in this thread.
    • async_write_at(stream, offset, buffer [, completion], handler) : This function starts the asynchronous write operation. The options are similar to async_read_at .
    • read_at(stream, offset, buffer [, completion]) : This function reads with an offset in this stream. The options are similar to async_read_at .
    • read_at(stream, offset, buffer [, completion]) : This function reads with an offset in this stream. The options are similar to async_read_at .

    These functions do not deal with sockets. They deal with random access streams; in other words, with threads that can be accessed randomly. Sockets are clearly not this case (sockets are forward-only ).
    Here's how you can read 128 bytes from a file, starting at offset 256:

    io_service service;
    int main(int argc, char* argv[]) 
    {
    	HANDLE file = ::CreateFile("readme.txt", GENERIC_READ, 0, 0,
    	OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,0);
    	windows::random_access_handle h(service, file);
    	streambuf buf;
    	read_at(h, 256, buf, transfer_exactly(128));
    	std::istream in(&buf);
    	std::string line;
    	std::getline(in, line);
    	std::cout << "first line: " << line << std::endl;
    }
    


    Thank you all for your attention, next time we'll talk about asynchronous programming. Write all comments in the comments. I will definitely answer.

    Good luck to all!