├── images └── architecture.jpg ├── README.md ├── posts ├── the-end.md ├── install-boost.md ├── endpoint.md ├── io_context.md ├── dns-query.md ├── throw-exception.md ├── accept-connections.md ├── udp-communication.md ├── socket.md ├── connect-server.md ├── synchronous-read-write-operations.md └── asynchronous-read-write-operations.md ├── .gitignore ├── SUMMARY.md └── LICENSE /images/architecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NanXiao/boost-asio-network-programming-little-book/HEAD/images/architecture.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Boost.Asio network programming little book 2 | 3 | This little book is a whirlwind tour of [Boost.Asio](https://www.boost.org/doc/libs/1_67_0/doc/html/boost_asio.html) network programming. It assumes the readers are already familiar with `UNIX` socket programming, and just want to grasp [Boost.Asio](https://www.boost.org/doc/libs/1_67_0/doc/html/boost_asio.html) network programming quickly. 4 | -------------------------------------------------------------------------------- /posts/the-end.md: -------------------------------------------------------------------------------- 1 | # The end 2 | 3 | It is time to wrap up this crash course. Hope this manual can open the door of using [Boost.Asio](https://www.boost.org/doc/libs/1_67_0/doc/html/boost_asio.html) network programming for you. Definitely, this little book just introduces the basic idea. To improve your coding skill, you need to read document more, read source code more, and practice more. 4 | 5 | Enjoy network programming! -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Node rules: 2 | ## Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 3 | .grunt 4 | 5 | ## Dependency directory 6 | ## Commenting this out is preferred by some people, see 7 | ## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git 8 | node_modules 9 | 10 | # Book build output 11 | _book 12 | 13 | # eBook build output 14 | *.epub 15 | *.mobi 16 | *.pdf -------------------------------------------------------------------------------- /posts/install-boost.md: -------------------------------------------------------------------------------- 1 | # Install Boost 2 | 3 | Installing [Boost](https://www.boost.org/) is not hard. On `OpenBSD`: 4 | 5 | $ pkg_add boost 6 | 7 | While on `Arch Linux`: 8 | 9 | $ sudo pacman -S boost 10 | 11 | The thing is when compiling program, please link related `Boost` libraries. E.g., on `OpenBSD`: 12 | 13 | $ c++ -L/usr/local/lib client.cpp -o client -lboost_system 14 | 15 | On `Arch Linux`, `-pthread` option is needed: 16 | 17 | $ c++ -pthread client.cpp -o client -lboost_system -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [Install Boost](posts/install-boost.md) 4 | * [io_context](posts/io_context.md) 5 | * [Socket](posts/socket.md) 6 | * [Endpoint](posts/endpoint.md) 7 | * [DNS query](posts/dns-query.md) 8 | * [Throw exception](posts/throw-exception.md) 9 | * [Connect server](posts/connect-server.md) 10 | * [Accept connections](posts/accept-connections.md) 11 | * [Synchronous read/write operations](posts/synchronous-read-write-operations.md) 12 | * [Asynchronous read/write operations](posts/asynchronous-read-write-operations.md) 13 | * [UDP communication](posts/udp-communication.md) 14 | * [The end](posts/the-end.md) 15 | -------------------------------------------------------------------------------- /posts/endpoint.md: -------------------------------------------------------------------------------- 1 | # Endpoint 2 | 3 | Endpoint is composed of "`IP` address + port": 4 | 5 | basic_endpoint(const boost::asio::ip::address& addr, unsigned short port_num) 6 | : impl_(addr, port_num) 7 | { 8 | } 9 | 10 | Client uses endpoint to designate server address, and server application uses endpoint to identify which address will be used to listen and accept connections. An example of creating `TCP` endpoint is like this: 11 | 12 | boost::asio::ip::tcp::endpoint endpoint{ 13 | boost::asio::ip::make_address("127.0.0.1"), 14 | 3303}; 15 | 16 | Usually, the server needs to listen to all the address of current machine, and it can resort to another constructor of `basic_endpoint`: 17 | 18 | basic_endpoint(const InternetProtocol& internet_protocol, 19 | unsigned short port_num) 20 | : impl_(internet_protocol.family(), port_num) 21 | { 22 | } 23 | 24 | An example of creating and `UDP` server who listens to all `IPv6` addresses: 25 | 26 | boost::asio::ip::udp::endpoint endpoint{ 27 | boost::asio::ip::udp::v6(), 28 | 3303}; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, Nan Xiao 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /posts/io_context.md: -------------------------------------------------------------------------------- 1 | # io_context 2 | 3 | Like traditional `Unix` network programming, `Boost.Asio`also has "socket" concept, but that's not enough, an `io_context` object (`io_service` class is deprecated now) is needed to communicate with Operating System's `I/O` services. The architecture is like following: 4 | ![image](https://raw.githubusercontent.com/NanXiao/boost-asio-network-programming-little-book/master/images/architecture.jpg) 5 | 6 | `io_context` derives from `execution_context`: 7 | 8 | class io_context 9 | : public execution_context 10 | { 11 | ...... 12 | } 13 | While `execution_context` derives from `noncopyable`: 14 | 15 | class execution_context 16 | : private noncopyable 17 | { 18 | ...... 19 | } 20 | 21 | Check `noncopyable` class definition: 22 | 23 | class noncopyable 24 | { 25 | protected: 26 | noncopyable() {} 27 | ~noncopyable() {} 28 | private: 29 | noncopyable(const noncopyable&); 30 | const noncopyable& operator=(const noncopyable&); 31 | }; 32 | 33 | It means the `io_context` object can't be copy constructed/copy assignment/move constructed/move assignment, So during initialization of socket, i.e., associate socket with `io_context`, the `io_context` should be passed as a reference. E.g.: 34 | 35 | template )> 37 | class basic_datagram_socket 38 | : public basic_socket 39 | { 40 | public: 41 | ...... 42 | explicit basic_datagram_socket(boost::asio::io_context& io_context) 43 | : basic_socket(io_context) 44 | { 45 | } 46 | ...... 47 | } -------------------------------------------------------------------------------- /posts/dns-query.md: -------------------------------------------------------------------------------- 1 | # DNS query 2 | 3 | The `resolver` class is used to do `DNS` query, i.e., convert a host+service to `IP`+port. Take `boost::asio::ip::tcp::resolver` as an example: 4 | 5 | #include 6 | #include 7 | 8 | int main() 9 | { 10 | try 11 | { 12 | boost::asio::io_context io_context; 13 | 14 | boost::asio::ip::tcp::resolver resolver{io_context}; 15 | boost::asio::ip::tcp::resolver::results_type endpoints = 16 | resolver.resolve("google.com", "https"); 17 | 18 | for (auto it = endpoints.cbegin(); it != endpoints.cend(); it++) 19 | { 20 | boost::asio::ip::tcp::endpoint endpoint = *it; 21 | std::cout << endpoint << '\n'; 22 | } 23 | } 24 | catch (std::exception& e) 25 | { 26 | std::cerr << e.what() << '\n'; 27 | } 28 | 29 | return 0; 30 | } 31 | 32 | The running result is: 33 | 34 | 74.125.24.101:443 35 | 74.125.24.139:443 36 | 74.125.24.138:443 37 | 74.125.24.102:443 38 | 74.125.24.100:443 39 | 74.125.24.113:443 40 | 41 | The element of `boost::asio::ip::tcp::resolver::results_type`'s every iteration is `basic_resolver_entry`: 42 | 43 | template 44 | class basic_resolver_entry 45 | { 46 | ...... 47 | public: 48 | /// The protocol type associated with the endpoint entry. 49 | typedef InternetProtocol protocol_type; 50 | 51 | /// The endpoint type associated with the endpoint entry. 52 | typedef typename InternetProtocol::endpoint endpoint_type; 53 | ...... 54 | /// Convert to the endpoint associated with the entry. 55 | operator endpoint_type() const 56 | { 57 | return endpoint_; 58 | } 59 | ...... 60 | } 61 | Since it has `endpoint_type() ` operator, it can be converted to endpoint directly: 62 | 63 | boost::asio::ip::tcp::endpoint endpoint = *it; 64 | -------------------------------------------------------------------------------- /posts/throw-exception.md: -------------------------------------------------------------------------------- 1 | # Throw exception 2 | 3 | `Boost.Asio` functions may throw `boost::system::system_error` exception. Take `resolve` as an example: 4 | 5 | results_type resolve(BOOST_ASIO_STRING_VIEW_PARAM host, 6 | BOOST_ASIO_STRING_VIEW_PARAM service, resolver_base::flags resolve_flags) 7 | { 8 | boost::system::error_code ec; 9 | ...... 10 | boost::asio::detail::throw_error(ec, "resolve"); 11 | return r; 12 | } 13 | 14 | There are two overloads of `boost::asio::detail::throw_error` functions: 15 | 16 | inline void throw_error(const boost::system::error_code& err) 17 | { 18 | if (err) 19 | do_throw_error(err); 20 | } 21 | 22 | inline void throw_error(const boost::system::error_code& err, 23 | const char* location) 24 | { 25 | if (err) 26 | do_throw_error(err, location); 27 | } 28 | The differences of these two functions is just including "location" ("`resolve`" string in our example) or not. Accordingly, `do_throw_error` also have two overloads, I just take one as an example: 29 | 30 | void do_throw_error(const boost::system::error_code& err, const char* location) 31 | { 32 | boost::system::system_error e(err, location); 33 | boost::asio::detail::throw_exception(e); 34 | } 35 | 36 | `boost::system::system_error` derives from `std::runtime_error`: 37 | 38 | class BOOST_SYMBOL_VISIBLE system_error : public std::runtime_error 39 | { 40 | ...... 41 | public: 42 | system_error( error_code ec ) 43 | : std::runtime_error(""), m_error_code(ec) {} 44 | 45 | system_error( error_code ec, const std::string & what_arg ) 46 | : std::runtime_error(what_arg), m_error_code(ec) {} 47 | ...... 48 | const error_code & code() const BOOST_NOEXCEPT_OR_NOTHROW { return m_error_code; } 49 | const char * what() const BOOST_NOEXCEPT_OR_NOTHROW; 50 | ...... 51 | } 52 | 53 | `what()` member function returns the detailed information of exception. -------------------------------------------------------------------------------- /posts/accept-connections.md: -------------------------------------------------------------------------------- 1 | # Accept connections 2 | 3 | Server needs to accept clients' connections. First server creates an `acceptor`: 4 | 5 | ...... 6 | boost::asio::io_context io_context; 7 | boost::asio::ip::tcp::acceptor acceptor{ 8 | io_context, 9 | boost::asio::ip::tcp::endpoint{boost::asio::ip::tcp::v6(), 3303}}; 10 | ...... 11 | `boost::asio::ip::tcp::acceptor` is an instance of `basic_socket_acceptor`: 12 | 13 | class tcp 14 | { 15 | ...... 16 | /// The TCP acceptor type. 17 | typedef basic_socket_acceptor acceptor; 18 | ...... 19 | } 20 | 21 | The following constructor of `basic_socket_acceptor` combines creating socket, setting reuse address, binding & listening functions: 22 | 23 | basic_socket_acceptor(boost::asio::io_context& io_context, 24 | const endpoint_type& endpoint, bool reuse_addr = true) 25 | : basic_io_object(io_context) 26 | { 27 | ...... 28 | } 29 | 30 | Then `acceptor` will accept the clients' connections. Following simple example just shows client's address and close the connection: 31 | 32 | #include 33 | #include 34 | 35 | int main() 36 | { 37 | try 38 | { 39 | boost::asio::io_context io_context; 40 | boost::asio::ip::tcp::acceptor acceptor{ 41 | io_context, 42 | boost::asio::ip::tcp::endpoint{boost::asio::ip::tcp::v6(), 3303}}; 43 | 44 | while (1) 45 | { 46 | boost::asio::ip::tcp::socket socket{io_context}; 47 | acceptor.accept(socket); 48 | 49 | std::cout << socket.remote_endpoint() << " connects to " << socket.local_endpoint() << '\n'; 50 | } 51 | } 52 | catch (std::exception& e) 53 | { 54 | std::cerr << e.what() << '\n'; 55 | return -1; 56 | } 57 | 58 | return 0; 59 | } 60 | 61 | The running result is like this: 62 | 63 | [::ffff:10.217.242.61]:39290 connects to [::ffff:192.168.35.145]:3303 64 | ...... 65 | -------------------------------------------------------------------------------- /posts/udp-communication.md: -------------------------------------------------------------------------------- 1 | # UDP communication 2 | 3 | We have discussed how to communicate through `TCP` enough, so it is time to switch to `UDP` now. `UDP` is a connectionless protocol, and it is easier to use than `TCP`. There is a client/server example. Below is client code: 4 | 5 | #include 6 | #include 7 | 8 | int main() 9 | { 10 | try 11 | { 12 | boost::asio::io_context io_context; 13 | 14 | boost::asio::ip::udp::socket socket{io_context}; 15 | socket.open(boost::asio::ip::udp::v4()); 16 | 17 | socket.send_to( 18 | boost::asio::buffer("Hello world!"), 19 | boost::asio::ip::udp::endpoint{boost::asio::ip::make_address("192.168.35.145"), 3303}); 20 | } 21 | catch (std::exception& e) 22 | { 23 | std::cerr << e.what() << '\n'; 24 | return -1; 25 | } 26 | 27 | return 0; 28 | } 29 | 30 | Although there is no need to call `socket.connect` function, you need call `socket.open` explicitly. Furthermore, the server's endpoint needs to be specified when invoking `socket.send_to`. 31 | 32 | Server code is like this: 33 | 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | int main() 41 | { 42 | try 43 | { 44 | boost::asio::io_context io_context; 45 | 46 | for (;;) 47 | { 48 | boost::asio::ip::udp::socket socket( 49 | io_context, 50 | boost::asio::ip::udp::endpoint{boost::asio::ip::udp::v4(), 3303}); 51 | 52 | boost::asio::ip::udp::endpoint client; 53 | char recv_str[1024] = {}; 54 | 55 | socket.receive_from( 56 | boost::asio::buffer(recv_str), 57 | client); 58 | std::cout << client << ": " << recv_str << '\n'; 59 | } 60 | } 61 | catch (std::exception& e) 62 | { 63 | std::cerr << e.what() << std::endl; 64 | } 65 | 66 | return 0; 67 | } 68 | 69 | Very easy, isn't it? Build and run client and server. The following log will be printed on server side: 70 | 71 | $ ./server 72 | 10.217.242.21:63838: Hello world! 73 | 10.217.242.21:61259: Hello world! 74 | -------------------------------------------------------------------------------- /posts/socket.md: -------------------------------------------------------------------------------- 1 | # Socket 2 | 3 | There are `4` types of socket: 4 | 5 | (1) `basic_stream_socket`: 6 | This socket provides sequenced, reliable, two-way connection based byte streams. `tcp::socket` is an instance of this socket: 7 | 8 | class tcp 9 | { 10 | ...... 11 | /// The TCP socket type. 12 | typedef basic_stream_socket socket; 13 | ...... 14 | } 15 | 16 | (2) `basic_datagram_socket`: 17 | This socket provides connectionless, datagram service. `udp::socket` is an instance of this socket: 18 | 19 | class udp 20 | { 21 | ...... 22 | /// The UDP socket type. 23 | typedef basic_datagram_socket socket; 24 | ...... 25 | } 26 | (3) `basic_raw_socket`: 27 | This socket provides access to internal network protocols and interfaces. `icmp::socket` is an instance of this socket: 28 | 29 | class icmp 30 | { 31 | ...... 32 | /// The ICMP socket type. 33 | typedef basic_raw_socket socket; 34 | ...... 35 | } 36 | (4) `basic_seq_packet_socket`: 37 | This socket combines stream and datagram: it provides a sequenced, reliable, two-way connection based datagrams service. [SCTP](https://en.wikipedia.org/wiki/Stream_Control_Transmission_Protocol) is an example of this type of service. 38 | 39 | All these `4` sockets derive from `basic_socket` class, and need to associate with an `io_context` during initialization. Take `tcp::socket` as an example: 40 | 41 | boost::asio::io_context io_context; 42 | boost::asio::ip::tcp::socket socket{io_context}; 43 | 44 | Please notice the `io_context` should be a reference in constructor of `socket` (Please refer [io_context](io_context.md)). Still use `basic_socket` an an instance, one of its constructor is like following: 45 | 46 | explicit basic_socket(boost::asio::io_context& io_context) 47 | : basic_io_object(io_context) 48 | { 49 | } 50 | 51 | For `basic_io_object` class, it does not support copy constructed/copy assignment: 52 | 53 | ...... 54 | private: 55 | basic_io_object(const basic_io_object&); 56 | void operator=(const basic_io_object&); 57 | ...... 58 | 59 | whilst it can be movable: 60 | 61 | ...... 62 | protectd: 63 | basic_io_object(basic_io_object&& other) 64 | { 65 | ...... 66 | } 67 | basic_io_object& operator=(basic_io_object&& other) 68 | { 69 | ...... 70 | } -------------------------------------------------------------------------------- /posts/connect-server.md: -------------------------------------------------------------------------------- 1 | # Connect server 2 | 3 | The client can use the endpoints returned by [DNS query](dns-query.md) to connect server application. The following is an example: 4 | 5 | #include 6 | #include 7 | 8 | int main() 9 | { 10 | try 11 | { 12 | boost::asio::io_context io_context; 13 | 14 | boost::asio::ip::tcp::resolver resolver{io_context}; 15 | boost::asio::ip::tcp::resolver::results_type endpoints = 16 | resolver.resolve("google.com", "https"); 17 | 18 | boost::asio::ip::tcp::tcp::socket socket{io_context}; 19 | auto endpoint = boost::asio::connect(socket, endpoints); 20 | 21 | std::cout << "Connect to " << endpoint << " successfully!\n"; 22 | } 23 | catch (std::exception& e) 24 | { 25 | std::cerr << e.what() << '\n'; 26 | return -1; 27 | } 28 | 29 | return 0; 30 | } 31 | 32 | The running result is like following: 33 | 34 | Connect to 172.217.194.101:443 successfully! 35 | 36 | Please notice `boost::asio::connect` requires the iterator of endpoints. If you just want one specified endpoint, you can use `connect` member function of socket. Check following code: 37 | 38 | #include 39 | #include 40 | 41 | int main() 42 | { 43 | try 44 | { 45 | boost::asio::io_context io_context; 46 | 47 | boost::asio::ip::tcp::resolver resolver{io_context}; 48 | boost::asio::ip::tcp::resolver::results_type endpoints = 49 | resolver.resolve("google.com", "https"); 50 | 51 | boost::asio::ip::tcp::tcp::socket socket{io_context}; 52 | auto eit = endpoints.cbegin(); 53 | for (; eit != endpoints.cend(); eit++) 54 | { 55 | boost::system::error_code ec; 56 | boost::asio::ip::tcp::endpoint endpoint = *eit; 57 | socket.connect(endpoint, ec); 58 | if (!ec) 59 | { 60 | std::cout << "Connect to " << endpoint << " successfully!\n"; 61 | break; 62 | } 63 | } 64 | 65 | if (eit == endpoints.cend()) 66 | { 67 | std::cout << "Connect failed!\n"; 68 | return -1; 69 | } 70 | } 71 | catch (std::exception& e) 72 | { 73 | std::cerr << e.what() << '\n'; 74 | return -1; 75 | } 76 | 77 | return 0; 78 | } 79 | 80 | The running result is like this: 81 | 82 | Connect to 172.217.194.139:443 successfully! 83 | -------------------------------------------------------------------------------- /posts/synchronous-read-write-operations.md: -------------------------------------------------------------------------------- 1 | # Synchronous read/write operations 2 | 3 | Once the connection is established, the client and server can communicate with each other. Like classical `UNIX` socket programming, `boost::asio` also provides `send` and `receive` functions. Use `basic_stream_socket` as an example and one pair of the implementations is like this: 4 | 5 | template 6 | std::size_t send(const ConstBufferSequence& buffers) 7 | { 8 | ...... 9 | } 10 | ...... 11 | template 12 | std::size_t receive(const MutableBufferSequence& buffers) 13 | { 14 | ...... 15 | } 16 | 17 | Please notice the buffer types of `send/receive` are `ConstBufferSequence/MutableBufferSequence`, and we can use `boost::asio::buffer` function to construct related types. 18 | 19 | Below is a simple client program which sends "`Hello world!`" to server after connection is established: 20 | 21 | #include 22 | #include 23 | 24 | int main() 25 | { 26 | try 27 | { 28 | boost::asio::io_context io_context; 29 | 30 | boost::asio::ip::tcp::endpoint endpoint{ 31 | boost::asio::ip::make_address("10.217.242.61"), 32 | 3303}; 33 | boost::asio::ip::tcp::tcp::socket socket{io_context}; 34 | socket.connect(endpoint); 35 | 36 | std::cout << "Connect to " << endpoint << " successfully!\n"; 37 | 38 | socket.send(boost::asio::buffer("Hello world!")); 39 | } 40 | catch (std::exception& e) 41 | { 42 | std::cerr << e.what() << '\n'; 43 | return -1; 44 | } 45 | 46 | return 0; 47 | } 48 | 49 | There is the server program which waits receiving greeting from client: 50 | 51 | #include 52 | #include 53 | 54 | int main() 55 | { 56 | try 57 | { 58 | boost::asio::io_context io_context; 59 | boost::asio::ip::tcp::acceptor acceptor{ 60 | io_context, 61 | boost::asio::ip::tcp::endpoint{boost::asio::ip::tcp::v4(), 3303}}; 62 | 63 | while (1) 64 | { 65 | boost::asio::ip::tcp::socket socket{io_context}; 66 | acceptor.accept(socket); 67 | 68 | std::cout << socket.remote_endpoint() << " connects to " << socket.local_endpoint() << '\n'; 69 | 70 | char recv_str[1024] = {}; 71 | socket.receive(boost::asio::buffer(recv_str)); 72 | 73 | std::cout << "Receive string: " << recv_str << '\n'; 74 | } 75 | } 76 | catch (std::exception& e) 77 | { 78 | std::cerr << e.what() << '\n'; 79 | return -1; 80 | } 81 | 82 | return 0; 83 | } 84 | 85 | Build and run programs. Client outputs following: 86 | 87 | $ ./client 88 | Connect to 10.217.242.61:3303 successfully! 89 | 90 | Server outputs following: 91 | 92 | $ ./server 93 | 10.217.242.21:64776 connects to 10.217.242.61:3303 94 | Receive string: Hello world! 95 | 96 | If no error occurs, `send` can guarantee at least one byte is sent successfully, and you should check the return value to see whether all bytes are sent successfully or not. `receive` is similar as `send`. `boost::asio::basic_stream_socket` also provides `read_some` and `write_some` which have the same functions as `receive` and `send`. 97 | 98 | If we don't bother to check the middle state (partial bytes are sent successfully), and only care whether all bytes are sent successfully or not, we can use `boost::asio::write` which actually uses `wrtie_some` under the hood. Correspondingly, it is not hard to guess what `boost::asio::read`does. 99 | -------------------------------------------------------------------------------- /posts/asynchronous-read-write-operations.md: -------------------------------------------------------------------------------- 1 | # Asynchronous read/write operations 2 | 3 | Unlike classical `UNIX` socket programming, `boost.asio` has battery-included asynchronous read/write abilities. Still use `basic_stream_socket` as an example, and one pair of the implementations is like this: 4 | 5 | template 6 | BOOST_ASIO_INITFN_RESULT_TYPE(WriteHandler, 7 | void (boost::system::error_code, std::size_t)) 8 | async_send(const ConstBufferSequence& buffers, 9 | BOOST_ASIO_MOVE_ARG(WriteHandler) handler) 10 | { 11 | ....... 12 | } 13 | template 14 | BOOST_ASIO_INITFN_RESULT_TYPE(ReadHandler, 15 | void (boost::system::error_code, std::size_t)) 16 | async_receive(const MutableBufferSequence& buffers, 17 | BOOST_ASIO_MOVE_ARG(ReadHandler) handler) 18 | { 19 | ....... 20 | } 21 | 22 | Since `async_send` and `async_receive` functions will return immediately, and not block current thread, you should pass a callback function as the parameter which receives the result of read/write operations: 23 | 24 | void handler( 25 | const boost::system::error_code& error, // Result of operation. 26 | std::size_t bytes_transferred // Number of bytes processed. 27 | ) 28 | 29 | There is a simple client/server example. Below is client code: 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | void callback( 37 | const boost::system::error_code& error, 38 | std::size_t bytes_transferred, 39 | std::shared_ptr socket, 40 | std::string str) 41 | { 42 | if (error) 43 | { 44 | std::cout << error.message() << '\n'; 45 | } 46 | else if (bytes_transferred == str.length()) 47 | { 48 | std::cout << "Message is sent successfully!" << '\n'; 49 | } 50 | else 51 | { 52 | socket->async_send( 53 | boost::asio::buffer(str.c_str() + bytes_transferred, str.length() - bytes_transferred), 54 | std::bind(callback, std::placeholders::_1, std::placeholders::_2, socket, str)); 55 | } 56 | } 57 | 58 | 59 | int main() 60 | { 61 | try 62 | { 63 | boost::asio::io_context io_context; 64 | 65 | boost::asio::ip::tcp::endpoint endpoint{ 66 | boost::asio::ip::make_address("192.168.35.145"), 67 | 3303}; 68 | 69 | std::shared_ptr socket{new boost::asio::ip::tcp::socket{io_context}}; 70 | socket->connect(endpoint); 71 | 72 | std::cout << "Connect to " << endpoint << " successfully!\n"; 73 | 74 | std::string str{"Hello world!"}; 75 | socket->async_send( 76 | boost::asio::buffer(str), 77 | std::bind(callback, std::placeholders::_1, std::placeholders::_2, socket, str)); 78 | socket->get_executor().context().run(); 79 | } 80 | catch (std::exception& e) 81 | { 82 | std::cerr << e.what() << '\n'; 83 | return -1; 84 | } 85 | 86 | return 0; 87 | } 88 | 89 | Let's go through the code: 90 | 91 | (1) Since socket object is non-copyable (please refer [socket](socket.md)), socket is created as an shared pointer: 92 | 93 | ...... 94 | std::shared_ptr socket{new boost::asio::ip::tcp::socket{io_context}}; 95 | ...... 96 | 97 | (2) Because the callback only has two parameters, it needs to use `std::bind` to pass additional parameters: 98 | 99 | ...... 100 | std::bind(callback, std::placeholders::_1, std::placeholders::_2, socket, str) 101 | ...... 102 | 103 | (3) `async_send` does not guarantee all the bytes are sent (`boost::asio::async_write` returns either all bytes are sent successfully or an error occurs), so needs to reissue `async_send` in callback: 104 | 105 | ...... 106 | if (error) 107 | { 108 | ...... 109 | } 110 | else if (bytes_transferred == str.length()) 111 | { 112 | ...... 113 | } 114 | else 115 | { 116 | socket->async_send(......); 117 | } 118 | (4) `io_context.run` function will block until all work has finished and there are no 119 | more handlers to be dispatched, or until the `io_context` has been stopped: 120 | 121 | socket->get_executor().context().run(); 122 | If there is no `io_context.run` function, the program will exit immediately. 123 | 124 | Check the server code who uses `async_receive`: 125 | 126 | #include 127 | #include 128 | #include 129 | #include 130 | #include 131 | 132 | void callback( 133 | const boost::system::error_code& error, 134 | std::size_t, 135 | char recv_str[]) { 136 | if (error) 137 | { 138 | std::cout << error.message() << '\n'; 139 | } 140 | else 141 | { 142 | std::cout << recv_str << '\n'; 143 | } 144 | } 145 | 146 | int main() 147 | { 148 | try 149 | { 150 | boost::asio::io_context io_context; 151 | 152 | boost::asio::ip::tcp::acceptor acceptor( 153 | io_context, 154 | boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 3303)); 155 | 156 | for (;;) 157 | { 158 | boost::asio::ip::tcp::socket socket(io_context); 159 | acceptor.accept(socket); 160 | 161 | char recv_str[1024] = {}; 162 | socket.async_receive( 163 | boost::asio::buffer(recv_str), 164 | std::bind(callback, std::placeholders::_1, std::placeholders::_2, recv_str)); 165 | socket.get_executor().context().run(); 166 | socket.get_executor().context().restart(); 167 | } 168 | } 169 | catch (std::exception& e) 170 | { 171 | std::cerr << e.what() << std::endl; 172 | } 173 | 174 | return 0; 175 | } 176 | 177 | There are two caveats you need to pay attention to: 178 | 179 | (1) Just for demo purpose: for every client, the callback is called only once; 180 | (2) `io_context.restart` must be called to invoke another `io_context.run`. 181 | 182 | Correspondingly, you can also check how to use `boost::asio::async_read`. 183 | 184 | Build and run programs. Client outputs following: 185 | 186 | $ ./client 187 | Connect to 192.168.35.145:3303 successfully! 188 | Message is sent successfully! 189 | 190 | Server outputs following: 191 | 192 | $ ./server 193 | Hello world! 194 | --------------------------------------------------------------------------------