Making things simpler

86

In the previous lesson we've reviewed a very simple chat server. Despite 160 lines of code is quite a small program, making the same server in high-level programming languages like Python or Erlang would take lesser code.

“But C++ is not Python nor Erlang. Isn't it low-level programming language?” — you may ask. Well, the answer is yes and no. C++ is an area where too much things are up to you and depend on you. And too much freedom means too much responsibility.

Yes, you could write quite a low-level code, dealing with raw memory and raw pointers. You could have to take byte order into consideration. Your code could generate unrecoverable errors leading to your application crash, as well as several other things that you'll never have to face in Python unless you want to. However, you could also layer your code in such a manner when each layer has a very narrow set of responsibilities related to its own level of abstraction and the most top layer of this code could be as much high-level as Python or Erlang.

So, C++ is a programming language where you should implement your code in a manner of stack of narrow layers, and you should do this very carefully.

Boost.Asio is a library which gives you low-level functionality. In your big production-quality application you shouldn't deal with Boost.Asio functions directly as you shouldn't use fopen or mutexes. Boost.Beast — the library which gives you HTTP and WebSockets functionality and built on top of Boost.Asio — gives you +1 level of abstration. However even that level is too low to use in a production-quality application directly. Accoring to Vinnie Falco — Boost.Beast author — the library is not a ready-to-use server or client. It is a set of tools which you should use to build your own libraries. And you should build your applications on top of your libraries, which are built on top of Boost.Beast, which is built on top of Boost.Asio. That's how you do things in C++.

So, during the development of your application, it will grow over time. And you should organize your code into narrow layers of functionality where each layer solve its own narrow set of problems. So, eventually, your chat server could be implemented something like that:

#include <chat/server.hpp>

using message_type = std::string;
using session_type = chat::session<message_type>;
using server_type = chat::server<session_type>;

class server
{
public:

    server(io::io_context& io_context, std::uint16_t port)
    : srv(io_context, port)
    {
        srv.on_join([&] (session_type& client)
        {
            client.post("Welcome to chat");
            srv.broadcast("We have a newcomer");
        });

        srv.on_leave([&]
        {
            srv.broadcast("We are one less");
        })

        srv.on_message([&] (message_type const& message)
        {
            srv.broadcast(message);
        })
    }

    void start()
    {
        srv.start();
    }

private:

    server_type srv;
};

int main()
{
    io::io_context io_context;
    server srv(io_context, 15001);
    srv.start();
    io_context.run();
    return 0;
}
Share this page:

Learning plan

How to handle Boost.Asio errors
There are several new things we should learn before jumping into a bigger example of a server
A bigger example of a server where you'll need to apply everything you've learned so far
9. Making things simpler
Principles you should take into consideration during the development of your applications
How to keep io_context::run running even when there is no work to do
How execute a regular code within io_context::run polling loop
We've dealt with a single-threaded environment so far; now it's time to go multithreading