How to use the JavaScript includes() function

The JavaScript array method includes(), like so many other array methods, does what its name proclaims: it will check an array to see if it includes a specific value. This array method is handy and incredibly easy to use!

What does includes() do?

The JavaScript array method includes() will check an array to see if it includes the specific value that you provided. This array method returns a boolean: true if the array includes the value or false if it does not.

#coding #programming #javascript #web-development #array-methods

What is GEEK

Buddha Community

How to use the JavaScript includes() function
Lina  Biyinzika

Lina Biyinzika

1651147200

Ultra Fast and Low Latency Asynchronous Socket Server & Client C++

CppServer

Ultra fast and low latency asynchronous socket server & client C++ library with support TCP, SSL, UDP, HTTP, HTTPS, WebSocket protocols and 10K connections problem solution.

Has integration with high-level message protocol based on Fast Binary Encoding

CppServer API reference

Contents

Features

Requirements

Optional:

How to build?

Linux: install required packages

sudo apt-get install -y binutils-dev uuid-dev libssl-dev

Install gil (git links) tool

pip3 install gil

Setup repository

git clone https://github.com/chronoxor/CppServer.git
cd CppServer
gil update

Linux

cd build
./unix.sh

MacOS

cd build
./unix.sh

Windows (MSYS2)

cd build
unix.bat

Windows (MinGW)

cd build
mingw.bat

Windows (Visual Studio)

cd build
vs.bat

Examples

Example: Asio service

Asio service is used to host all clients/servers based on Asio C++ library. It is implemented based on Asio C++ Library and use a separate thread to perform all asynchronous IO operations and communications.

The common usecase is to instantiate one Asio service, start the service and attach TCP/UDP/WebSocket servers or/and clients to it. One Asio service can handle several servers and clients asynchronously at the same time in one I/O thread. If you want to scale your servers or clients it is possible to create and use more than one Asio services to handle your servers/clients in balance.

Also it is possible to dispatch or post your custom handler into I/O thread. Dispatch will execute the handler immediately if the current thread is I/O one. Otherwise the handler will be enqueued to the I/O queue. In opposite the post method will always enqueue the handler into the I/O queue.

Here comes an example of using custom Asio service with dispatch/post methods:

#include "server/asio/service.h"
#include "threads/thread.h"

#include <iostream>

int main(int argc, char** argv)
{
    // Create a new Asio service
    auto service = std::make_shared<CppServer::Asio::Service>();

    // Start the Asio service
    std::cout << "Asio service starting...";
    service->Start();
    std::cout << "Done!" << std::endl;

    // Dispatch
    std::cout << "1 - Dispatch from the main thread with Id " << CppCommon::Thread::CurrentThreadId() << std::endl;
    service->Dispatch([service]()
    {
        std::cout << "1.1 - Dispatched in thread with Id " << CppCommon::Thread::CurrentThreadId() << std::endl;

        std::cout << "1.2 - Dispatch from thread with Id " << CppCommon::Thread::CurrentThreadId() << std::endl;
        service->Dispatch([service]()
        {
            std::cout << "1.2.1 - Dispatched in thread with Id " << CppCommon::Thread::CurrentThreadId() << std::endl;
        });

        std::cout << "1.3 - Post from thread with Id " << CppCommon::Thread::CurrentThreadId() << std::endl;
        service->Post([service]()
        {
            std::cout << "1.3.1 - Posted in thread with Id " << CppCommon::Thread::CurrentThreadId() << std::endl;
        });
    });

    // Post
    std::cout << "2 - Post from the main thread with Id " << CppCommon::Thread::CurrentThreadId() << std::endl;
    service->Post([service]()
    {
        std::cout << "2.1 - Posted in thread with Id " << CppCommon::Thread::CurrentThreadId() << std::endl;

        std::cout << "2.2 - Dispatch from thread with Id " << CppCommon::Thread::CurrentThreadId() << std::endl;
        service->Dispatch([service]()
        {
            std::cout << "2.2.1 - Dispatched in thread with Id " << CppCommon::Thread::CurrentThreadId() << std::endl;
        });

        std::cout << "2.3 - Post from thread with Id " << CppCommon::Thread::CurrentThreadId() << std::endl;
        service->Post([service]()
        {
            std::cout << "2.3.1 - Posted in thread with Id " << CppCommon::Thread::CurrentThreadId() << std::endl;
        });
    });

    // Wait for a while...
    CppCommon::Thread::Sleep(1000);

    // Stop the Asio service
    std::cout << "Asio service stopping...";
    service->Stop();
    std::cout << "Done!" << std::endl;

    return 0;
}

Output of the above example is the following:

Asio service started!
1 - Dispatch from the main thread with Id 16744
2 - Post from the main thread with Id 16744
1.1 - Dispatched in thread with Id 19920
1.2 - Dispatch from thread with Id 19920
1.2.1 - Dispatched in thread with Id 19920
1.3 - Post from thread with Id 19920
2.1 - Posted in thread with Id 19920
2.2 - Dispatch from thread with Id 19920
2.2.1 - Dispatched in thread with Id 19920
2.3 - Post from thread with Id 19920
1.3.1 - Posted in thread with Id 19920
2.3.1 - Posted in thread with Id 19920
Asio service stopped!

Example: Asio timer

Here comes the example of Asio timer. It can be used to wait for some action in future with providing absolute time or relative time span. Asio timer can be used in synchronous or asynchronous modes.

#include "server/asio/timer.h"
#include "threads/thread.h"

#include <iostream>

class AsioTimer : public CppServer::Asio::Timer
{
public:
    using CppServer::Asio::Timer::Timer;

protected:
    void onTimer(bool canceled) override
    {
        std::cout << "Asio timer " << (canceled ? "canceled" : "expired") << std::endl;
    }

    void onError(int error, const std::string& category, const std::string& message) override
    {
        std::cout << "Asio timer caught an error with code " << error << " and category '" << category << "': " << message << std::endl;
    }
};

int main(int argc, char** argv)
{
    // Create a new Asio service
    auto service = std::make_shared<CppServer::Asio::Service>();

    // Start the Asio service
    std::cout << "Asio service starting...";
    service->Start();
    std::cout << "Done!" << std::endl;

    // Create a new Asio timer
    auto timer = std::make_shared<AsioTimer>(service);

    // Setup and synchronously wait for the timer
    timer->Setup(CppCommon::UtcTime() + CppCommon::Timespan::seconds(1));
    timer->WaitSync();

    // Setup and asynchronously wait for the timer
    timer->Setup(CppCommon::Timespan::seconds(1));
    timer->WaitAsync();

    // Wait for a while...
    CppCommon::Thread::Sleep(2000);

    // Setup and asynchronously wait for the timer
    timer->Setup(CppCommon::Timespan::seconds(1));
    timer->WaitAsync();

    // Wait for a while...
    CppCommon::Thread::Sleep(500);

    // Cancel the timer
    timer->Cancel();

    // Wait for a while...
    CppCommon::Thread::Sleep(500);

    // Stop the Asio service
    std::cout << "Asio service stopping...";
    service->Stop();
    std::cout << "Done!" << std::endl;

    return 0;
}

Output of the above example is the following:

Asio service starting...Done!
Timer was expired
Timer was canceled
Asio service stopping...Done!

Example: TCP chat server

Here comes the example of the TCP chat server. It handles multiple TCP client sessions and multicast received message from any session to all ones. Also it is possible to send admin message directly from the server.

#include "server/asio/tcp_server.h"
#include "threads/thread.h"

#include <iostream>

class ChatSession : public CppServer::Asio::TCPSession
{
public:
    using CppServer::Asio::TCPSession::TCPSession;

protected:
    void onConnected() override
    {
        std::cout << "Chat TCP session with Id " << id() << " connected!" << std::endl;

        // Send invite message
        std::string message("Hello from TCP chat! Please send a message or '!' to disconnect the client!");
        SendAsync(message);
    }

    void onDisconnected() override
    {
        std::cout << "Chat TCP session with Id " << id() << " disconnected!" << std::endl;
    }

    void onReceived(const void* buffer, size_t size) override
    {
        std::string message((const char*)buffer, size);
        std::cout << "Incoming: " << message << std::endl;

        // Multicast message to all connected sessions
        server()->Multicast(message);

        // If the buffer starts with '!' the disconnect the current session
        if (message == "!")
            DisconnectAsync();
    }

    void onError(int error, const std::string& category, const std::string& message) override
    {
        std::cout << "Chat TCP session caught an error with code " << error << " and category '" << category << "': " << message << std::endl;
    }
};

class ChatServer : public CppServer::Asio::TCPServer
{
public:
    using CppServer::Asio::TCPServer::TCPServer;

protected:
    std::shared_ptr<CppServer::Asio::TCPSession> CreateSession(std::shared_ptr<CppServer::Asio::TCPServer> server) override
    {
        return std::make_shared<ChatSession>(server);
    }

protected:
    void onError(int error, const std::string& category, const std::string& message) override
    {
        std::cout << "Chat TCP server caught an error with code " << error << " and category '" << category << "': " << message << std::endl;
    }
};

int main(int argc, char** argv)
{
    // TCP server port
    int port = 1111;
    if (argc > 1)
        port = std::atoi(argv[1]);

    std::cout << "TCP server port: " << port << std::endl;

    // Create a new Asio service
    auto service = std::make_shared<CppServer::Asio::Service>();

    // Start the Asio service
    std::cout << "Asio service starting...";
    service->Start();
    std::cout << "Done!" << std::endl;

    // Create a new TCP chat server
    auto server = std::make_shared<ChatServer>(service, port);

    // Start the server
    std::cout << "Server starting...";
    server->Start();
    std::cout << "Done!" << std::endl;

    std::cout << "Press Enter to stop the server or '!' to restart the server..." << std::endl;

    // Perform text input
    std::string line;
    while (getline(std::cin, line))
    {
        if (line.empty())
            break;

        // Restart the server
        if (line == "!")
        {
            std::cout << "Server restarting...";
            server->Restart();
            std::cout << "Done!" << std::endl;
            continue;
        }

        // Multicast admin message to all sessions
        line = "(admin) " + line;
        server->Multicast(line);
    }

    // Stop the server
    std::cout << "Server stopping...";
    server->Stop();
    std::cout << "Done!" << std::endl;

    // Stop the Asio service
    std::cout << "Asio service stopping...";
    service->Stop();
    std::cout << "Done!" << std::endl;

    return 0;
}

Example: TCP chat client

Here comes the example of the TCP chat client. It connects to the TCP chat server and allows to send message to it and receive new messages.

#include "server/asio/tcp_client.h"
#include "threads/thread.h"

#include <atomic>
#include <iostream>

class ChatClient : public CppServer::Asio::TCPClient
{
public:
    ChatClient(std::shared_ptr<CppServer::Asio::Service> service, const std::string& address, int port)
        : CppServer::Asio::TCPClient(service, address, port)
    {
        _stop = false;
    }

    void DisconnectAndStop()
    {
        _stop = true;
        DisconnectAsync();
        while (IsConnected())
            CppCommon::Thread::Yield();
    }

protected:
    void onConnected() override
    {
        std::cout << "Chat TCP client connected a new session with Id " << id() << std::endl;
    }

    void onDisconnected() override
    {
        std::cout << "Chat TCP client disconnected a session with Id " << id() << std::endl;

        // Wait for a while...
        CppCommon::Thread::Sleep(1000);

        // Try to connect again
        if (!_stop)
            ConnectAsync();
    }

    void onReceived(const void* buffer, size_t size) override
    {
        std::cout << "Incoming: " << std::string((const char*)buffer, size) << std::endl;
    }

    void onError(int error, const std::string& category, const std::string& message) override
    {
        std::cout << "Chat TCP client caught an error with code " << error << " and category '" << category << "': " << message << std::endl;
    }

private:
    std::atomic<bool> _stop;
};

int main(int argc, char** argv)
{
    // TCP server address
    std::string address = "127.0.0.1";
    if (argc > 1)
        address = argv[1];

    // TCP server port
    int port = 1111;
    if (argc > 2)
        port = std::atoi(argv[2]);

    std::cout << "TCP server address: " << address << std::endl;
    std::cout << "TCP server port: " << port << std::endl;

    // Create a new Asio service
    auto service = std::make_shared<CppServer::Asio::Service>();

    // Start the Asio service
    std::cout << "Asio service starting...";
    service->Start();
    std::cout << "Done!" << std::endl;

    // Create a new TCP chat client
    auto client = std::make_shared<ChatClient>(service, address, port);

    // Connect the client
    std::cout << "Client connecting...";
    client->ConnectAsync();
    std::cout << "Done!" << std::endl;

    std::cout << "Press Enter to stop the client or '!' to reconnect the client..." << std::endl;

    // Perform text input
    std::string line;
    while (getline(std::cin, line))
    {
        if (line.empty())
            break;

        // Disconnect the client
        if (line == "!")
        {
            std::cout << "Client disconnecting...";
            client->DisconnectAsync();
            std::cout << "Done!" << std::endl;
            continue;
        }

        // Send the entered text to the chat server
        client->SendAsync(line);
    }

    // Disconnect the client
    std::cout << "Client disconnecting...";
    client->DisconnectAndStop();
    std::cout << "Done!" << std::endl;

    // Stop the Asio service
    std::cout << "Asio service stopping...";
    service->Stop();
    std::cout << "Done!" << std::endl;

    return 0;
}

Example: SSL chat server

Here comes the example of the SSL chat server. It handles multiple SSL client sessions and multicast received message from any session to all ones. Also it is possible to send admin message directly from the server.

This example is very similar to the TCP one except the code that prepares SSL context and handshake handler.

#include "server/asio/ssl_server.h"
#include "threads/thread.h"

#include <iostream>

class ChatSession : public CppServer::Asio::SSLSession
{
public:
    using CppServer::Asio::SSLSession::SSLSession;

protected:
    void onConnected() override
    {
        std::cout << "Chat SSL session with Id " << id() << " connected!" << std::endl;
    }

    void onHandshaked() override
    {
        std::cout << "Chat SSL session with Id " << id() << " handshaked!" << std::endl;

        // Send invite message
        std::string message("Hello from SSL chat! Please send a message or '!' to disconnect the client!");
        SendAsync(message.data(), message.size());
    }

    void onDisconnected() override
    {
        std::cout << "Chat SSL session with Id " << id() << " disconnected!" << std::endl;
    }

    void onReceived(const void* buffer, size_t size) override
    {
        std::string message((const char*)buffer, size);
        std::cout << "Incoming: " << message << std::endl;

        // Multicast message to all connected sessions
        server()->Multicast(message);

        // If the buffer starts with '!' the disconnect the current session
        if (message == "!")
            DisconnectAsync();
    }

    void onError(int error, const std::string& category, const std::string& message) override
    {
        std::cout << "Chat SSL session caught an error with code " << error << " and category '" << category << "': " << message << std::endl;
    }
};

class ChatServer : public CppServer::Asio::SSLServer
{
public:
    using CppServer::Asio::SSLServer::SSLServer;

protected:
    std::shared_ptr<CppServer::Asio::SSLSession> CreateSession(std::shared_ptr<CppServer::Asio::SSLServer> server) override
    {
        return std::make_shared<ChatSession>(server);
    }

protected:
    void onError(int error, const std::string& category, const std::string& message) override
    {
        std::cout << "Chat TCP server caught an error with code " << error << " and category '" << category << "': " << message << std::endl;
    }
};

int main(int argc, char** argv)
{
    // SSL server port
    int port = 2222;
    if (argc > 1)
        port = std::atoi(argv[1]);

    std::cout << "SSL server port: " << port << std::endl;

    // Create a new Asio service
    auto service = std::make_shared<CppServer::Asio::Service>();

    // Start the Asio service
    std::cout << "Asio service starting...";
    service->Start();
    std::cout << "Done!" << std::endl;

    // Create and prepare a new SSL server context
    auto context = std::make_shared<CppServer::Asio::SSLContext>(asio::ssl::context::tlsv12);
    context->set_password_callback([](size_t max_length, asio::ssl::context::password_purpose purpose) -> std::string { return "qwerty"; });
    context->use_certificate_chain_file("../tools/certificates/server.pem");
    context->use_private_key_file("../tools/certificates/server.pem", asio::ssl::context::pem);
    context->use_tmp_dh_file("../tools/certificates/dh4096.pem");

    // Create a new SSL chat server
    auto server = std::make_shared<ChatServer>(service, context, port);

    // Start the server
    std::cout << "Server starting...";
    server->Start();
    std::cout << "Done!" << std::endl;

    std::cout << "Press Enter to stop the server or '!' to restart the server..." << std::endl;

    // Perform text input
    std::string line;
    while (getline(std::cin, line))
    {
        if (line.empty())
            break;

        // Restart the server
        if (line == "!")
        {
            std::cout << "Server restarting...";
            server->Restart();
            std::cout << "Done!" << std::endl;
            continue;
        }

        // Multicast admin message to all sessions
        line = "(admin) " + line;
        server->Multicast(line);
    }

    // Stop the server
    std::cout << "Server stopping...";
    server->Stop();
    std::cout << "Done!" << std::endl;

    // Stop the Asio service
    std::cout << "Asio service stopping...";
    service->Stop();
    std::cout << "Done!" << std::endl;

    return 0;
}

Example: SSL chat client

Here comes the example of the SSL chat client. It connects to the SSL chat server and allows to send message to it and receive new messages.

This example is very similar to the TCP one except the code that prepares SSL context and handshake handler.

#include "server/asio/ssl_client.h"
#include "threads/thread.h"

#include <atomic>
#include <iostream>

class ChatClient : public CppServer::Asio::SSLClient
{
public:
    ChatClient(std::shared_ptr<CppServer::Asio::Service> service, std::shared_ptr<CppServer::Asio::SSLContext> context, const std::string& address, int port)
        : CppServer::Asio::SSLClient(service, context, address, port)
    {
        _stop = false;
    }

    void DisconnectAndStop()
    {
        _stop = true;
        DisconnectAsync();
        while (IsConnected())
            CppCommon::Thread::Yield();
    }

protected:
    void onConnected() override
    {
        std::cout << "Chat SSL client connected a new session with Id " << id() << std::endl;
    }

    void onHandshaked() override
    {
        std::cout << "Chat SSL client handshaked a new session with Id " << id() << std::endl;
    }

    void onDisconnected() override
    {
        std::cout << "Chat SSL client disconnected a session with Id " << id() << std::endl;

        // Wait for a while...
        CppCommon::Thread::Sleep(1000);

        // Try to connect again
        if (!_stop)
            ConnectAsync();
    }

    void onReceived(const void* buffer, size_t size) override
    {
        std::cout << "Incoming: " << std::string((const char*)buffer, size) << std::endl;
    }

    void onError(int error, const std::string& category, const std::string& message) override
    {
        std::cout << "Chat SSL client caught an error with code " << error << " and category '" << category << "': " << message << std::endl;
    }

private:
    std::atomic<bool> _stop;
};

int main(int argc, char** argv)
{
    // SSL server address
    std::string address = "127.0.0.1";
    if (argc > 1)
        address = argv[1];

    // SSL server port
    int port = 2222;
    if (argc > 2)
        port = std::atoi(argv[2]);

    std::cout << "SSL server address: " << address << std::endl;
    std::cout << "SSL server port: " << port << std::endl;

    // Create a new Asio service
    auto service = std::make_shared<CppServer::Asio::Service>();

    // Start the Asio service
    std::cout << "Asio service starting...";
    service->Start();
    std::cout << "Done!" << std::endl;

    // Create and prepare a new SSL client context
    auto context = std::make_shared<CppServer::Asio::SSLContext>(asio::ssl::context::tlsv12);
    context->set_default_verify_paths();
    context->set_root_certs();
    context->set_verify_mode(asio::ssl::verify_peer | asio::ssl::verify_fail_if_no_peer_cert);
    context->load_verify_file("../tools/certificates/ca.pem");

    // Create a new SSL chat client
    auto client = std::make_shared<ChatClient>(service, context, address, port);

    // Connect the client
    std::cout << "Client connecting...";
    client->ConnectAsync();
    std::cout << "Done!" << std::endl;

    std::cout << "Press Enter to stop the client or '!' to reconnect the client..." << std::endl;

    // Perform text input
    std::string line;
    while (getline(std::cin, line))
    {
        if (line.empty())
            break;

        // Disconnect the client
        if (line == "!")
        {
            std::cout << "Client disconnecting...";
            client->DisconnectAsync();
            std::cout << "Done!" << std::endl;
            continue;
        }

        // Send the entered text to the chat server
        client->SendAsync(line);
    }

    // Disconnect the client
    std::cout << "Client disconnecting...";
    client->DisconnectAndStop();
    std::cout << "Done!" << std::endl;

    // Stop the Asio service
    std::cout << "Asio service stopping...";
    service->Stop();
    std::cout << "Done!" << std::endl;

    return 0;
}

Example: UDP echo server

Here comes the example of the UDP echo server. It receives a datagram mesage from any UDP client and resend it back without any changes.

#include "server/asio/udp_server.h"
#include "threads/thread.h"

#include <iostream>

class EchoServer : public CppServer::Asio::UDPServer
{
public:
    using CppServer::Asio::UDPServer::UDPServer;

protected:
    void onStarted() override
    {
        // Start receive datagrams
        ReceiveAsync();
    }

    void onReceived(const asio::ip::udp::endpoint& endpoint, const void* buffer, size_t size) override
    {
        std::string message((const char*)buffer, size);
        std::cout << "Incoming: " << message << std::endl;

        // Echo the message back to the sender
        SendAsync(endpoint, message);
    }

    void onSent(const asio::ip::udp::endpoint& endpoint, size_t sent) override
    {
        // Continue receive datagrams
        ReceiveAsync();
    }

    void onError(int error, const std::string& category, const std::string& message) override
    {
        std::cout << "Echo UDP server caught an error with code " << error << " and category '" << category << "': " << message << std::endl;
    }
};

int main(int argc, char** argv)
{
    // UDP server port
    int port = 3333;
    if (argc > 1)
        port = std::atoi(argv[1]);

    std::cout << "UDP server port: " << port << std::endl;

    // Create a new Asio service
    auto service = std::make_shared<CppServer::Asio::Service>();

    // Start the Asio service
    std::cout << "Asio service starting...";
    service->Start();
    std::cout << "Done!" << std::endl;

    // Create a new UDP echo server
    auto server = std::make_shared<EchoServer>(service, port);

    // Start the server
    std::cout << "Server starting...";
    server->Start();
    std::cout << "Done!" << std::endl;

    std::cout << "Press Enter to stop the server or '!' to restart the server..." << std::endl;

    // Perform text input
    std::string line;
    while (getline(std::cin, line))
    {
        if (line.empty())
            break;

        // Restart the server
        if (line == "!")
        {
            std::cout << "Server restarting...";
            server->Restart();
            std::cout << "Done!" << std::endl;
            continue;
        }
    }

    // Stop the server
    std::cout << "Server stopping...";
    server->Stop();
    std::cout << "Done!" << std::endl;

    // Stop the Asio service
    std::cout << "Asio service stopping...";
    service->Stop();
    std::cout << "Done!" << std::endl;

    return 0;
}

Example: UDP echo client

Here comes the example of the UDP echo client. It sends user datagram message to UDP server and listen for response.

#include "server/asio/udp_client.h"
#include "threads/thread.h"

#include <atomic>
#include <iostream>

class EchoClient : public CppServer::Asio::UDPClient
{
public:
    EchoClient(std::shared_ptr<CppServer::Asio::Service> service, const std::string& address, int port)
        : CppServer::Asio::UDPClient(service, address, port)
    {
        _stop = false;
    }

    void DisconnectAndStop()
    {
        _stop = true;
        DisconnectAsync();
        while (IsConnected())
            CppCommon::Thread::Yield();
    }

protected:
    void onConnected() override
    {
        std::cout << "Echo UDP client connected a new session with Id " << id() << std::endl;

        // Start receive datagrams
        ReceiveAsync();
    }

    void onDisconnected() override
    {
        std::cout << "Echo UDP client disconnected a session with Id " << id() << std::endl;

        // Wait for a while...
        CppCommon::Thread::Sleep(1000);

        // Try to connect again
        if (!_stop)
            ConnectAsync();
    }

    void onReceived(const asio::ip::udp::endpoint& endpoint, const void* buffer, size_t size) override
    {
        std::cout << "Incoming: " << std::string((const char*)buffer, size) << std::endl;

        // Continue receive datagrams
        ReceiveAsync();
    }

    void onError(int error, const std::string& category, const std::string& message) override
    {
        std::cout << "Echo UDP client caught an error with code " << error << " and category '" << category << "': " << message << std::endl;
    }

private:
    std::atomic<bool> _stop;
};

int main(int argc, char** argv)
{
    // UDP server address
    std::string address = "127.0.0.1";
    if (argc > 1)
        address = argv[1];

    // UDP server port
    int port = 3333;
    if (argc > 2)
        port = std::atoi(argv[2]);

    std::cout << "UDP server address: " << address << std::endl;
    std::cout << "UDP server port: " << port << std::endl;

    // Create a new Asio service
    auto service = std::make_shared<CppServer::Asio::Service>();

    // Start the Asio service
    std::cout << "Asio service starting...";
    service->Start();
    std::cout << "Done!" << std::endl;

    // Create a new UDP echo client
    auto client = std::make_shared<EchoClient>(service, address, port);

    // Connect the client
    std::cout << "Client connecting...";
    client->ConnectAsync();
    std::cout << "Done!" << std::endl;

    std::cout << "Press Enter to stop the client or '!' to reconnect the client..." << std::endl;

    // Perform text input
    std::string line;
    while (getline(std::cin, line))
    {
        if (line.empty())
            break;

        // Disconnect the client
        if (line == "!")
        {
            std::cout << "Client disconnecting...";
            client->DisconnectAsync();
            std::cout << "Done!" << std::endl;
            continue;
        }

        // Send the entered text to the echo server
        client->SendSync(line);
    }

    // Disconnect the client
    std::cout << "Client disconnecting...";
    client->DisconnectAndStop();
    std::cout << "Done!" << std::endl;

    // Stop the Asio service
    std::cout << "Asio service stopping...";
    service->Stop();
    std::cout << "Done!" << std::endl;

    return 0;
}

Example: UDP multicast server

Here comes the example of the UDP multicast server. It use multicast IP address to multicast datagram messages to all client that joined corresponding UDP multicast group.

#include "server/asio/udp_server.h"
#include "threads/thread.h"

#include <iostream>

class MulticastServer : public CppServer::Asio::UDPServer
{
public:
    using CppServer::Asio::UDPServer::UDPServer;

protected:
    void onError(int error, const std::string& category, const std::string& message) override
    {
        std::cout << "Multicast UDP server caught an error with code " << error << " and category '" << category << "': " << message << std::endl;
    }
};

int main(int argc, char** argv)
{
    // UDP multicast address
    std::string multicast_address = "239.255.0.1";
    if (argc > 1)
        multicast_address = argv[1];

    // UDP multicast port
    int multicast_port = 3334;
    if (argc > 2)
        multicast_port = std::atoi(argv[2]);

    std::cout << "UDP multicast address: " << multicast_address << std::endl;
    std::cout << "UDP multicast port: " << multicast_port << std::endl;

    // Create a new Asio service
    auto service = std::make_shared<CppServer::Asio::Service>();

    // Start the Asio service
    std::cout << "Asio service starting...";
    service->Start();
    std::cout << "Done!" << std::endl;

    // Create a new UDP multicast server
    auto server = std::make_shared<MulticastServer>(service, 0);

    // Start the multicast server
    std::cout << "Server starting...";
    server->Start(multicast_address, multicast_port);
    std::cout << "Done!" << std::endl;

    std::cout << "Press Enter to stop the server or '!' to restart the server..." << std::endl;

    // Perform text input
    std::string line;
    while (getline(std::cin, line))
    {
        if (line.empty())
            break;

        // Restart the server
        if (line == "!")
        {
            std::cout << "Server restarting...";
            server->Restart();
            std::cout << "Done!" << std::endl;
            continue;
        }

        // Multicast admin message to all sessions
        line = "(admin) " + line;
        server->MulticastSync(line);
    }

    // Stop the server
    std::cout << "Server stopping...";
    server->Stop();
    std::cout << "Done!" << std::endl;

    // Stop the Asio service
    std::cout << "Asio service stopping...";
    service->Stop();
    std::cout << "Done!" << std::endl;

    return 0;
}

Example: UDP multicast client

Here comes the example of the UDP multicast client. It use multicast IP address and joins UDP multicast group in order to receive multicasted datagram messages from UDP server.

#include "server/asio/udp_client.h"
#include "threads/thread.h"

#include <atomic>
#include <iostream>

class MulticastClient : public CppServer::Asio::UDPClient
{
public:
    MulticastClient(std::shared_ptr<CppServer::Asio::Service> service, const std::string& address, const std::string& multicast, int port)
        : CppServer::Asio::UDPClient(service, address, port),
          _multicast(multicast)
    {
        _stop = false;
    }

    void DisconnectAndStop()
    {
        _stop = true;
        DisconnectAsync();
        while (IsConnected())
            CppCommon::Thread::Yield();
    }

protected:
    void onConnected() override
    {
        std::cout << "Multicast UDP client connected a new session with Id " << id() << std::endl;

        // Join UDP multicast group
        JoinMulticastGroupAsync(_multicast);

        // Start receive datagrams
        ReceiveAsync();
    }

    void onDisconnected() override
    {
        std::cout << "Multicast UDP client disconnected a session with Id " << id() << std::endl;

        // Wait for a while...
        CppCommon::Thread::Sleep(1000);

        // Try to connect again
        if (!_stop)
            ConnectAsync();
    }

    void onReceived(const asio::ip::udp::endpoint& endpoint, const void* buffer, size_t size) override
    {
        std::cout << "Incoming: " << std::string((const char*)buffer, size) << std::endl;

        // Continue receive datagrams
        ReceiveAsync();
    }

    void onError(int error, const std::string& category, const std::string& message) override
    {
        std::cout << "Multicast UDP client caught an error with code " << error << " and category '" << category << "': " << message << std::endl;
    }

private:
    std::atomic<bool> _stop;
    std::string _multicast;
};

int main(int argc, char** argv)
{
    // UDP listen address
    std::string listen_address = "0.0.0.0";
    if (argc > 1)
        listen_address = argv[1];

    // UDP multicast address
    std::string multicast_address = "239.255.0.1";
    if (argc > 2)
        multicast_address = argv[2];

    // UDP multicast port
    int multicast_port = 3334;
    if (argc > 3)
        multicast_port = std::atoi(argv[3]);

    std::cout << "UDP listen address: " << listen_address << std::endl;
    std::cout << "UDP multicast address: " << multicast_address << std::endl;
    std::cout << "UDP multicast port: " << multicast_port << std::endl;

    // Create a new Asio service
    auto service = std::make_shared<CppServer::Asio::Service>();

    // Start the Asio service
    std::cout << "Asio service starting...";
    service->Start();
    std::cout << "Done!" << std::endl;

    // Create a new UDP multicast client
    auto client = std::make_shared<MulticastClient>(service, listen_address, multicast_address, multicast_port);
    client->SetupMulticast(true);

    // Connect the client
    std::cout << "Client connecting...";
    client->ConnectAsync();
    std::cout << "Done!" << std::endl;

    std::cout << "Press Enter to stop the client or '!' to reconnect the client..." << std::endl;

    // Perform text input
    std::string line;
    while (getline(std::cin, line))
    {
        if (line.empty())
            break;

        // Disconnect the client
        if (line == "!")
        {
            std::cout << "Client disconnecting...";
            client->DisconnectAsync();
            std::cout << "Done!" << std::endl;
            continue;
        }
    }

    // Disconnect the client
    std::cout << "Client disconnecting...";
    client->DisconnectAndStop();
    std::cout << "Done!" << std::endl;

    // Stop the Asio service
    std::cout << "Asio service stopping...";
    service->Stop();
    std::cout << "Done!" << std::endl;

    return 0;
}

Example: Simple protocol

Simple protocol is defined in simple.fbe file:

/*
   Simple Fast Binary Encoding protocol for CppServer
   https://github.com/chronoxor/FastBinaryEncoding

   Generate protocol command: fbec --cpp --proto --input=simple.fbe --output=.
*/

// Domain declaration
domain com.chronoxor

// Package declaration
package simple

// Protocol version
version 1.0

// Simple request message
[request]
[response(SimpleResponse)]
[reject(SimpleReject)]
message SimpleRequest
{
    // Request Id
    uuid [id] = uuid1;
    // Request message
    string Message;
}

// Simple response
message SimpleResponse
{
    // Response Id
    uuid [id] = uuid1;
    // Calculated message hash
    uint32 Hash;
}

// Simple reject
message SimpleReject
{
    // Reject Id
    uuid [id] = uuid1;
    // Error message
    string Error;
}

// Simple notification
message SimpleNotify
{
    // Server notification
    string Notification;
}

// Disconnect request message
[request]
message DisconnectRequest
{
    // Request Id
    uuid [id] = uuid1;
}

Example: Simple protocol server

Here comes the example of the simple protocol server. It process client requests, answer with corresponding responses and send server notifications back to clients.

#include "asio_service.h"

#include "server/asio/tcp_server.h"

#include "../proto/simple_protocol.h"

#include <iostream>

class SimpleProtoSession : public CppServer::Asio::TCPSession, public FBE::simple::Sender, public FBE::simple::Receiver
{
public:
    using CppServer::Asio::TCPSession::TCPSession;

protected:
    void onConnected() override
    {
        std::cout << "Simple protocol session with Id " << id() << " connected!" << std::endl;

        // Send invite notification
        simple::SimpleNotify notify;
        notify.Notification = "Hello from Simple protocol server! Please send a message or '!' to disconnect the client!";
        send(notify);
    }

    void onDisconnected() override
    {
        std::cout << "Simple protocol session with Id " << id() << " disconnected!" << std::endl;
    }

    void onError(int error, const std::string& category, const std::string& message) override
    {
        std::cout << "Simple protocol session caught an error with code " << error << " and category '" << category << "': " << message << std::endl;
    }

    // Protocol handlers
    void onReceive(const ::simple::DisconnectRequest& request) override { Disconnect(); }
    void onReceive(const ::simple::SimpleRequest& request) override
    {
        std::cout << "Received: " << request << std::endl;

        // Validate request
        if (request.Message.empty())
        {
            // Send reject
            simple::SimpleReject reject;
            reject.id = request.id;
            reject.Error = "Request message is empty!";
            send(reject);
            return;
        }

        static std::hash<std::string> hasher;

        // Send response
        simple::SimpleResponse response;
        response.id = request.id;
        response.Hash = (uint32_t)hasher(request.Message);
        send(response);
    }

    // Protocol implementation
    void onReceived(const void* buffer, size_t size) override { receive(buffer, size); }
    size_t onSend(const void* data, size_t size) override { return SendAsync(data, size) ? size : 0; }
};

class SimpleProtoServer : public CppServer::Asio::TCPServer, public FBE::simple::Sender
{
public:
    using CppServer::Asio::TCPServer::TCPServer;

protected:
    std::shared_ptr<CppServer::Asio::TCPSession> CreateSession(const std::shared_ptr<CppServer::Asio::TCPServer>& server) override
    {
        return std::make_shared<SimpleProtoSession>(server);
    }

protected:
    void onError(int error, const std::string& category, const std::string& message) override
    {
        std::cout << "Simple protocol server caught an error with code " << error << " and category '" << category << "': " << message << std::endl;
    }

    // Protocol implementation
    size_t onSend(const void* data, size_t size) override { Multicast(data, size); return size; }
};

int main(int argc, char** argv)
{
    // Simple protocol server port
    int port = 4444;
    if (argc > 1)
        port = std::atoi(argv[1]);

    std::cout << "Simple protocol server port: " << port << std::endl;

    std::cout << std::endl;

    // Create a new Asio service
    auto service = std::make_shared<AsioService>();

    // Start the Asio service
    std::cout << "Asio service starting...";
    service->Start();
    std::cout << "Done!" << std::endl;

    // Create a new simple protocol server
    auto server = std::make_shared<SimpleProtoServer>(service, port);

    // Start the server
    std::cout << "Server starting...";
    server->Start();
    std::cout << "Done!" << std::endl;

    std::cout << "Press Enter to stop the server or '!' to restart the server..." << std::endl;

    // Perform text input
    std::string line;
    while (getline(std::cin, line))
    {
        if (line.empty())
            break;

        // Restart the server
        if (line == "!")
        {
            std::cout << "Server restarting...";
            server->Restart();
            std::cout << "Done!" << std::endl;
            continue;
        }

        // Multicast admin notification to all sessions
        simple::SimpleNotify notify;
        notify.Notification = "(admin) " + line;
        server->send(notify);
    }

    // Stop the server
    std::cout << "Server stopping...";
    server->Stop();
    std::cout << "Done!" << std::endl;

    // Stop the Asio service
    std::cout << "Asio service stopping...";
    service->Stop();
    std::cout << "Done!" << std::endl;

    return 0;
}

Example: Simple protocol client

Here comes the example of the simple protocol client. It connects to the simple protocol server and allows to send requests to it and receive corresponding responses.

#include "asio_service.h"

#include "server/asio/tcp_client.h"
#include "threads/thread.h"

#include "../proto/simple_protocol.h"

#include <atomic>
#include <iostream>

class SimpleProtoClient : public CppServer::Asio::TCPClient, public FBE::simple::Client
{
public:
    using CppServer::Asio::TCPClient::TCPClient;

    void DisconnectAndStop()
    {
        _stop = true;
        DisconnectAsync();
        while (IsConnected())
            CppCommon::Thread::Yield();
    }

protected:
    void onConnected() override
    {
        std::cout << "Simple protocol client connected a new session with Id " << id() << std::endl;

        // Reset FBE protocol buffers
        reset();
    }

    void onDisconnected() override
    {
        std::cout << "Simple protocol client disconnected a session with Id " << id() << std::endl;

        // Wait for a while...
        CppCommon::Thread::Sleep(1000);

        // Try to connect again
        if (!_stop)
            ConnectAsync();
    }

    void onError(int error, const std::string& category, const std::string& message) override
    {
        std::cout << "Simple protocol client caught an error with code " << error << " and category '" << category << "': " << message << std::endl;
    }

    // Protocol handlers
    void onReceive(const ::simple::DisconnectRequest& request) override { Client::onReceive(request); std::cout << "Received: " << request << std::endl; DisconnectAsync(); }
    void onReceive(const ::simple::SimpleResponse& response) override { Client::onReceive(response); std::cout << "Received: " << response << std::endl; }
    void onReceive(const ::simple::SimpleReject& reject) override { Client::onReceive(reject); std::cout << "Received: " << reject << std::endl; }
    void onReceive(const ::simple::SimpleNotify& notify) override { Client::onReceive(notify); std::cout << "Received: " << notify << std::endl; }

    // Protocol implementation
    void onReceived(const void* buffer, size_t size) override { receive(buffer, size); }
    size_t onSend(const void* data, size_t size) override { return SendAsync(data, size) ? size : 0; }

private:
    std::atomic<bool> _stop{false};
};

int main(int argc, char** argv)
{
    // TCP server address
    std::string address = "127.0.0.1";
    if (argc > 1)
        address = argv[1];

    // Simple protocol server port
    int port = 4444;
    if (argc > 2)
        port = std::atoi(argv[2]);

    std::cout << "Simple protocol server address: " << address << std::endl;
    std::cout << "Simple protocol server port: " << port << std::endl;

    std::cout << std::endl;

    // Create a new Asio service
    auto service = std::make_shared<AsioService>();

    // Start the Asio service
    std::cout << "Asio service starting...";
    service->Start();
    std::cout << "Done!" << std::endl;

    // Create a new simple protocol client
    auto client = std::make_shared<SimpleProtoClient>(service, address, port);

    // Connect the client
    std::cout << "Client connecting...";
    client->ConnectAsync();
    std::cout << "Done!" << std::endl;

    std::cout << "Press Enter to stop the client or '!' to reconnect the client..." << std::endl;

    // Perform text input
    std::string line;
    while (getline(std::cin, line))
    {
        if (line.empty())
            break;

        // Reconnect the client
        if (line == "!")
        {
            std::cout << "Client reconnecting...";
            client->IsConnected() ? client->ReconnectAsync() : client->ConnectAsync();
            std::cout << "Done!" << std::endl;
            continue;
        }

        // Send request to the simple protocol server
        simple::SimpleRequest request;
        request.Message = line;
        auto response = client->request(request).get();

        // Show string hash calculation result
        std::cout << "Hash of '" << line << "' = " << std::format("0x{:8X}", response.Hash) << std::endl;
    }

    // Disconnect the client
    std::cout << "Client disconnecting...";
    client->DisconnectAndStop();
    std::cout << "Done!" << std::endl;

    // Stop the Asio service
    std::cout << "Asio service stopping...";
    service->Stop();
    std::cout << "Done!" << std::endl;

    return 0;
}

Example: HTTP server

Here comes the example of the HTTP cache server. It allows to manipulate cache data with HTTP methods (GET, POST, PUT and DELETE).

Use the following link to open Swagger OpenAPI iterative documentation: http://localhost:8080/api/index.html

OpenAPI-HTTP

#include "server/http/http_server.h"
#include "string/string_utils.h"
#include "utility/singleton.h"

#include <iostream>
#include <map>
#include <mutex>

class Cache : public CppCommon::Singleton<Cache>
{
   friend CppCommon::Singleton<Cache>;

public:
    std::string GetAllCache()
    {
        std::scoped_lock locker(_cache_lock);
        std::string result;
        result += "[\n";
        for (const auto& item : _cache)
        {
            result += "  {\n";
            result += "    \"key\": \"" + item.first + "\",\n";
            result += "    \"value\": \"" + item.second + "\",\n";
            result += "  },\n";
        }
        result += "]\n";
        return result;
    }

    bool GetCacheValue(std::string_view key, std::string& value)
    {
        std::scoped_lock locker(_cache_lock);
        auto it = _cache.find(key);
        if (it != _cache.end())
        {
            value = it->second;
            return true;
        }
        else
            return false;
    }

    void PutCacheValue(std::string_view key, std::string_view value)
    {
        std::scoped_lock locker(_cache_lock);
        auto it = _cache.emplace(key, value);
        if (!it.second)
            it.first->second = value;
    }

    bool DeleteCacheValue(std::string_view key, std::string& value)
    {
        std::scoped_lock locker(_cache_lock);
        auto it = _cache.find(key);
        if (it != _cache.end())
        {
            value = it->second;
            _cache.erase(it);
            return true;
        }
        else
            return false;
    }

private:
    std::mutex _cache_lock;
    std::map<std::string, std::string, std::less<>> _cache;
};

class HTTPCacheSession : public CppServer::HTTP::HTTPSession
{
public:
    using CppServer::HTTP::HTTPSession::HTTPSession;

protected:
    void onReceivedRequest(const CppServer::HTTP::HTTPRequest& request) override
    {
        // Show HTTP request content
        std::cout << std::endl << request;

        // Process HTTP request methods
        if (request.method() == "HEAD")
            SendResponseAsync(response().MakeHeadResponse());
        else if (request.method() == "GET")
        {
            std::string key(request.url());
            std::string value;

            // Decode the key value
            key = CppCommon::Encoding::URLDecode(key);
            CppCommon::StringUtils::ReplaceFirst(key, "/api/cache", "");
            CppCommon::StringUtils::ReplaceFirst(key, "?key=", "");

            if (key.empty())
            {
                // Response with all cache values
                SendResponseAsync(response().MakeGetResponse(Cache::GetInstance().GetAllCache(), "application/json; charset=UTF-8"));
            }
            // Get the cache value by the given key
            else if (Cache::GetInstance().GetCacheValue(key, value))
            {
                // Response with the cache value
                SendResponseAsync(response().MakeGetResponse(value));
            }
            else
                SendResponseAsync(response().MakeErrorResponse(404, "Required cache value was not found for the key: " + key));
        }
        else if ((request.method() == "POST") || (request.method() == "PUT"))
        {
            std::string key(request.url());
            std::string value(request.body());

            // Decode the key value
            key = CppCommon::Encoding::URLDecode(key);
            CppCommon::StringUtils::ReplaceFirst(key, "/api/cache", "");
            CppCommon::StringUtils::ReplaceFirst(key, "?key=", "");

            // Put the cache value
            Cache::GetInstance().PutCacheValue(key, value);

            // Response with the cache value
            SendResponseAsync(response().MakeOKResponse());
        }
        else if (request.method() == "DELETE")
        {
            std::string key(request.url());
            std::string value;

            // Decode the key value
            key = CppCommon::Encoding::URLDecode(key);
            CppCommon::StringUtils::ReplaceFirst(key, "/api/cache", "");
            CppCommon::StringUtils::ReplaceFirst(key, "?key=", "");

            // Delete the cache value
            if (Cache::GetInstance().DeleteCacheValue(key, value))
            {
                // Response with the cache value
                SendResponseAsync(response().MakeGetResponse(value));
            }
            else
                SendResponseAsync(response().MakeErrorResponse(404, "Deleted cache value was not found for the key: " + key));
        }
        else if (request.method() == "OPTIONS")
            SendResponseAsync(response().MakeOptionsResponse());
        else if (request.method() == "TRACE")
            SendResponseAsync(response().MakeTraceResponse(request.cache()));
        else
            SendResponseAsync(response().MakeErrorResponse("Unsupported HTTP method: " + std::string(request.method())));
    }

    void onReceivedRequestError(const CppServer::HTTP::HTTPRequest& request, const std::string& error) override
    {
        std::cout << "Request error: " << error << std::endl;
    }

    void onError(int error, const std::string& category, const std::string& message) override
    {
        std::cout << "HTTP session caught an error with code " << error << " and category '" << category << "': " << message << std::endl;
    }
};

class HTTPCacheServer : public CppServer::HTTP::HTTPServer
{
public:
    using CppServer::HTTP::HTTPServer::HTTPServer;

protected:
    std::shared_ptr<CppServer::Asio::TCPSession> CreateSession(const std::shared_ptr<CppServer::Asio::TCPServer>& server) override
    {
        return std::make_shared<HTTPCacheSession>(std::dynamic_pointer_cast<CppServer::HTTP::HTTPServer>(server));
    }

protected:
    void onError(int error, const std::string& category, const std::string& message) override
    {
        std::cout << "HTTP server caught an error with code " << error << " and category '" << category << "': " << message << std::endl;
    }
};

int main(int argc, char** argv)
{
    // HTTP server port
    int port = 8080;
    if (argc > 1)
        port = std::atoi(argv[1]);
    // HTTP server content path
    std::string www = "../www/api";
    if (argc > 2)
        www = argv[2];

    std::cout << "HTTP server port: " << port << std::endl;
    std::cout << "HTTP server static content path: " << www << std::endl;
    std::cout << "HTTP server website: " << "http://localhost:" << port << "/api/index.html" << std::endl;

    std::cout << std::endl;

    // Create a new Asio service
    auto service = std::make_shared<CppServer::Asio::Service>();

    // Start the Asio service
    std::cout << "Asio service starting...";
    service->Start();
    std::cout << "Done!" << std::endl;

    // Create a new HTTP server
    auto server = std::make_shared<HTTPCacheServer>(service, port);
    server->AddStaticContent(www, "/api");

    // Start the server
    std::cout << "Server starting...";
    server->Start();
    std::cout << "Done!" << std::endl;

    std::cout << "Press Enter to stop the server or '!' to restart the server..." << std::endl;

    // Perform text input
    std::string line;
    while (getline(std::cin, line))
    {
        if (line.empty())
            break;

        // Restart the server
        if (line == "!")
        {
            std::cout << "Server restarting...";
            server->Restart();
            std::cout << "Done!" << std::endl;
            continue;
        }
    }

    // Stop the server
    std::cout << "Server stopping...";
    server->Stop();
    std::cout << "Done!" << std::endl;

    // Stop the Asio service
    std::cout << "Asio service stopping...";
    service->Stop();
    std::cout << "Done!" << std::endl;

    return 0;
}

Example: HTTP client

Here comes the example of the HTTP client. It allows to send HTTP requests (GET, POST, PUT and DELETE) and receive HTTP responses.

#include "server/http/http_client.h"
#include "string/string_utils.h"

#include <iostream>

int main(int argc, char** argv)
{
    // HTTP server address
    std::string address = "127.0.0.1";
    if (argc > 1)
        address = argv[1];

    std::cout << "HTTP server address: " << address << std::endl;

    std::cout << std::endl;

    // Create a new Asio service
    auto service = std::make_shared<CppServer::Asio::Service>();

    // Start the Asio service
    std::cout << "Asio service starting...";
    service->Start();
    std::cout << "Done!" << std::endl;

    // Create a new HTTP client
    auto client = std::make_shared<CppServer::HTTP::HTTPClientEx>(service, address, "http");

    std::cout << "Press Enter to stop the client or '!' to reconnect the client..." << std::endl;

    try
    {
        // Perform text input
        std::string line;
        while (getline(std::cin, line))
        {
            if (line.empty())
                break;

            // Reconnect the client
            if (line == "!")
            {
                std::cout << "Client reconnecting...";
                client->ReconnectAsync();
                std::cout << "Done!" << std::endl;
                continue;
            }

            auto commands = CppCommon::StringUtils::Split(line, ' ', true);
            if (commands.size() < 2)
            {
                std::cout << "HTTP method and URL must be entered!" << std::endl;
                continue;
            }

            if (CppCommon::StringUtils::ToUpper(commands[0]) == "HEAD")
            {
                auto response = client->SendHeadRequest(commands[1]).get();
                std::cout << response << std::endl;
            }
            else if (CppCommon::StringUtils::ToUpper(commands[0]) == "GET")
            {
                auto response = client->SendGetRequest(commands[1]).get();
                std::cout << response << std::endl;
            }
            else if (CppCommon::StringUtils::ToUpper(commands[0]) == "POST")
            {
                if (commands.size() < 3)
                {
                    std::cout << "HTTP method, URL and body must be entered!" << std::endl;
                    continue;
                }
                auto response = client->SendPostRequest(commands[1], commands[2]).get();
                std::cout << response << std::endl;
            }
            else if (CppCommon::StringUtils::ToUpper(commands[0]) == "PUT")
            {
                if (commands.size() < 3)
                {
                    std::cout << "HTTP method, URL and body must be entered!" << std::endl;
                    continue;
                }
                auto response = client->SendPutRequest(commands[1], commands[2]).get();
                std::cout << response << std::endl;
            }
            else if (CppCommon::StringUtils::ToUpper(commands[0]) == "DELETE")
            {
                auto response = client->SendDeleteRequest(commands[1]).get();
                std::cout << response << std::endl;
            }
            else if (CppCommon::StringUtils::ToUpper(commands[0]) == "OPTIONS")
            {
                auto response = client->SendOptionsRequest(commands[1]).get();
                std::cout << response << std::endl;
            }
            else if (CppCommon::StringUtils::ToUpper(commands[0]) == "TRACE")
            {
                auto response = client->SendTraceRequest(commands[1]).get();
                std::cout << response << std::endl;
            }
            else
                std::cout << "Unknown HTTP method: " << commands[0] << std::endl;
        }
    }
    catch (const std::exception& ex)
    {
        std::cerr << ex.what() << std::endl;
    }

    // Stop the Asio service
    std::cout << "Asio service stopping...";
    service->Stop();
    std::cout << "Done!" << std::endl;

    return 0;
}

Example: HTTPS server

Here comes the example of the HTTPS cache server. It allows to manipulate cache data with HTTP methods (GET, POST, PUT and DELETE) with secured transport protocol.

Use the following link to open Swagger OpenAPI iterative documentation: https://localhost:8443/api/index.html

OpenAPI-HTTPS

#include "server/http/https_server.h"
#include "string/string_utils.h"
#include "utility/singleton.h"

#include <iostream>
#include <map>
#include <mutex>

class Cache : public CppCommon::Singleton<Cache>
{
   friend CppCommon::Singleton<Cache>;

public:
    std::string GetAllCache()
    {
        std::scoped_lock locker(_cache_lock);
        std::string result;
        result += "[\n";
        for (const auto& item : _cache)
        {
            result += "  {\n";
            result += "    \"key\": \"" + item.first + "\",\n";
            result += "    \"value\": \"" + item.second + "\",\n";
            result += "  },\n";
        }
        result += "]\n";
        return result;
    }

    bool GetCacheValue(std::string_view key, std::string& value)
    {
        std::scoped_lock locker(_cache_lock);
        auto it = _cache.find(key);
        if (it != _cache.end())
        {
            value = it->second;
            return true;
        }
        else
            return false;
    }

    void PutCacheValue(std::string_view key, std::string_view value)
    {
        std::scoped_lock locker(_cache_lock);
        auto it = _cache.emplace(key, value);
        if (!it.second)
            it.first->second = value;
    }

    bool DeleteCacheValue(std::string_view key, std::string& value)
    {
        std::scoped_lock locker(_cache_lock);
        auto it = _cache.find(key);
        if (it != _cache.end())
        {
            value = it->second;
            _cache.erase(it);
            return true;
        }
        else
            return false;
    }

private:
    std::mutex _cache_lock;
    std::map<std::string, std::string, std::less<>> _cache;
};

class HTTPSCacheSession : public CppServer::HTTP::HTTPSSession
{
public:
    using CppServer::HTTP::HTTPSSession::HTTPSSession;

protected:
    void onReceivedRequest(const CppServer::HTTP::HTTPRequest& request) override
    {
        // Show HTTP request content
        std::cout << std::endl << request;

        // Process HTTP request methods
        if (request.method() == "HEAD")
            SendResponseAsync(response().MakeHeadResponse());
        else if (request.method() == "GET")
        {
            std::string key(request.url());
            std::string value;

            // Decode the key value
            key = CppCommon::Encoding::URLDecode(key);
            CppCommon::StringUtils::ReplaceFirst(key, "/api/cache", "");
            CppCommon::StringUtils::ReplaceFirst(key, "?key=", "");

            if (key.empty())
            {
                // Response with all cache values
                SendResponseAsync(response().MakeGetResponse(Cache::GetInstance().GetAllCache(), "application/json; charset=UTF-8"));
            }
            // Get the cache value by the given key
            else if (Cache::GetInstance().GetCacheValue(key, value))
            {
                // Response with the cache value
                SendResponseAsync(response().MakeGetResponse(value));
            }
            else
                SendResponseAsync(response().MakeErrorResponse(404, "Required cache value was not found for the key: " + key));
        }
        else if ((request.method() == "POST") || (request.method() == "PUT"))
        {
            std::string key(request.url());
            std::string value(request.body());

            // Decode the key value
            key = CppCommon::Encoding::URLDecode(key);
            CppCommon::StringUtils::ReplaceFirst(key, "/api/cache", "");
            CppCommon::StringUtils::ReplaceFirst(key, "?key=", "");

            // Put the cache value
            Cache::GetInstance().PutCacheValue(key, value);

            // Response with the cache value
            SendResponseAsync(response().MakeOKResponse());
        }
        else if (request.method() == "DELETE")
        {
            std::string key(request.url());
            std::string value;

            // Decode the key value
            key = CppCommon::Encoding::URLDecode(key);
            CppCommon::StringUtils::ReplaceFirst(key, "/api/cache", "");
            CppCommon::StringUtils::ReplaceFirst(key, "?key=", "");

            // Delete the cache value
            if (Cache::GetInstance().DeleteCacheValue(key, value))
            {
                // Response with the cache value
                SendResponseAsync(response().MakeGetResponse(value));
            }
            else
                SendResponseAsync(response().MakeErrorResponse(404, "Deleted cache value was not found for the key: " + key));
        }
        else if (request.method() == "OPTIONS")
            SendResponseAsync(response().MakeOptionsResponse());
        else if (request.method() == "TRACE")
            SendResponseAsync(response().MakeTraceResponse(request.cache()));
        else
            SendResponseAsync(response().MakeErrorResponse("Unsupported HTTP method: " + std::string(request.method())));
    }

    void onReceivedRequestError(const CppServer::HTTP::HTTPRequest& request, const std::string& error) override
    {
        std::cout << "Request error: " << error << std::endl;
    }

    void onError(int error, const std::string& category, const std::string& message) override
    {
        std::cout << "HTTPS session caught an error with code " << error << " and category '" << category << "': " << message << std::endl;
    }
};

class HTTPSCacheServer : public CppServer::HTTP::HTTPSServer
{
public:
    using CppServer::HTTP::HTTPSServer::HTTPSServer;

protected:
    std::shared_ptr<CppServer::Asio::SSLSession> CreateSession(const std::shared_ptr<CppServer::Asio::SSLServer>& server) override
    {
        return std::make_shared<HTTPSCacheSession>(std::dynamic_pointer_cast<CppServer::HTTP::HTTPSServer>(server));
    }

protected:
    void onError(int error, const std::string& category, const std::string& message) override
    {
        std::cout << "HTTPS server caught an error with code " << error << " and category '" << category << "': " << message << std::endl;
    }
};

int main(int argc, char** argv)
{
    // HTTPS server port
    int port = 8443;
    if (argc > 1)
        port = std::atoi(argv[1]);
    // HTTPS server content path
    std::string www = "../www/api";
    if (argc > 2)
        www = argv[2];

    std::cout << "HTTPS server port: " << port << std::endl;
    std::cout << "HTTPS server static content path: " << www << std::endl;
    std::cout << "HTTPS server website: " << "https://localhost:" << port << "/api/index.html" << std::endl;

    std::cout << std::endl;

    // Create a new Asio service
    auto service = std::make_shared<CppServer::Asio::Service>();

    // Start the Asio service
    std::cout << "Asio service starting...";
    service->Start();
    std::cout << "Done!" << std::endl;

    // Create and prepare a new SSL server context
    auto context = std::make_shared<CppServer::Asio::SSLContext>(asio::ssl::context::tlsv12);
    context->set_password_callback([](size_t max_length, asio::ssl::context::password_purpose purpose) -> std::string { return "qwerty"; });
    context->use_certificate_chain_file("../tools/certificates/server.pem");
    context->use_private_key_file("../tools/certificates/server.pem", asio::ssl::context::pem);
    context->use_tmp_dh_file("../tools/certificates/dh4096.pem");

    // Create a new HTTPS server
    auto server = std::make_shared<HTTPSCacheServer>(service, context, port);
    server->AddStaticContent(www, "/api");

    // Start the server
    std::cout << "Server starting...";
    server->Start();
    std::cout << "Done!" << std::endl;

    std::cout << "Press Enter to stop the server or '!' to restart the server..." << std::endl;

    // Perform text input
    std::string line;
    while (getline(std::cin, line))
    {
        if (line.empty())
            break;

        // Restart the server
        if (line == "!")
        {
            std::cout << "Server restarting...";
            server->Restart();
            std::cout << "Done!" << std::endl;
            continue;
        }
    }

    // Stop the server
    std::cout << "Server stopping...";
    server->Stop();
    std::cout << "Done!" << std::endl;

    // Stop the Asio service
    std::cout << "Asio service stopping...";
    service->Stop();
    std::cout << "Done!" << std::endl;

    return 0;
}

Example: HTTPS client

Here comes the example of the HTTPS client. It allows to send HTTP requests (GET, POST, PUT and DELETE) and receive HTTP responses with secured transport protocol.

#include "server/http/https_client.h"
#include "string/string_utils.h"

#include <iostream>

int main(int argc, char** argv)
{
    // HTTP server address
    std::string address = "127.0.0.1";
    if (argc > 1)
        address = argv[1];

    std::cout << "HTTPS server address: " << address << std::endl;

    std::cout << std::endl;

    // Create a new Asio service
    auto service = std::make_shared<CppServer::Asio::Service>();

    // Start the Asio service
    std::cout << "Asio service starting...";
    service->Start();
    std::cout << "Done!" << std::endl;

    // Create and prepare a new SSL client context
    auto context = std::make_shared<CppServer::Asio::SSLContext>(asio::ssl::context::tlsv12);
    context->set_default_verify_paths();
    context->set_root_certs();
    context->set_verify_mode(asio::ssl::verify_peer | asio::ssl::verify_fail_if_no_peer_cert);
    context->load_verify_file("../tools/certificates/ca.pem");

    // Create a new HTTP client
    auto client = std::make_shared<CppServer::HTTP::HTTPSClientEx>(service, context, address, "https");

    std::cout << "Press Enter to stop the client or '!' to reconnect the client..." << std::endl;

    try
    {
        // Perform text input
        std::string line;
        while (getline(std::cin, line))
        {
            if (line.empty())
                break;

            // Reconnect the client
            if (line == "!")
            {
                std::cout << "Client reconnecting...";
                client->ReconnectAsync();
                std::cout << "Done!" << std::endl;
                continue;
            }

            auto commands = CppCommon::StringUtils::Split(line, ' ', true);
            if (commands.size() < 2)
            {
                std::cout << "HTTP method and URL must be entered!" << std::endl;
                continue;
            }

            if (CppCommon::StringUtils::ToUpper(commands[0]) == "HEAD")
            {
                auto response = client->SendHeadRequest(commands[1]).get();
                std::cout << response << std::endl;
            }
            else if (CppCommon::StringUtils::ToUpper(commands[0]) == "GET")
            {
                auto response = client->SendGetRequest(commands[1]).get();
                std::cout << response << std::endl;
            }
            else if (CppCommon::StringUtils::ToUpper(commands[0]) == "POST")
            {
                if (commands.size() < 3)
                {
                    std::cout << "HTTP method, URL and body must be entered!" << std::endl;
                    continue;
                }
                auto response = client->SendPostRequest(commands[1], commands[2]).get();
                std::cout << response << std::endl;
            }
            else if (CppCommon::StringUtils::ToUpper(commands[0]) == "PUT")
            {
                if (commands.size() < 3)
                {
                    std::cout << "HTTP method, URL and body must be entered!" << std::endl;
                    continue;
                }
                auto response = client->SendPutRequest(commands[1], commands[2]).get();
                std::cout << response << std::endl;
            }
            else if (CppCommon::StringUtils::ToUpper(commands[0]) == "DELETE")
            {
                auto response = client->SendDeleteRequest(commands[1]).get();
                std::cout << response << std::endl;
            }
            else if (CppCommon::StringUtils::ToUpper(commands[0]) == "OPTIONS")
            {
                auto response = client->SendOptionsRequest(commands[1]).get();
                std::cout << response << std::endl;
            }
            else if (CppCommon::StringUtils::ToUpper(commands[0]) == "TRACE")
            {
                auto response = client->SendTraceRequest(commands[1]).get();
                std::cout << response << std::endl;
            }
            else
                std::cout << "Unknown HTTP method: " << commands[0] << std::endl;
        }
    }
    catch (const std::exception& ex)
    {
        std::cerr << ex.what() << std::endl;
    }

    // Stop the Asio service
    std::cout << "Asio service stopping...";
    service->Stop();
    std::cout << "Done!" << std::endl;

    return 0;
}

Example: WebSocket chat server

Here comes the example of the WebSocket chat server. It handles multiple WebSocket client sessions and multicast received message from any session to all ones. Also it is possible to send admin message directly from the server.

Use the following link to open WebSocket chat server example: http://localhost:8080/chat/index.html

ws-chat

#include "server/ws/ws_server.h"

#include <iostream>

class ChatSession : public CppServer::WS::WSSession
{
public:
    using CppServer::WS::WSSession::WSSession;

protected:
    void onWSConnected(const CppServer::HTTP::HTTPRequest& request) override
    {
        std::cout << "Chat WebSocket session with Id " << id() << " connected!" << std::endl;

        // Send invite message
        std::string message("Hello from WebSocket chat! Please send a message or '!' to disconnect the client!");
        SendTextAsync(message);
    }

    void onWSDisconnected() override
    {
        std::cout << "Chat WebSocket session with Id " << id() << " disconnected!" << std::endl;
    }

    void onWSReceived(const void* buffer, size_t size) override
    {
        std::string message((const char*)buffer, size);
        std::cout << "Incoming: " << message << std::endl;

        // Multicast message to all connected sessions
        std::dynamic_pointer_cast<CppServer::WS::WSServer>(server())->MulticastText(message);

        // If the buffer starts with '!' the disconnect the current session
        if (message == "!")
            Close(1000);
    }

    void onWSPing(const void* buffer, size_t size) override
    {
        SendPongAsync(buffer, size);
    }

    void onError(int error, const std::string& category, const std::string& message) override
    {
        std::cout << "Chat WebSocket session caught an error with code " << error << " and category '" << category << "': " << message << std::endl;
    }
};

class ChatServer : public CppServer::WS::WSServer
{
public:
    using CppServer::WS::WSServer::WSServer;

protected:
    std::shared_ptr<CppServer::Asio::TCPSession> CreateSession(std::shared_ptr<CppServer::Asio::TCPServer> server) override
    {
        return std::make_shared<ChatSession>(std::dynamic_pointer_cast<CppServer::WS::WSServer>(server));
    }

protected:
    void onError(int error, const std::string& category, const std::string& message) override
    {
        std::cout << "Chat WebSocket server caught an error with code " << error << " and category '" << category << "': " << message << std::endl;
    }
};

int main(int argc, char** argv)
{
    // WebSocket server port
    int port = 8080;
    if (argc > 1)
        port = std::atoi(argv[1]);
    // WebSocket server content path
    std::string www = "../www/ws";
    if (argc > 2)
        www = argv[2];

    std::cout << "WebSocket server port: " << port << std::endl;
    std::cout << "WebSocket server static content path: " << www << std::endl;
    std::cout << "WebSocket server website: " << "http://localhost:" << port << "/chat/index.html" << std::endl;

    std::cout << std::endl;

    // Create a new Asio service
    auto service = std::make_shared<CppServer::Asio::Service>();

    // Start the Asio service
    std::cout << "Asio service starting...";
    service->Start();
    std::cout << "Done!" << std::endl;

    // Create a new WebSocket chat server
    auto server = std::make_shared<ChatServer>(service, port);
    server->AddStaticContent(www, "/chat");

    // Start the server
    std::cout << "Server starting...";
    server->Start();
    std::cout << "Done!" << std::endl;

    std::cout << "Press Enter to stop the server or '!' to restart the server..." << std::endl;

    // Perform text input
    std::string line;
    while (getline(std::cin, line))
    {
        if (line.empty())
            break;

        // Restart the server
        if (line == "!")
        {
            std::cout << "Server restarting...";
            server->Restart();
            std::cout << "Done!" << std::endl;
            continue;
        }

        // Multicast admin message to all sessions
        line = "(admin) " + line;
        server->MulticastText(line);
    }

    // Stop the server
    std::cout << "Server stopping...";
    server->Stop();
    std::cout << "Done!" << std::endl;

    // Stop the Asio service
    std::cout << "Asio service stopping...";
    service->Stop();
    std::cout << "Done!" << std::endl;

    return 0;
}

Example: WebSocket chat client

Here comes the example of the WebSocket chat client. It connects to the WebSocket chat server and allows to send message to it and receive new messages.

#include "server/ws/ws_client.h"
#include "threads/thread.h"

#include <atomic>
#include <iostream>

class ChatClient : public CppServer::WS::WSClient
{
public:
    using CppServer::WS::WSClient::WSClient;

    void DisconnectAndStop()
    {
        _stop = true;
        CloseAsync(1000);
        while (IsConnected())
            CppCommon::Thread::Yield();
    }

protected:
    void onWSConnecting(CppServer::HTTP::HTTPRequest& request) override
    {
        request.SetBegin("GET", "/");
        request.SetHeader("Host", "localhost");
        request.SetHeader("Origin", "http://localhost");
        request.SetHeader("Upgrade", "websocket");
        request.SetHeader("Connection", "Upgrade");
        request.SetHeader("Sec-WebSocket-Key", CppCommon::Encoding::Base64Encode(ws_nonce()));
        request.SetHeader("Sec-WebSocket-Protocol", "chat, superchat");
        request.SetHeader("Sec-WebSocket-Version", "13");
    }

    void onWSConnected(const CppServer::HTTP::HTTPResponse& response) override
    {
        std::cout << "Chat WebSocket client connected a new session with Id " << id() << std::endl;
    }

    void onWSDisconnected() override
    {
        std::cout << "Chat WebSocket client disconnected a session with Id " << id() << std::endl;
    }

    void onWSReceived(const void* buffer, size_t size) override
    {
        std::cout << "Incoming: " << std::string((const char*)buffer, size) << std::endl;
    }

    void onWSPing(const void* buffer, size_t size) override
    {
        SendPongAsync(buffer, size);
    }

    void onDisconnected() override
    {
        WSClient::onDisconnected();

        // Wait for a while...
        CppCommon::Thread::Sleep(1000);

        // Try to connect again
        if (!_stop)
            ConnectAsync();
    }

    void onError(int error, const std::string& category, const std::string& message) override
    {
        std::cout << "Chat WebSocket client caught an error with code " << error << " and category '" << category << "': " << message << std::endl;
    }

private:
    std::atomic<bool> _stop{false};
};

int main(int argc, char** argv)
{
    // WebSocket server address
    std::string address = "127.0.0.1";
    if (argc > 1)
        address = argv[1];

    // WebSocket server port
    int port = 8080;
    if (argc > 2)
        port = std::atoi(argv[2]);

    std::cout << "WebSocket server address: " << address << std::endl;
    std::cout << "WebSocket server port: " << port << std::endl;

    std::cout << std::endl;

    // Create a new Asio service
    auto service = std::make_shared<CppServer::Asio::Service>();

    // Start the Asio service
    std::cout << "Asio service starting...";
    service->Start();
    std::cout << "Done!" << std::endl;

    // Create a new WebSocket chat client
    auto client = std::make_shared<ChatClient>(service, address, port);

    // Connect the client
    std::cout << "Client connecting...";
    client->ConnectAsync();
    std::cout << "Done!" << std::endl;

    std::cout << "Press Enter to stop the client or '!' to reconnect the client..." << std::endl;

    // Perform text input
    std::string line;
    while (getline(std::cin, line))
    {
        if (line.empty())
            break;

        // Reconnect the client
        if (line == "!")
        {
            std::cout << "Client reconnecting...";
            client->ReconnectAsync();
            std::cout << "Done!" << std::endl;
            continue;
        }

        // Send the entered text to the chat server
        client->SendTextAsync(line);
    }

    // Disconnect the client
    std::cout << "Client disconnecting...";
    client->DisconnectAndStop();
    std::cout << "Done!" << std::endl;

    // Stop the Asio service
    std::cout << "Asio service stopping...";
    service->Stop();
    std::cout << "Done!" << std::endl;

    return 0;
}

Example: WebSocket secure chat server

Here comes the example of the WebSocket secure chat server. It handles multiple WebSocket secure client sessions and multicast received message from any session to all ones. Also it is possible to send admin message directly from the server.

This example is very similar to the WebSocket one except the code that prepares WebSocket secure context and handshake handler.

Use the following link to open WebSocket secure chat server example: https://localhost:8443/chat/index.html

wss-chat

#include "server/ws/wss_server.h"

#include <iostream>

class ChatSession : public CppServer::WS::WSSSession
{
public:
    using CppServer::WS::WSSSession::WSSSession;

protected:
    void onWSConnected(const CppServer::HTTP::HTTPRequest& request) override
    {
        std::cout << "Chat WebSocket secure session with Id " << id() << " connected!" << std::endl;

        // Send invite message
        std::string message("Hello from WebSocket secure chat! Please send a message or '!' to disconnect the client!");
        SendTextAsync(message);
    }

    void onWSDisconnected() override
    {
        std::cout << "Chat WebSocket secure session with Id " << id() << " disconnected!" << std::endl;
    }

    void onWSReceived(const void* buffer, size_t size) override
    {
        std::string message((const char*)buffer, size);
        std::cout << "Incoming: " << message << std::endl;

        // Multicast message to all connected sessions
        std::dynamic_pointer_cast<CppServer::WS::WSSServer>(server())->MulticastText(message);

        // If the buffer starts with '!' the disconnect the current session
        if (message == "!")
            Close(1000);
    }

    void onWSPing(const void* buffer, size_t size) override
    {
        SendPongAsync(buffer, size);
    }

    void onError(int error, const std::string& category, const std::string& message) override
    {
        std::cout << "Chat WebSocket secure session caught an error with code " << error << " and category '" << category << "': " << message << std::endl;
    }
};

class ChatServer : public CppServer::WS::WSSServer
{
public:
    using CppServer::WS::WSSServer::WSSServer;

protected:
    std::shared_ptr<CppServer::Asio::SSLSession> CreateSession(std::shared_ptr<CppServer::Asio::SSLServer> server) override
    {
        return std::make_shared<ChatSession>(std::dynamic_pointer_cast<CppServer::WS::WSSServer>(server));
    }

protected:
    void onError(int error, const std::string& category, const std::string& message) override
    {
        std::cout << "Chat WebSocket secure server caught an error with code " << error << " and category '" << category << "': " << message << std::endl;
    }
};

int main(int argc, char** argv)
{
    // WebSocket secure server port
    int port = 8443;
    if (argc > 1)
        port = std::atoi(argv[1]);
    // WebSocket secure server content path
    std::string www = "../www/wss";
    if (argc > 2)
        www = argv[2];

    std::cout << "WebSocket secure server port: " << port << std::endl;
    std::cout << "WebSocket secure server static content path: " << www << std::endl;
    std::cout << "WebSocket server website: " << "https://localhost:" << port << "/chat/index.html" << std::endl;

    std::cout << std::endl;

    // Create a new Asio service
    auto service = std::make_shared<CppServer::Asio::Service>();

    // Start the Asio service
    std::cout << "Asio service starting...";
    service->Start();
    std::cout << "Done!" << std::endl;

    // Create and prepare a new SSL server context
    auto context = std::make_shared<CppServer::Asio::SSLContext>(asio::ssl::context::tlsv12);
    context->set_password_callback([](size_t max_length, asio::ssl::context::password_purpose purpose) -> std::string { return "qwerty"; });
    context->use_certificate_chain_file("../tools/certificates/server.pem");
    context->use_private_key_file("../tools/certificates/server.pem", asio::ssl::context::pem);
    context->use_tmp_dh_file("../tools/certificates/dh4096.pem");

    // Create a new WebSocket secure chat server
    auto server = std::make_shared<ChatServer>(service, context, port);
    server->AddStaticContent(www, "/chat");

    // Start the server
    std::cout << "Server starting...";
    server->Start();
    std::cout << "Done!" << std::endl;

    std::cout << "Press Enter to stop the server or '!' to restart the server..." << std::endl;

    // Perform text input
    std::string line;
    while (getline(std::cin, line))
    {
        if (line.empty())
            break;

        // Restart the server
        if (line == "!")
        {
            std::cout << "Server restarting...";
            server->Restart();
            std::cout << "Done!" << std::endl;
            continue;
        }

        // Multicast admin message to all sessions
        line = "(admin) " + line;
        server->MulticastText(line);
    }

    // Stop the server
    std::cout << "Server stopping...";
    server->Stop();
    std::cout << "Done!" << std::endl;

    // Stop the Asio service
    std::cout << "Asio service stopping...";
    service->Stop();
    std::cout << "Done!" << std::endl;

    return 0;
}

Example: WebSocket secure chat client

Here comes the example of the WebSocket secure chat client. It connects to the WebSocket secure chat server and allows to send message to it and receive new messages.

This example is very similar to the WebSocket one except the code that prepares WebSocket secure context and handshake handler.

#include "server/ws/wss_client.h"
#include "threads/thread.h"

#include <atomic>
#include <iostream>

class ChatClient : public CppServer::WS::WSSClient
{
public:
    using CppServer::WS::WSSClient::WSSClient;

    void DisconnectAndStop()
    {
        _stop = true;
        CloseAsync(1000);
        while (IsConnected())
            CppCommon::Thread::Yield();
    }

protected:
    void onWSConnecting(CppServer::HTTP::HTTPRequest& request) override
    {
        request.SetBegin("GET", "/");
        request.SetHeader("Host", "localhost");
        request.SetHeader("Origin", "https://localhost");
        request.SetHeader("Upgrade", "websocket");
        request.SetHeader("Connection", "Upgrade");
        request.SetHeader("Sec-WebSocket-Key", CppCommon::Encoding::Base64Encode(ws_nonce()));
        request.SetHeader("Sec-WebSocket-Protocol", "chat, superchat");
        request.SetHeader("Sec-WebSocket-Version", "13");
    }

    void onWSConnected(const CppServer::HTTP::HTTPResponse& response) override
    {
        std::cout << "Chat WebSocket secure client connected a new session with Id " << id() << std::endl;
    }

    void onWSDisconnected() override
    {
        std::cout << "Chat WebSocket secure client disconnected a session with Id " << id() << std::endl;
    }

    void onWSReceived(const void* buffer, size_t size) override
    {
        std::cout << "Incoming: " << std::string((const char*)buffer, size) << std::endl;
    }

    void onWSPing(const void* buffer, size_t size) override
    {
        SendPongAsync(buffer, size);
    }

    void onDisconnected() override
    {
        WSSClient::onDisconnected();

        // Wait for a while...
        CppCommon::Thread::Sleep(1000);

        // Try to connect again
        if (!_stop)
            ConnectAsync();
    }

    void onError(int error, const std::string& category, const std::string& message) override
    {
        std::cout << "Chat WebSocket secure client caught an error with code " << error << " and category '" << category << "': " << message << std::endl;
    }

private:
    std::atomic<bool> _stop{false};
};

int main(int argc, char** argv)
{
    // WebSocket server address
    std::string address = "127.0.0.1";
    if (argc > 1)
        address = argv[1];

    // WebSocket server port
    int port = 8443;
    if (argc > 2)
        port = std::atoi(argv[2]);

    std::cout << "WebSocket secure server address: " << address << std::endl;
    std::cout << "WebSocket secure server port: " << port << std::endl;

    std::cout << std::endl;

    // Create a new Asio service
    auto service = std::make_shared<CppServer::Asio::Service>();

    // Start the Asio service
    std::cout << "Asio service starting...";
    service->Start();
    std::cout << "Done!" << std::endl;

    // Create and prepare a new SSL client context
    auto context = std::make_shared<CppServer::Asio::SSLContext>(asio::ssl::context::tlsv12);
    context->set_default_verify_paths();
    context->set_root_certs();
    context->set_verify_mode(asio::ssl::verify_peer | asio::ssl::verify_fail_if_no_peer_cert);
    context->load_verify_file("../tools/certificates/ca.pem");

    // Create a new WebSocket chat client
    auto client = std::make_shared<ChatClient>(service, context, address, port);

    // Connect the client
    std::cout << "Client connecting...";
    client->ConnectAsync();
    std::cout << "Done!" << std::endl;

    std::cout << "Press Enter to stop the client or '!' to reconnect the client..." << std::endl;

    // Perform text input
    std::string line;
    while (getline(std::cin, line))
    {
        if (line.empty())
            break;

        // Reconnect the client
        if (line == "!")
        {
            std::cout << "Client reconnecting...";
            client->ReconnectAsync();
            std::cout << "Done!" << std::endl;
            continue;
        }

        // Send the entered text to the chat server
        client->SendTextAsync(line);
    }

    // Disconnect the client
    std::cout << "Client disconnecting...";
    client->DisconnectAndStop();
    std::cout << "Done!" << std::endl;

    // Stop the Asio service
    std::cout << "Asio service stopping...";
    service->Stop();
    std::cout << "Done!" << std::endl;

    return 0;
}

Performance

Here comes several communication scenarios with timing measurements.

Benchmark environment is the following:

CPU architecutre: Intel(R) Core(TM) i7-4790K CPU @ 4.00GHz
CPU logical cores: 8
CPU physical cores: 4
CPU clock speed: 3.998 GHz
CPU Hyper-Threading: enabled
RAM total: 31.962 GiB
RAM free: 21.623 GiB

OS version: Microsoft Windows 8 Enterprise Edition (build 9200), 64-bit
OS bits: 64-bit
Process bits: 64-bit
Process configuaraion: release

Benchmark: Round-Trip

Round-trip

This scenario sends lots of messages from several clients to a server. The server responses to each message and resend the similar response to the client. The benchmark measures total round-trip time to send all messages and receive all responses, messages & data throughput, count of errors.

TCP echo server

Server address: 127.0.0.1
Server port: 1111
Working threads: 1
Working clients: 1
Working messages: 1000
Message size: 32
Seconds to benchmarking: 10

Errors: 0

Total time: 10.001 s
Total data: 1.692 GiB
Total messages: 56261685
Data throughput: 171.693 MiB/s
Message latency: 177 ns
Message throughput: 5625528 msg/s
Server address: 127.0.0.1
Server port: 1111
Working threads: 4
Working clients: 100
Working messages: 1000
Message size: 32
Seconds to benchmarking: 10

Errors: 0

Total time: 10.007 s
Total data: 1.151 GiB
Total messages: 38503396
Data throughput: 117.423 MiB/s
Message latency: 259 ns
Message throughput: 3847402 msg/s

SSL echo server

Server address: 127.0.0.1
Server port: 2222
Working threads: 1
Working clients: 1
Working messages: 1000
Message size: 32
Seconds to benchmarking: 10

Errors: 0

Total time: 10.012 s
Total data: 296.350 MiB
Total messages: 9710535
Data throughput: 29.612 MiB/s
Message latency: 1.031 mcs
Message throughput: 969878 msg/s
Server address: 127.0.0.1
Server port: 2222
Working threads: 4
Working clients: 100
Working messages: 1000
Message size: 32
Seconds to benchmarking: 10

Errors: 0

Total time: 10.341 s
Total data: 390.660 MiB
Total messages: 12800660
Data throughput: 37.792 MiB/s
Message latency: 807 ns
Message throughput: 1237782 msg/s

UDP echo server

Server address: 127.0.0.1
Server port: 3333
Working threads: 1
Working clients: 1
Working messages: 1000
Message size: 32
Seconds to benchmarking: 10

Errors: 0

Total time: 10.002 s
Total data: 46.032 MiB
Total messages: 1508355
Data throughput: 4.616 MiB/s
Message latency: 6.631 mcs
Message throughput: 150801 msg/s
Server address: 127.0.0.1
Server port: 3333
Working threads: 4
Working clients: 100
Working messages: 1000
Message size: 32
Seconds to benchmarking: 10

Errors: 0

Total time: 10.152 s
Total data: 32.185 MiB
Total messages: 1054512
Data throughput: 3.173 MiB/s
Message latency: 9.627 mcs
Message throughput: 103867 msg/s

Simple protocol server

Server address: 127.0.0.1
Server port: 4444
Working threads: 1
Working clients: 1
Working messages: 1000
Message size: 32
Seconds to benchmarking: 10

Errors: 0

Total time: 10.002 s
Total data: 497.096 MiB
Total messages: 16288783
Data throughput: 49.715 MiB/s
Message latency: 614 ns
Message throughput: 1628542 msg/s
Server address: 127.0.0.1
Server port: 4444
Working threads: 4
Working clients: 100
Working messages: 1000
Message size: 32
Seconds to benchmarking: 10

Errors: 0

Total time: 10.066 s
Total data: 997.384 MiB
Total messages: 32681995
Data throughput: 99.078 MiB/s
Message latency: 308 ns
Message throughput: 3246558 msg/s

WebSocket echo server

Server address: 127.0.0.1
Server port: 8080
Working threads: 1
Working clients: 1
Working messages: 1000
Message size: 32
Seconds to benchmarking: 10

Errors: 0

Total time: 9.994 s
Total data: 48.958 MiB
Total messages: 1603548
Data throughput: 4.918 MiB/s
Message latency: 6.232 mcs
Message throughput: 160448 msg/s
Server address: 127.0.0.1
Server port: 8080
Working threads: 4
Working clients: 100
Working messages: 1000
Message size: 32
Seconds to benchmarking: 10

Errors: 0

Total time: 11.402 s
Total data: 206.827 MiB
Total messages: 6776702
Data throughput: 18.140 MiB/s
Message latency: 1.682 mcs
Message throughput: 594328 msg/s

WebSocket secure echo server

Server address: 127.0.0.1
Server port: 8443
Working threads: 1
Working clients: 1
Working messages: 1000
Message size: 32
Seconds to benchmarking: 10

Errors: 0

Total time: 10.001 s
Total data: 62.068 MiB
Total messages: 2033811
Data throughput: 6.210 MiB/s
Message latency: 4.917 mcs
Message throughput: 203343 msg/s
Server address: 127.0.0.1
Server port: 8443
Working threads: 4
Working clients: 100
Working messages: 1000
Message size: 32
Seconds to benchmarking: 10

Errors: 0

Total time: 10.011 s
Total data: 249.1023 MiB
Total messages: 8191971
Data throughput: 24.993 MiB/s
Message latency: 1.222 mcs
Message throughput: 818230 msg/s

Benchmark: Multicast

Multicast

In this scenario server multicasts messages to all connected clients. The benchmark counts total messages received by all clients for all the working time and measures messages & data throughput, count of errors.

TCP multicast server

Server address: 127.0.0.1
Server port: 1111
Working threads: 1
Working clients: 1
Message size: 32
Seconds to benchmarking: 10

Errors: 0

Total time: 10.001 s
Total data: 1.907 GiB
Total messages: 63283367
Data throughput: 193.103 MiB/s
Message latency: 158 ns
Message throughput: 6327549 msg/s
Server address: 127.0.0.1
Server port: 1111
Working threads: 4
Working clients: 100
Message size: 32
Seconds to benchmarking: 10

Errors: 0

Total time: 10.006 s
Total data: 1.1006 GiB
Total messages: 66535013
Data throughput: 202.930 MiB/s
Message latency: 150 ns
Message throughput: 6648899 msg/s

SSL multicast server

Server address: 127.0.0.1
Server port: 2222
Working threads: 1
Working clients: 1
Message size: 32
Seconds to benchmarking: 10

Errors: 0

Total time: 10.014 s
Total data: 1.535 GiB
Total messages: 51100073
Data throughput: 155.738 MiB/s
Message latency: 195 ns
Message throughput: 5102683 msg/s
Server address: 127.0.0.1
Server port: 2222
Working threads: 4
Working clients: 100
Message size: 32
Seconds to benchmarking: 10

Errors: 0

Total time: 10.691 s
Total data: 1.878 GiB
Total messages: 62334478
Data throughput: 177.954 MiB/s
Message latency: 171 ns
Message throughput: 5830473 msg/s

UDP multicast server

Server address: 239.255.0.1
Server port: 3333
Working threads: 1
Working clients: 1
Message size: 32
Seconds to benchmarking: 10

Errors: 0

Total time: 10.002 s
Total data: 23.777 MiB
Total messages: 778555
Data throughput: 2.384 MiB/s
Message latency: 12.847 mcs
Message throughput: 77833 msg/s
Server address: 239.255.0.1
Server port: 3333
Working threads: 4
Working clients: 100
Message size: 32
Seconds to benchmarking: 10

Errors: 0

Total time: 10.004 s
Total data: 52.457 MiB
Total messages: 1718575
Data throughput: 5.248 MiB/s
Message latency: 5.821 mcs
Message throughput: 171784 msg/s

WebSocket multicast server

Server address: 127.0.0.1
Server port: 8080
Working threads: 1
Working clients: 1
Message size: 32
Seconds to benchmarking: 10

Errors: 0

Total time: 10.001 s
Total data: 960.902 MiB
Total messages: 31486166
Data throughput: 96.075 MiB/s
Message latency: 317 ns
Message throughput: 3148135 msg/s
Server address: 127.0.0.1
Server port: 8080
Working threads: 4
Working clients: 100
Message size: 32
Seconds to benchmarking: 10

Errors: 0

Total time: 10.020 s
Total data: 986.489 MiB
Total messages: 32324898
Data throughput: 98.459 MiB/s
Message latency: 309 ns
Message throughput: 3225965 msg/s

WebSocket secure multicast server

Server address: 127.0.0.1
Server port: 8443
Working threads: 1
Working clients: 1
Message size: 32
Seconds to benchmarking: 10

Errors: 0

Total time: 10.002 s
Total data: 1.041 GiB
Total messages: 34903186
Data throughput: 106.505 MiB/s
Message latency: 286 ns
Message throughput: 3489578 msg/s
Server address: 127.0.0.1
Server port: 8443
Working threads: 4
Working clients: 100
Message size: 32
Seconds to benchmarking: 10

Errors: 0

Total time: 10.013 s
Total data: 1.569 GiB
Total messages: 52225588
Data throughput: 159.172 MiB/s
Message latency: 191 ns
Message throughput: 5215639 msg/s

Benchmark: Web Server

HTTP Trace server

Server address: 127.0.0.1
Server port: 80
Working threads: 1
Working clients: 1
Working messages: 1
Seconds to benchmarking: 10

Errors: 0

Total time: 10.001 s
Total data: 58.476 MiB
Total messages: 578353
Data throughput: 5.865 MiB/s
Message latency: 17.293 mcs
Message throughput: 57825 msg/s
Server address: 127.0.0.1
Server port: 80
Working threads: 4
Working clients: 100
Working messages: 1
Seconds to benchmarking: 10

Errors: 0

Total time: 10.006 s
Total data: 310.730 MiB
Total messages: 3073650
Data throughput: 31.051 MiB/s
Message latency: 3.255 mcs
Message throughput: 307154 msg/s

HTTPS Trace server

Server address: 127.0.0.1
Server port: 443
Working threads: 1
Working clients: 1
Working messages: 1
Seconds to benchmarking: 10

Errors: 0

Total time: 10.003 s
Total data: 37.475 MiB
Total messages: 370602
Data throughput: 3.763 MiB/s
Message latency: 26.992 mcs
Message throughput: 37047 msg/s
Server address: 127.0.0.1
Server port: 443
Working threads: 4
Working clients: 100
Working messages: 1
Seconds to benchmarking: 10

Errors: 0

Total time: 10.035 s
Total data: 204.531 MiB
Total messages: 2023152
Data throughput: 20.389 MiB/s
Message latency: 4.960 mcs
Message throughput: 201602 msg/s

OpenSSL certificates

In order to create OpenSSL based server and client you should prepare a set of SSL certificates.

Production

Depending on your project, you may need to purchase a traditional SSL certificate signed by a Certificate Authority. If you, for instance, want some else's web browser to talk to your WebSocket project, you'll need a traditional SSL certificate.

Development

The commands below entered in the order they are listed will generate a self-signed certificate for development or testing purposes.

Certificate Authority

  • Create CA private key
openssl genrsa -passout pass:qwerty -out ca-secret.key 4096
  • Remove passphrase
openssl rsa -passin pass:qwerty -in ca-secret.key -out ca.key
  • Create CA self-signed certificate
openssl req -new -x509 -days 3650 -subj '/C=BY/ST=Belarus/L=Minsk/O=Example root CA/OU=Example CA unit/CN=example.com' -key ca.key -out ca.crt
  • Convert CA self-signed certificate to PFX
openssl pkcs12 -export -passout pass:qwerty -inkey ca.key -in ca.crt -out ca.pfx
  • Convert CA self-signed certificate to PEM
openssl pkcs12 -passin pass:qwerty -passout pass:qwerty -in ca.pfx -out ca.pem

SSL Server certificate

  • Create private key for the server
openssl genrsa -passout pass:qwerty -out server-secret.key 4096
  • Remove passphrase
openssl rsa -passin pass:qwerty -in server-secret.key -out server.key
  • Create CSR for the server
openssl req -new -subj '/C=BY/ST=Belarus/L=Minsk/O=Example server/OU=Example server unit/CN=server.example.com' -key server.key -out server.csr
  • Create certificate for the server
openssl x509 -req -days 3650 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt
  • Convert the server certificate to PFX
openssl pkcs12 -export -passout pass:qwerty -inkey server.key -in server.crt -out server.pfx
  • Convert the server certificate to PEM
openssl pkcs12 -passin pass:qwerty -passout pass:qwerty -in server.pfx -out server.pem

SSL Client certificate

  • Create private key for the client
openssl genrsa -passout pass:qwerty -out client-secret.key 4096
  • Remove passphrase
openssl rsa -passin pass:qwerty -in client-secret.key -out client.key
  • Create CSR for the client
openssl req -new -subj '/C=BY/ST=Belarus/L=Minsk/O=Example client/OU=Example client unit/CN=client.example.com' -key client.key -out client.csr
  • Create the client certificate
openssl x509 -req -days 3650 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt
  • Convert the client certificate to PFX
openssl pkcs12 -export -passout pass:qwerty -inkey client.key -in client.crt -out client.pfx
  • Convert the client certificate to PEM
openssl pkcs12 -passin pass:qwerty -passout pass:qwerty -in client.pfx -out client.pem

Diffie-Hellman key exchange

  • Create DH parameters
openssl dhparam -out dh4096.pem 4096

Author: chronoxor
Source Code: https://github.com/chronoxor/CppServer
License: MIT License

#cpluplus #c 

Eric  Bukenya

Eric Bukenya

1649494800

Activity indicators for Modern C++

Indicators: Activity indicators for Modern C++
 

Highlights

  • Thread-safe progress bars and spinners
  • Header-only library. Grab a copy of include/indicators.
  • Single-header version in single_include/indicators.
  • Source for the above GIF can be found here
  • MIT License

Basic Progress bar

To introduce a progress bar in your application, include indicators/progress_bar.hpp and create a ProgressBar object. Here's the general structure of a progress bar:

{prefix} {start} {fill} {lead} {remaining} {end} {percentage} [{elapsed}<{remaining}] {postfix}
         ^^^^^^^^^^^^^ Bar Width ^^^^^^^^^^^^^^^   

The amount of progress in ProgressBar is maintained as a size_t in range [0, 100]. When progress reaches 100, the progression is complete.

From application-level code, there are two ways in which you can update this progress:

Update progress using bar.tick()

You can update the progress bar using bar.tick() which increments progress by exactly 1%.

#include <indicators/progress_bar.hpp>
#include <thread>
#include <chrono>

int main() {
  using namespace indicators;
  ProgressBar bar{
    option::BarWidth{50},
    option::Start{"["},
    option::Fill{"="},
    option::Lead{">"},
    option::Remainder{" "},
    option::End{"]"},
    option::PostfixText{"Extracting Archive"},
    option::ForegroundColor{Color::green},
    option::FontStyles{std::vector<FontStyle>{FontStyle::bold}}
  };
  
  // Update bar state
  while (true) {
    bar.tick();
    if (bar.is_completed())
      break;
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
  }

  return 0;
}

The above code will print a progress bar that goes from 0 to 100% at the rate of 1% every 100 ms.

Updating progress using bar.set_progress(value)

If you'd rather control progress of the bar in discrete steps, consider using bar.set_progress(value). Example:

#include <chrono>
#include <indicators/cursor_control.hpp>
#include <indicators/progress_bar.hpp>
#include <thread>

int main() {
  using namespace indicators;

  // Hide cursor
  show_console_cursor(false);

  ProgressBar bar{
    option::BarWidth{50},
    option::Start{"["},
    option::Fill{"■"},
    option::Lead{"■"},
    option::Remainder{"-"},
    option::End{" ]"},
    option::PostfixText{"Loading dependency 1/4"},
    option::ForegroundColor{Color::cyan},
    option::FontStyles{std::vector<FontStyle>{FontStyle::bold}}
  };

  // Update bar state
  bar.set_progress(10); // 10% done

  // do some work
  std::this_thread::sleep_for(std::chrono::milliseconds(800));

  bar.set_option(option::PostfixText{"Loading dependency 2/4"});  

  bar.set_progress(30); // 30% done

  // do some more work
  std::this_thread::sleep_for(std::chrono::milliseconds(700));

  bar.set_option(option::PostfixText{"Loading dependency 3/4"});  

  bar.set_progress(65); // 65% done

  // do final bit of work
  std::this_thread::sleep_for(std::chrono::milliseconds(900));

  bar.set_option(option::PostfixText{"Loaded dependencies!"});

  bar.set_progress(100); // all done

  // Show cursor
  show_console_cursor(true);

  return 0;
}

Showing Time Elapsed/Remaining

All progress bars and spinners in indicators support showing time elapsed and time remaining. Inspired by python's tqdm module, the format of this meter is [{elapsed}<{remaining}]:

#include <chrono>
#include <indicators/cursor_control.hpp>
#include <indicators/progress_bar.hpp>
#include <thread>

int main() {
  using namespace indicators;

  // Hide cursor
  show_console_cursor(false);

  indicators::ProgressBar bar{
    option::BarWidth{50},
    option::Start{" ["},
    option::Fill{"█"},
    option::Lead{"█"},
    option::Remainder{"-"},
    option::End{"]"},
    option::PrefixText{"Training Gaze Network 👀"},
    option::ForegroundColor{Color::yellow},
    option::ShowElapsedTime{true},
    option::ShowRemainingTime{true},
    option::FontStyles{std::vector<FontStyle>{FontStyle::bold}}
  };

  // Update bar state
  while (true) {
    bar.tick();
    if (bar.is_completed())
      break;
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
  }

  // Show cursor
  show_console_cursor(true);

  return 0;
}

Indeterminate Progress Bar

You might have a use-case for a progress bar where the maximum amount of progress is unknown, e.g., you're downloading from a remote server that isn't advertising the total bytes.

Use an indicators::IndeterminateProgressBar for such cases. An IndeterminateProgressBar is similar to a regular progress bar except the total amount to progress towards is unknown. Ticking on this progress bar will happily run forever.

When you know progress is complete, simply call bar.mark_as_completed().

#include <chrono>
#include <indicators/indeterminate_progress_bar.hpp>
#include <indicators/cursor_control.hpp>
#include <indicators/termcolor.hpp>
#include <thread>

int main() {
  indicators::IndeterminateProgressBar bar{
      indicators::option::BarWidth{40},
      indicators::option::Start{"["},
      indicators::option::Fill{"·"},
      indicators::option::Lead{"<==>"},
      indicators::option::End{"]"},
      indicators::option::PostfixText{"Checking for Updates"},
      indicators::option::ForegroundColor{indicators::Color::yellow},
      indicators::option::FontStyles{
          std::vector<indicators::FontStyle>{indicators::FontStyle::bold}}
  };

  indicators::show_console_cursor(false);

  auto job = [&bar]() {
    std::this_thread::sleep_for(std::chrono::milliseconds(10000));
    bar.mark_as_completed();
    std::cout << termcolor::bold << termcolor::green 
        << "System is up to date!\n" << termcolor::reset;
  };
  std::thread job_completion_thread(job);

  // Update bar state
  while (!bar.is_completed()) {
    bar.tick();
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
  }

  job_completion_thread.join();
  
  indicators::show_console_cursor(true);  
  return 0;
}

Block Progress Bar

Are you in need of a smooth block progress bar using unicode block elements? Use BlockProgressBar instead of ProgressBar. Thanks to this blog post for making BlockProgressBar an easy addition to the library.

#include <indicators/block_progress_bar.hpp>
#include <thread>
#include <chrono>

int main() {

  using namespace indicators;

  // Hide cursor
  show_console_cursor(false);

  BlockProgressBar bar{
    option::BarWidth{80},
    option::Start{"["},
    option::End{"]"},
    option::ForegroundColor{Color::white}  ,
    option::FontStyles{std::vector<FontStyle>{FontStyle::bold}}
  };
  
  // Update bar state
  auto progress = 0.0f;
  while (true) {
    bar.set_progress(progress);
    progress += 0.25f;
    if (bar.is_completed())
      break;
    std::this_thread::sleep_for(std::chrono::milliseconds(50));
  }

  // Show cursor
  show_console_cursor(true);

  return 0;
}

MultiProgress

indicators supports management of multiple progress bars with the MultiProgress class template.

template <typename Indicator, size_t count> class MultiProgress is a class template that holds references to multiple progress bars and provides a safe interface to update the state of each bar. MultiProgress works with both ProgressBar and BlockProgressBar classes.

Use this class if you know the number of progress bars to manage at compile time.

Below is an example MultiProgress object that manages three ProgressBar objects.

#include <indicators/multi_progress.hpp>
#include <indicators/progress_bar.hpp>

int main() {
  using namespace indicators;
  // Configure first progress bar
  ProgressBar bar1{
    option::BarWidth{50},
    option::Start{"["},
    option::Fill{"■"},
    option::Lead{"■"},
    option::Remainder{" "},
    option::End{" ]"},
    option::ForegroundColor{Color::yellow},
    option::ShowElapsedTime{true},
    option::ShowRemainingTime{true},
    option::PrefixText{"Progress Bar #1 "},
    option::FontStyles{std::vector<FontStyle>{FontStyle::bold}}
  };

  // Configure second progress bar

  ProgressBar bar2{
    option::BarWidth{50},
    option::Start{"["},
    option::Fill{"="},
    option::Lead{">"},
    option::Remainder{" "},
    option::End{" ]"},
    option::ForegroundColor{Color::cyan},
    option::ShowElapsedTime{true},
    option::ShowRemainingTime{true},
    option::PrefixText{"Progress Bar #2 "},
    option::FontStyles{std::vector<FontStyle>{FontStyle::bold}}
  };
  
  // Configure third progress bar
  indicators::ProgressBar bar3{
    option::BarWidth{50},
    option::Start{"["},
    option::Fill{"#"},
    option::Lead{"#"},
    option::Remainder{" "},
    option::End{" ]"},
    option::ForegroundColor{Color::red},
    option::ShowElapsedTime{true},
    option::ShowRemainingTime{true},
    option::PrefixText{"Progress Bar #3 "},
    option::FontStyles{std::vector<FontStyle>{FontStyle::bold}}
  };

  // Construct MultiProgress object
  indicators::MultiProgress<indicators::ProgressBar, 3> bars(bar1, bar2, bar3);

  std::cout << "Multiple Progress Bars:\n";

  auto job1 = [&bars]() {
    while (true) {
      bars.tick<0>();
      if (bars.is_completed<0>())
        break;
      std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
  };

  auto job2 = [&bars]() {
    while (true) {
      bars.tick<1>();
      if (bars.is_completed<1>())
        break;
      std::this_thread::sleep_for(std::chrono::milliseconds(200));
    }
  };

  auto job3 = [&bars]() {
    while (true) {
      bars.tick<2>();
      if (bars.is_completed<2>())
        break;
      std::this_thread::sleep_for(std::chrono::milliseconds(60));
    }
  };

  std::thread first_job(job1);
  std::thread second_job(job2);
  std::thread third_job(job3);

  first_job.join();
  second_job.join();
  third_job.join();

  return 0;
}

DynamicProgress

DynamicProgress is a container class, similar to MultiProgress, for managing multiple progress bars. As the name suggests, with DynamicProgress, you can dynamically add new progress bars.

To add new progress bars, call bars.push_back(new_bar). This call will return the index of the appended bar. You can then refer to this bar with the indexing operator, e.g., bars[4].set_progress(55).

Use this class if you don't know the number of progress bars at compile time.

Below is an example DynamicProgress object that manages six ProgressBar objects. Three of these bars are added dynamically.

#include <indicators/dynamic_progress.hpp>
#include <indicators/progress_bar.hpp>
using namespace indicators;

int main() {

  ProgressBar bar1{option::BarWidth{50}, option::ForegroundColor{Color::red},
                   option::ShowElapsedTime{true}, option::ShowRemainingTime{true},
                   option::PrefixText{"5c90d4a2d1a8: Downloading "}};

  ProgressBar bar2{option::BarWidth{50}, option::ForegroundColor{Color::yellow},
                   option::ShowElapsedTime{true}, option::ShowRemainingTime{true},
                   option::PrefixText{"22337bfd13a9: Downloading "}};

  ProgressBar bar3{option::BarWidth{50}, option::ForegroundColor{Color::green},
                   option::ShowElapsedTime{true}, option::ShowRemainingTime{true},
                   option::PrefixText{"10f26c680a34: Downloading "}};

  ProgressBar bar4{option::BarWidth{50}, option::ForegroundColor{Color::white},
                   option::ShowElapsedTime{true}, option::ShowRemainingTime{true},
                   option::PrefixText{"6364e0d7a283: Downloading "}};

  ProgressBar bar5{option::BarWidth{50}, option::ForegroundColor{Color::blue},
                   option::ShowElapsedTime{true}, option::ShowRemainingTime{true},
                   option::PrefixText{"ff1356ba118b: Downloading "}};

  ProgressBar bar6{option::BarWidth{50}, option::ForegroundColor{Color::cyan},
                   option::ShowElapsedTime{true}, option::ShowRemainingTime{true},
                   option::PrefixText{"5a17453338b4: Downloading "}};

  std::cout << termcolor::bold << termcolor::white << "Pulling image foo:bar/baz\n";

  // Construct with 3 progress bars. We'll add 3 more at a later point
  DynamicProgress<ProgressBar> bars(bar1, bar2, bar3);
  
  // Do not hide bars when completed
  bars.set_option(option::HideBarWhenComplete{false});

  std::thread fourth_job, fifth_job, sixth_job;

  auto job4 = [&bars](size_t i) {
    while (true) {
      bars[i].tick();
      if (bars[i].is_completed()) {
        bars[i].set_option(option::PrefixText{"6364e0d7a283: Pull complete "});
        bars[i].mark_as_completed();
        break;
      }
      std::this_thread::sleep_for(std::chrono::milliseconds(50));
    }
  };

  auto job5 = [&bars](size_t i) {
    while (true) {
      bars[i].tick();
      if (bars[i].is_completed()) {
        bars[i].set_option(option::PrefixText{"ff1356ba118b: Pull complete "});
        bars[i].mark_as_completed();
        break;
      }
      std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
  };

  auto job6 = [&bars](size_t i) {
    while (true) {
      bars[i].tick();
      if (bars[i].is_completed()) {
        bars[i].set_option(option::PrefixText{"5a17453338b4: Pull complete "});
        bars[i].mark_as_completed();
        break;
      }
      std::this_thread::sleep_for(std::chrono::milliseconds(40));
    }
  };

  auto job1 = [&bars, &bar6, &sixth_job, &job6]() {
    while (true) {
      bars[0].tick();
      if (bars[0].is_completed()) {
        bars[0].set_option(option::PrefixText{"5c90d4a2d1a8: Pull complete "});
        // bar1 is completed, adding bar6
        auto i = bars.push_back(bar6);
        sixth_job = std::thread(job6, i);
        sixth_job.join();
        break;
      }
      std::this_thread::sleep_for(std::chrono::milliseconds(140));
    }
  };

  auto job2 = [&bars, &bar5, &fifth_job, &job5]() {
    while (true) {
      bars[1].tick();
      if (bars[1].is_completed()) {
        bars[1].set_option(option::PrefixText{"22337bfd13a9: Pull complete "});
        // bar2 is completed, adding bar5
        auto i = bars.push_back(bar5);
        fifth_job = std::thread(job5, i);
        fifth_job.join();
        break;
      }
      std::this_thread::sleep_for(std::chrono::milliseconds(25));
    }
  };

  auto job3 = [&bars, &bar4, &fourth_job, &job4]() {
    while (true) {
      bars[2].tick();
      if (bars[2].is_completed()) {
        bars[2].set_option(option::PrefixText{"10f26c680a34: Pull complete "});
        // bar3 is completed, adding bar4
        auto i = bars.push_back(bar4);
        fourth_job = std::thread(job4, i);
        fourth_job.join();
        break;
      }
      std::this_thread::sleep_for(std::chrono::milliseconds(50));
    }
  };

  std::thread first_job(job1);
  std::thread second_job(job2);
  std::thread third_job(job3);

  third_job.join();
  second_job.join();
  first_job.join();

  std::cout << termcolor::bold << termcolor::green << "✔ Downloaded image foo/bar:baz" << std::endl;
  std::cout << termcolor::reset;

  return 0;
}

In the above code, notice the option bars.set_option(option::HideBarWhenComplete{true});. Yes, you can hide progress bars as and when they complete by setting this option to true. If you do so, the above example will look like this:

Progress Spinner

To introduce a progress spinner in your application, include indicators/progress_spinner.hpp and create a ProgressSpinner object. Here's the general structure of a progress spinner:

{prefix} {spinner} {percentage} [{elapsed}<{remaining}] {postfix}

ProgressSpinner has a vector of strings: spinner_states. At each update, the spinner will pick the next string from this sequence to print to the console. The spinner state can be updated similarly to ProgressBars: Using either tick() or set_progress(value).

#include <indicators/progress_spinner.hpp>

int main() {
  using namespace indicators;
  indicators::ProgressSpinner spinner{
    option::PostfixText{"Checking credentials"},
    option::ForegroundColor{Color::yellow},
    option::SpinnerStates{std::vector<std::string>{"⠈", "⠐", "⠠", "⢀", "⡀", "⠄", "⠂", "⠁"}},
    option::FontStyles{std::vector<FontStyle>{FontStyle::bold}}
  };
 
  // Update spinner state
  auto job = [&spinner]() {
    while (true) {
      if (spinner.is_completed()) {
        spinner.set_option(option::ForegroundColor{Color::green});
        spinner.set_option(option::PrefixText{"✔"});
        spinner.set_option(option::ShowSpinner{false});
        spinner.set_option(option::ShowPercentage{false});
        spinner.set_option(option::PostfixText{"Authenticated!"});
        spinner.mark_as_completed();	
        break;
      } else
        spinner.tick();
      std::this_thread::sleep_for(std::chrono::milliseconds(40));
    }
  };
  std::thread thread(job);
  thread.join();  

  return 0;
}

Decremental Progress

indicators allows you to easily control the progress direction, i.e., incremental or decremental progress by using option::ProgressType. To program a countdown progress bar, use option::ProgressType::decremental

#include <chrono>
#include <indicators/progress_bar.hpp>
#include <thread>
using namespace indicators;

int main() {

  ProgressBar bar{option::BarWidth{50},
                  option::ProgressType{ProgressType::decremental},
                  option::Start{"["},
                  option::Fill{"■"},
                  option::Lead{"■"},
                  option::Remainder{"-"},
                  option::End{"]"},
                  option::PostfixText{"Reverting System Restore"},
                  option::ForegroundColor{Color::yellow},
                  option::FontStyles{std::vector<FontStyle>{FontStyle::bold}}};

  // Update bar state
  while (true) {
    bar.tick();
    if (bar.is_completed())
      break;
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
  }

  std::cout << termcolor::bold << termcolor::white
            << "Task Failed Successfully\n" << termcolor::reset;

  return 0;
}

Working with Iterables

If you'd like to use progress bars to indicate progress while iterating over iterables, e.g., a list of numbers, this can be achieved by using the option::MaxProgress:

#include <chrono>
#include <indicators/block_progress_bar.hpp>
#include <indicators/cursor_control.hpp>
#include <thread>

int main() {

  // Hide cursor
  indicators::show_console_cursor(false);

  // Random list of numbers
  std::vector<size_t> numbers;
  for (size_t i = 0; i < 1259438; ++i) {
      numbers.push_back(i);
  }

  using namespace indicators;
  BlockProgressBar bar{
    option::BarWidth{80},
    option::ForegroundColor{Color::white},
    option::FontStyles{
          std::vector<FontStyle>{FontStyle::bold}},
    option::MaxProgress{numbers.size()}
  };

  std::cout << "Iterating over a list of numbers (size = "
            << numbers.size() << ")\n";

  std::vector<size_t> result;
  for (size_t i = 0; i < numbers.size(); ++i) {

    // Perform some computation
    result.push_back(numbers[i] * numbers[i]);

    // Show iteration as postfix text
    bar.set_option(option::PostfixText{
      std::to_string(i) + "/" + std::to_string(numbers.size())
    });

    // update progress bar
    bar.tick();
  }

  bar.mark_as_completed();

  // Show cursor
  indicators::show_console_cursor(true);

  return 0;
}

Unicode Support

indicators supports multi-byte unicode characters in progress bars.

If the option::BarWidth is set, the library aims to respect this setting. When filling the bar, if the next Fill string has a display width that would exceed the bar width, then the library will fill the remainder of the bar with ' ' space characters instead.

See below an example of some progress bars, each with a bar width of 50, displaying different unicode characters:

#include <chrono>
#include <indicators/progress_bar.hpp>
#include <indicators/indeterminate_progress_bar.hpp>
#include <indicators/cursor_control.hpp>
#include <thread>

int main() {

    indicators::show_console_cursor(false);

    std::this_thread::sleep_for(std::chrono::milliseconds(2000));

    {
        // Plain old ASCII
        indicators::ProgressBar bar{
            indicators::option::BarWidth{50},
            indicators::option::Start{"["},
            indicators::option::Fill{"="},
            indicators::option::Lead{">"},
            indicators::option::Remainder{" "},
            indicators::option::End{" ]"},
            indicators::option::PostfixText{"Plain-old ASCII"},
            indicators::option::ForegroundColor{indicators::Color::green},
            indicators::option::FontStyles{
                std::vector<indicators::FontStyle>{indicators::FontStyle::bold}}
        };

        // Update bar state
        while (true) {
            bar.tick();
            if (bar.is_completed())
            break;
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    }

    {
        // Unicode
        indicators::ProgressBar bar{
            indicators::option::BarWidth{50},
            indicators::option::Start{"["},
            indicators::option::Fill{"驚くばかり"},
            indicators::option::Lead{">"},
            indicators::option::Remainder{" "},
            indicators::option::End{" ]"},
            indicators::option::PostfixText{"Japanese"},
            indicators::option::ForegroundColor{indicators::Color::yellow},
            indicators::option::FontStyles{
                std::vector<indicators::FontStyle>{indicators::FontStyle::bold}}
        };

        // Update bar state
        while (true) {
            bar.tick();
            if (bar.is_completed())
            break;
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    }

    {
        // Russian
        indicators::ProgressBar bar{
            indicators::option::BarWidth{50},
            indicators::option::Start{"["},
            indicators::option::Fill{"Потрясающие"},
            indicators::option::Remainder{" "},
            indicators::option::End{" ]"},
            indicators::option::PostfixText{"Russian"},
            indicators::option::ForegroundColor{indicators::Color::red},
            indicators::option::FontStyles{
                std::vector<indicators::FontStyle>{indicators::FontStyle::bold}}
        };

        // Update bar state
        while (true) {
            bar.tick();
            if (bar.is_completed())
            break;
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    }

    {
        // Greek
        indicators::ProgressBar bar{
            indicators::option::BarWidth{50},
            indicators::option::Start{"["},
            indicators::option::Fill{"Φοβερός"},
            indicators::option::Remainder{" "},
            indicators::option::End{" ]"},
            indicators::option::PostfixText{"Greek"},
            indicators::option::ForegroundColor{indicators::Color::cyan},
            indicators::option::FontStyles{
                std::vector<indicators::FontStyle>{indicators::FontStyle::bold}}
        };

        // Update bar state
        while (true) {
            bar.tick();
            if (bar.is_completed())
            break;
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    }

    {
        // Chinese
        indicators::ProgressBar bar{
            indicators::option::BarWidth{50},
            indicators::option::Start{"["},
            indicators::option::Fill{"太棒了"},
            indicators::option::Remainder{" "},
            indicators::option::End{" ]"},
            indicators::option::PostfixText{"Chinese"},
            indicators::option::ForegroundColor{indicators::Color::green},
            indicators::option::FontStyles{
                std::vector<indicators::FontStyle>{indicators::FontStyle::bold}}
        };

        // Update bar state
        while (true) {
            bar.tick();
            if (bar.is_completed())
            break;
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }        
    }

    {
        // Emojis
        indicators::ProgressBar bar{
            indicators::option::BarWidth{50},
            indicators::option::Start{"["},
            indicators::option::Fill{"🔥"},
            indicators::option::Lead{"🔥"},
            indicators::option::Remainder{" "},
            indicators::option::End{" ]"},
            indicators::option::PostfixText{"Emojis"},
            indicators::option::ForegroundColor{indicators::Color::white},
            indicators::option::FontStyles{
                std::vector<indicators::FontStyle>{indicators::FontStyle::bold}}
        };

        // Update bar state
        while (true) {
            bar.tick();
            if (bar.is_completed())
            break;
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    }

    {
        // Indeterminate progress bar
        indicators::IndeterminateProgressBar bar{
            indicators::option::BarWidth{50},
            indicators::option::Start{"["},
            indicators::option::Fill{"✯"},
            indicators::option::Lead{"載入中"},
            indicators::option::End{" ]"},
            indicators::option::PostfixText{"Loading Progress Bar"},
            indicators::option::ForegroundColor{indicators::Color::yellow},
            indicators::option::FontStyles{
                std::vector<indicators::FontStyle>{indicators::FontStyle::bold}}
        };

        auto job = [&bar]() {
            std::this_thread::sleep_for(std::chrono::milliseconds(10000));
            bar.mark_as_completed();
        };
        std::thread job_completion_thread(job);

        // Update bar state
        while (!bar.is_completed()) {
            bar.tick();
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }

        job_completion_thread.join();
    }

    indicators::show_console_cursor(true);

  return 0;
}

Building Samples

git clone https://github.com/p-ranav/indicators
cd indicators
mkdir build && cd build
cmake -DINDICATORS_SAMPLES=ON -DINDICATORS_DEMO=ON ..
make

WinLibs + MinGW

For Windows, if you use WinLibs like I do, the cmake command would look like this:

foo@bar:~$ mkdir build && cd build
foo@bar:~$ cmake -G "MinGW Makefiles" -DCMAKE_CXX_COMPILER="C:/WinLibs/mingw64/bin/g++.exe" -DINDICATORS_SAMPLES=ON -DINDICATORS_DEMO=ON ..
foo@bar:~$ make -j4

Generating Single Header

python3 utils/amalgamate/amalgamate.py -c single_include.json -s .

Contributing

Contributions are welcome, have a look at the CONTRIBUTING.md document for more information.


Author: p-ranav
Source Code: https://github.com/p-ranav/indicators/
License: View license

#cpluplus 

Lina  Biyinzika

Lina Biyinzika

1651161600

Curlcpp: An Object Oriented C++ Wrapper for CURL (libcurl)

curlcpp

An object-oriented C++ wrapper for cURL tool

If you want to know a bit more about cURL and libcurl, you should go on the official website http://curl.haxx.se/

Donate

Help me to improve this project!

Compile and link

Standalone

cd build
cmake ..
make # -j2

Note: cURL >= 7.34 is required.

Then add <curlcpp root>/build/src/ to your library path and <curlcpp root>/include/ to your include path.

When linking, link against curlcpp (e.g.: g++ -std=c++11 example.cpp -o example -lcurlcpp -lcurl). Or if you want run from terminal,

g++ -std=c++11 example.cpp -L/home/username/path/to/build/src/ -I/home/username/path/to/include/ -lcurlcpp -lcurl

Submodule

When using a git submodule and CMake-buildsystem, add the following lines to your CMakeLists.txt:

ADD_SUBDIRECTORY(ext/curlcpp) # Change `ext/curlcpp` to a directory according to your setup
INCLUDE_DIRECTORIES(${CURLCPP_SOURCE_DIR}/include)

Simple usage example

Here are some usage examples. You will find more examples in the test folder!

Here's an example of a simple HTTP request to get google web page, using the curl_easy interface:

#include "curl_easy.h"

using curl::curl_easy;
using curl::curl_easy_exception;
using curl::curlcpp_traceback;

/**
 * This example shows how to make a simple request with curl.
 */
int main() {
    // Easy object to handle the connection.
    curl_easy easy;

    // Add some options.
    easy.add<CURLOPT_URL>("http://<your_url_here>");
    easy.add<CURLOPT_FOLLOWLOCATION>(1L);

    try {
        easy.perform();
    } catch (curl_easy_exception &error) {
        // If you want to print the last error.
        std::cerr<<error.what()<<std::endl;
    }
    return 0;
}

If you want to get information about the current curl session, you could do:

#include "curl_easy.h"
#include "curl_ios.h"
#include "curl_exception.h"

using std::ostringstream;

using curl::curl_easy;
using curl::curl_easy_exception;
using curl::curlcpp_traceback;
using curl::curl_ios;

/**
 * This example shows how to use the easy interface and obtain
 * informations about the current session.
 */
int main(int argc, const char **argv) {
    // Let's declare a stream
    ostringstream stream;

    // We are going to put the request's output in the previously declared stream
    curl_ios<ostringstream> ios(stream);

    // Declaration of an easy object
    curl_easy easy(ios);

    // Add some option to the curl_easy object.
    easy.add<CURLOPT_URL>("http://<your_url_here>");
    easy.add<CURLOPT_FOLLOWLOCATION>(1L);

    try {
        easy.perform();

        // Retrieve information about curl current session.
        auto x = easy.get_info<CURLINFO_CONTENT_TYPE>();

        /**
         * get_info returns a curl_easy_info object. With the get method we retrieve
         * the std::pair object associated with it: the first item is the return code of the
         * request. The second is the element requested by the specified libcurl macro.
         */
        std::cout<<x.get()<<std::endl;

    } catch (curl_easy_exception &error) {
        // If you want to print the last error.
        std::cerr<<error.what()<<std::endl;

        // If you want to print the entire error stack you can do
        error.print_traceback();
    }
    return 0;
}

Here's instead, the creation of an HTTPS POST login form:

#include <string>

#include "curl_easy.h"
#include "curl_pair.h"
#include "curl_form.h"
#include "curl_exception.h"

using std::string;

using curl::curl_form;
using curl::curl_easy;
using curl::curl_pair;
using curl::curl_easy_exception;
using curl::curlcpp_traceback;

int main(int argc, const char * argv[]) {
    curl_form form;
    curl_easy easy;

    // Forms creation
    curl_pair<CURLformoption,string> name_form(CURLFORM_COPYNAME,"user");
    curl_pair<CURLformoption,string> name_cont(CURLFORM_COPYCONTENTS,"you username here");
    curl_pair<CURLformoption,string> pass_form(CURLFORM_COPYNAME,"passw");
    curl_pair<CURLformoption,string> pass_cont(CURLFORM_COPYCONTENTS,"your password here");
    
    try {
        // Form adding
        form.add(name_form,name_cont);
        form.add(pass_form,pass_cont);
        
        // Add some options to our request
        easy.add<CURLOPT_URL>("http://<your_url_here>");
        easy.add<CURLOPT_SSL_VERIFYPEER>(false);
        easy.add<CURLOPT_HTTPPOST>(form.get());
        // Execute the request.
        easy.perform();

    } catch (curl_easy_exception &error) {
        // If you want to get the entire error stack we can do:
        curlcpp_traceback errors = error.get_traceback();
        // Otherwise we could print the stack like this:
        error.print_traceback();
    }
    return 0;
}

And if we would like to put the returned content in a file? Nothing easier than:

#include <iostream>
#include <ostream>
#include <fstream>

#include "curl_easy.h"
#include "curl_ios.h"
#include "curl_exception.h"

using std::cout;
using std::endl;
using std::ostream;
using std::ofstream;

using curl::curl_easy;
using curl::curl_ios;
using curl::curl_easy_exception;
using curl::curlcpp_traceback;

int main(int argc, const char * argv[]) {
    // Create a file
    ofstream myfile;
    myfile.open ("/path/to/your/file");
    
    // Create a curl_ios object to handle the stream
    curl_ios<ostream> writer(myfile);
    // Pass it to the easy constructor and watch the content returned in that file!
    curl_easy easy(writer);
    
    // Add some option to the easy handle
    easy.add<CURLOPT_URL>("http://<your_url_here>");
    easy.add<CURLOPT_FOLLOWLOCATION>(1L);
    try {
        // Execute the request
        easy.perform();

    } catch (curl_easy_exception &error) {
        // If you want to print the last error.
        std::cerr<<error.what()<<std::endl;

        // If you want to print the entire error stack you can do
        error.print_traceback();
    }
    myfile.close();
    return 0;
}

Not interested in files? So let's put the request's output in a variable!

#include <iostream>
#include <ostream>

#include "curl_easy.h"
#include "curl_form.h"
#include "curl_ios.h"
#include "curl_exception.h"

using std::cout;
using std::endl;
using std::ostringstream;

using curl::curl_easy;
using curl::curl_ios;
using curl::curl_easy_exception;
using curl::curlcpp_traceback;

int main() {
    // Create a stringstream object
    ostringstream str;
    // Create a curl_ios object, passing the stream object.
    curl_ios<ostringstream> writer(str);
    
    // Pass the writer to the easy constructor and watch the content returned in that variable!
    curl_easy easy(writer);
    // Add some option to the easy handle
    easy.add<CURLOPT_URL>("http://<your_url_here>");
    easy.add<CURLOPT_FOLLOWLOCATION>(1L);

    try {
        easy.perform();

        // Let's print the stream content
        cout<<str.str()<<endl;

    } catch (curl_easy_exception &error) {
        // If you want to print the last error.
        std::cerr<<error.what()<<std::endl;

        // If you want to print the entire error stack you can do
        error.print_traceback();
    }
    return 0;
}

I have implemented a sender and a receiver to make it easy to use send/receive without handling buffers. For example, a very simple send/receiver would be:

#include <iostream>
#include <string>

#include "curl_easy.h"
#include "curl_form.h"
#include "curl_pair.h"
#include "curl_receiver.h"
#include "curl_exception.h"
#include "curl_sender.h"

using std::cout;
using std::endl;
using std::string;

using curl::curl_form;
using curl::curl_easy;
using curl::curl_sender;
using curl::curl_receiver;
using curl::curl_easy_exception;
using curl::curlcpp_traceback;

int main(int argc, const char * argv[]) {
    // Simple request
    string request = "GET / HTTP/1.0\r\nHost: example.com\r\n\r\n";
    // Creation of easy object.
    curl_easy easy;
    try {
        easy.add<CURLOPT_URL>("http://<your_url_here>");
        // Just connect
        easy.add<CURLOPT_CONNECT_ONLY>(true);
        // Execute the request.
        easy.perform();

    } catch (curl_easy_exception &error) {
        // If you want to get the entire error stack we can do:
        curlcpp_traceback errors = error.get_traceback();
        // Otherwise we could print the stack like this:
        error.print_traceback();
    }
    
    // Creation of a sender. You should wait here using select to check if socket is ready to send.
    curl_sender<string> sender(easy);
    sender.send(request);
    // Prints che sent bytes number.
    cout<<"Sent bytes: "<<sender.get_sent_bytes()<<endl;

    for(;;) {
        // You should wait here to check if socket is ready to receive
        try {
            // Create a receiver
            curl_receiver<char, 1024> receiver;
            // Receive the content on the easy handler
            receiver.receive(easy);
            // Prints the received bytes number.
            cout<<"Receiver bytes: "<<receiver.get_received_bytes()<<endl;

        } catch (curl_easy_exception &error) {
            // If any errors occurs, exit from the loop
            break;
        }
    }
    return 0;
}

Author: JosephP91
Source Code: https://github.com/JosephP91/curlcpp
License: MIT License
#cpluplus #c 

Kevin  Taylor

Kevin Taylor

1649659620

Concurrencpp: Modern Concurrency for C++.

concurrencpp, the C++ concurrency library

concurrencpp is a tasking library for C++ allowing developers to write highly concurrent applications easily and safely by using tasks, executors and coroutines. By using concurrencpp applications can break down big procedures that need to be processed asynchronously into smaller tasks that run concurrently and work in a co-operative manner to achieve the wanted result. concurrencpp also allows applications to write parallel algorithms easily by using parallel coroutines.

concurrencpp main advantages are:

  • Being able to write modern concurrency code without having to rely on low-level concurrency primitives like locks and condition variables.
  • Being able to write highly concurrent and parallel applications that scale automatically to use all hardware resources, as needed.
  • Being able to write non-blocking, synchronous-like code easily by using C++20 coroutines and the co_await keyword.
  • Reducing the possibility of race conditions, data races and deadlocks by using high-level objects with built-in synchronization.
  • concurrencpp provides various types of commonly used executors with a complete coroutine integration.
  • Applications can extend the library by implementing their own provided executors.

Table of contents


concurrencpp overview

concurrencpp is a task-centric library. A task is an asynchronous operation. Tasks offer a higher level of abstraction for concurrent code than traditional thread-centric approaches. Tasks can be chained together, meaning that tasks pass their asynchronous result from one to another, where the result of one task is used as if it were a parameter or an intermediate value of another ongoing task. Tasks allow applications to utilize available hardware resources better and scale much more than using raw threads, since tasks can be suspended, waiting for another task to produce a result, without blocking underlying OS-threads. Tasks bring much more productivity to developers by allowing them to focus more on business-logic and less on low-level concepts like thread management and inter-thread synchronization.

While tasks specify what actions have to be executed, executors are worker-objects that specify where and how to execute tasks. Executors spare applications the managing of thread pools and task queues themselves. Executors also decouple those concepts away from application code, by providing a unified API for creating and scheduling tasks.

Tasks communicate with each other using result objects. A result object is an asynchronous pipe that pass the asynchronous result of one task to another ongoing-task. Results can be awaited and resolved in a non-blocking manner.

These three concepts - the task, the executor and the associated result are the building blocks of concurrencpp. Executors run tasks that communicate with each-other by sending results through result-objects. Tasks, executors and result objects work together symbiotically to produce concurrent code which is fast and clean.

concurrencpp is built around the RAII concept. In order to use tasks and executors, applications create a runtime instance in the beginning of the main function. The runtime is then used to acquire existing executors and register new user-defined executors. Executors are used to create and schedule tasks to run, and they might return a result object that can be used to marshal the asynchronous result to another task that acts as its consumer. When the runtime is destroyed, it iterates over every stored executor and calls its shutdown method. Every executor then exits gracefully. Unscheduled tasks are destroyed, and attempts to create new tasks will throw an exception.

"Hello world" program using concurrencpp:

#include "concurrencpp/concurrencpp.h"
#include <iostream>

int main() {
    concurrencpp::runtime runtime;
    auto result = runtime.thread_executor()->submit([] {
        std::cout << "hello world" << std::endl;
    });

    result.get();
    return 0;
}

In this basic example, we created a runtime object, then we acquired the thread executor from the runtime. We used submit to pass a lambda as our given callable. This lambda returns void, hence, the executor returns a result<void> object that marshals the asynchronous result back to the caller. main calls get which blocks the main thread until the result becomes ready. If no exception was thrown, get returns void. If an exception was thrown, get re-throws it. Asynchronously, thread_executor launches a new thread of execution and runs the given lambda. It implicitly co_return void and the task is finished. main is then unblocked.

Concurrent even-number counting:

#include "concurrencpp/concurrencpp.h"

#include <iostream>
#include <vector>
#include <algorithm>

#include <ctime>

using namespace concurrencpp;

std::vector<int> make_random_vector() {
    std::vector<int> vec(64 * 1'024);

    std::srand(std::time(nullptr));
    for (auto& i : vec) {
        i = ::rand();
    }

    return vec;
}

result<size_t> count_even(std::shared_ptr<thread_pool_executor> tpe, const std::vector<int>& vector) {
    const auto vecor_size = vector.size();
    const auto concurrency_level = tpe->max_concurrency_level();
    const auto chunk_size = vecor_size / concurrency_level;

    std::vector<result<size_t>> chunk_count;

    for (auto i = 0; i < concurrency_level; i++) {
        const auto chunk_begin = i * chunk_size;
        const auto chunk_end = chunk_begin + chunk_size;
        auto result = tpe->submit([&vector, chunk_begin, chunk_end]() -> size_t {
            return std::count_if(vector.begin() + chunk_begin, vector.begin() + chunk_end, [](auto i) {
                return i % 2 == 0;
            });
        });

        chunk_count.emplace_back(std::move(result));
    }

    size_t total_count = 0;

    for (auto& result : chunk_count) {
        total_count += co_await result;
    }

    co_return total_count;
}

int main() {
    concurrencpp::runtime runtime;
    const auto vector = make_random_vector();
    auto result = count_even(runtime.thread_pool_executor(), vector);
    const auto total_count = result.get();
    std::cout << "there are " << total_count << " even numbers in the vector" << std::endl;
    return 0;
}

In this example, we start the program by creating a runtime object. We create a vector filled with random numbers, then we acquire the thread_pool_executor from the runtime and call count_even. count_even is a coroutine that spawns more tasks and co_awaits for them to finish inside. max_concurrency_level returns the maximum amount of workers that the executor supports, In the threadpool executor case, the number of workers is calculated from the number of cores. We then partition the array to match the number of workers and send every chunk to be processed in its own task. Asynchronously, the workers count how many even numbers each chunk contains, and co_return the result. count_even sums every result by pulling the count using co_await, the final result is then co_returned. The main thread, which was blocked by calling get is unblocked and the total count is returned. main prints the number of even numbers and the program terminates gracefully.

Tasks

Every big or complex operation can be broken down to smaller and chainable steps. Tasks are asynchronous operations implementing those computational steps. Tasks can run anywhere with the help of executors. While tasks can be created from regular callables (such as functors and lambdas), Tasks are mostly used with coroutines, which allow smooth suspension and resumption. In concurrencpp, the task concept is represented by the concurrencpp::task class. Although the task concept is central to concurrenpp, applications will rarely have to create and manipulate task objects themselves, as task objects are created and scheduled by the runtime with no external help.

concurrencpp coroutines

concurrencpp allows applications to produce and consume coroutines as the main way of creating tasks. concurrencpp supports both eager and lazy tasks.

Eager tasks start to run the moment they are invoked. This type of execution is recommended when applications need to fire an asynchronous action and consume its result later on (fire and consume later), or completely ignore the asynchronous result (fire and forget).

Eager tasks can return result or null_result. result return type tells the coroutine to marshal the returned value or the thrown exception (fire and consume later) while null_result return type tells the coroutine to drop and ignore any of them (fire and forget).

Eager coroutines can start to run synchronously, in the caller thread. This kind of coroutines is called "regular coroutines". Concurrencpp eager coroutines can also start to run in parallel, inside a given executor, this kind of coroutines is called "parallel coroutines".

Lazy tasks, on the other hand, start to run only when co_awaited. This type of tasks is recommended when the result of the task is meant to be consumed immediately after creating the task. Lazy tasks, being deferred, are a bit more optimized for the case of immediate-consumption, as they do not need special thread-synchronization in order to marshal the asynchronous result back to its consumer. The compiler might also optimize away some memory allocations needed to form the underlying coroutine promise. It is not possible to fire a lazy task and execute something else meanwhile - the firing of a lazy-callee coroutine necessarily means the suspension of the caller-coroutine. The caller coroutine will only be resumed when the lazy-callee coroutine completes. Lazy tasks can only return lazy_result.

Lazy tasks can be converted to eager tasks by calling lazy_result::run. This method runs the lazy task inline and returns a result object that monitors the newly started task. If developers are unsure which result type to use, they are encouraged to use lazy results, as they can be converted to regular (eager) results if needed.

When a function returns any of lazy_result, result or null_resultand contains at least one co_await or co_return in its body, the function is a concurrencpp coroutine. Every valid concurrencpp coroutine is a valid task. In our count-even example above, count_even is such a coroutine. We first spawned count_even, then inside it the threadpool executor spawned more child tasks (that are created from regular callables), that were eventually joined using co_await.

Executors

A concurrencpp executor is an object that is able to schedule and run tasks. Executors simplify the work of managing resources such as threads, thread pools and task queues by decoupling them away from application code. Executors provide a unified way of scheduling and executing tasks, since they all extend concurrencpp::executor.

executor API

class executor {
    /*
        Initializes a new executor and gives it a name.
    */
    executor(std::string_view name);

    /*
        Destroys this executor.
    */
    virtual ~executor() noexcept = default;

    /*
        The name of the executor, used for logging and debugging.
    */
    const std::string name;

    /*
        Schedules a task to run in this executor.
        Throws concurrencpp::errors::runtime_shutdown exception if shutdown was called before.
    */
    virtual void enqueue(concurrencpp::task task) = 0;

    /*
        Schedules a range of tasks to run in this executor.
        Throws concurrencpp::errors::runtime_shutdown exception if shutdown was called before.
    */    
    virtual void enqueue(std::span<concurrencpp::task> tasks) = 0;

    /*
        Returns the maximum count of real OS threads this executor supports.
        The actual count of threads this executor is running might be smaller than this number.
        returns numeric_limits<int>::max if the executor does not have a limit for OS threads.
    */
    virtual int max_concurrency_level() const noexcept = 0;

    /*
        Returns true if shutdown was called before, false otherwise.
    */
    virtual bool shutdown_requested() const noexcept = 0;

    /*
        Shuts down the executor:
        - Tells underlying threads to exit their work loop and joins them.
        - Destroys unexecuted coroutines.
        - Makes subsequent calls to enqueue, post, submit, bulk_post and
            bulk_submit to throw concurrencpp::errors::runtime_shutdown exception.
        - Makes shutdown_requested return true.
    */
    virtual void shutdown() noexcept = 0;

    /*
        Turns a callable and its arguments into a task object and
        schedules it to run in this executor using enqueue.
        Arguments are passed to the task by decaying them first.
        Throws errors::runtime_shutdown exception if shutdown has been called before.
    */
    template<class callable_type, class ... argument_types>
    void post(callable_type&& callable, argument_types&& ... arguments);
    
    /*
        Like post, but returns a result object that marshals the asynchronous result.
        Throws errors::runtime_shutdown exception if shutdown has been called before.
    */
    template<class callable_type, class ... argument_types>
    result<type> submit(callable_type&& callable, argument_types&& ... arguments);

    /*
        Turns an array of callables into an array of tasks and
        schedules them to run in this executor using enqueue.
        Throws errors::runtime_shutdown exception if shutdown has been called before.
    */
    template<class callable_type>
    void bulk_post(std::span<callable_type> callable_list);

    /*
        Like bulk_post, but returns an array of result objects that marshal the asynchronous results.
        Throws errors::runtime_shutdown exception if shutdown has been called before.
    */    
    template<class callable_type>
    std::vector<concurrencpp::result<type>> bulk_submit(std::span<callable_type> callable_list);
};

Executor types

As mentioned above, concurrencpp provides commonly used executors. These executor types are:

thread pool executor - a general purpose executor that maintains a pool of threads. The thread pool executor is suitable for short cpu-bound tasks that don't block. Applications are encouraged to use this executor as the default executor for non-blocking tasks. The concurrencpp thread pool provides dynamic thread injection and dynamic work balancing.

background executor - a threadpool executor with a larger pool of threads. Suitable for launching short blocking tasks like file io and db queries. Important note: when consuming results this executor returned by calling submit and bulk_submit, it is important to switch execution using resume_on to a cpu-bound executor, in order to prevent cpu-bound tasks to be processed inside background_executor.

example:

    auto result = background_executor.submit([] { /* some blocking action */ });
    auto done_result = co_await result.resolve();
    co_await resume_on(some_cpu_executor);
    auto val = co_await done_result;  // runs inside some_cpu_executor

thread executor - an executor that launches each enqueued task to run on a new thread of execution. Threads are not reused. This executor is good for long running tasks, like objects that run a work loop, or long blocking operations.

worker thread executor - a single thread executor that maintains a single task queue. Suitable when applications want a dedicated thread that executes many related tasks.

manual executor - an executor that does not execute coroutines by itself. Application code can execute previously enqueued tasks by manually invoking its execution methods.

derivable executor - a base class for user defined executors. Although inheriting directly from concurrencpp::executor is possible, derivable_executor uses the CRTP pattern that provides some optimization opportunities for the compiler.

inline executor - mainly used to override the behavior of other executors. Enqueuing a task is equivalent to invoking it inline.

Using executors

The bare mechanism of an executor is encapsulated in its enqueue method. This method enqueues a task for execution and has two overloads: One overload receives a single task object as an argument, and another that receives a span of task objects. The second overload is used to enqueue a batch of tasks. This allows better scheduling heuristics and decreased contention.

Applications don't have to rely on enqueue alone, concurrencpp::executor provides an API for scheduling user callables by converting them to task objects behind the scenes. Applications can request executors to return a result object that marshals the asynchronous result of the provided callable. This is done by calling executor::submit and executor::bulk_submit. submit gets a callable, and returns a result object. executor::bulk_submit gets a span of callables and returns a vectorof result objects in a similar way submit works. In many cases, applications are not interested in the asynchronous value or exception. In this case, applications can use executor:::post and executor::bulk_post to schedule a callable or a span of callables to be executed, but also tells the task to drop any returned value or thrown exception. Not marshaling the asynchronous result is faster than marshaling, but then we have no way of knowing the status or the result of the ongoing task.

post, bulk_post, submit and bulk_submit use enqueue behind the scenes for the underlying scheduling mechanism.

thread_pool_executor API

Aside from post, submit, bulk_post and bulk_submit, the thread_pool_executor provides these additional methods.

class thread_pool_executor {

    /*
        Returns the number of milliseconds each thread-pool worker
        remains idle (lacks any task to execute) before exiting.
        This constant can be set by passing a runtime_options object
        to the constructor of the runtime class.
    */
    std::chrono::milliseconds max_worker_idle_time() const noexcept;

};

manual_executor API

Aside from post, submit, bulk_post and bulk_submit, the manual_executor provides these additional methods.

class manual_executor {

    /*
        Destructor. Equivalent to clear.
    */
    ~manual_executor() noexcept;

    /*
        Returns the number of enqueued tasks at the moment of invocation.
        This number can change quickly by the time the application handles it, it should be used as a hint.
        This method is thread safe.
        Might throw std::system_error if one of the underlying synchronization primitives throws.
    */
    size_t size() const noexcept;
        
    /*
        Queries whether the executor is empty from tasks at the moment of invocation.
        This value can change quickly by the time the application handles it, it should be used as a hint.
        This method is thread safe.
        Might throw std::system_error if one of the underlying synchronization primitives throws.
    */
    bool empty() const noexcept;

    /*
        Clears the executor from any enqueued but yet to-be-executed tasks,
        and returns the number of cleared tasks.
        Tasks enqueued to this executor by (post_)submit method are resumed
        and errors::broken_task exception is thrown inside them.
        Ongoing tasks that are being executed by loop_once(_XXX) or loop(_XXX) are uneffected.
        This method is thread safe.
        Might throw std::system_error if one of the underlying synchronization primitives throws.
        Throws errors::shutdown_exception if shutdown was called before.
    */
    size_t clear();

    /*
        Tries to execute a single task. If at the moment of invocation the executor
        is empty, the method does nothing.
        Returns true if a task was executed, false otherwise.
        This method is thread safe.
        Might throw std::system_error if one of the underlying synchronization primitives throws. 
        Throws errors::shutdown_exception if shutdown was called before.
    */
    bool loop_once();

    /*
        Tries to execute a single task.
        This method returns when either a task was executed or max_waiting_time
        (in milliseconds) has reached.
        If max_waiting_time is 0, the method is equivalent to loop_once.
        If shutdown is called from another thread, this method returns
        and throws errors::shutdown_exception.
        This method is thread safe.
        Might throw std::system_error if one of the underlying synchronization primitives throws.
        Throws errors::shutdown_exception if shutdown was called before.
    */
    bool loop_once_for(std::chrono::milliseconds max_waiting_time);

    /*
        Tries to execute a single task.
        This method returns when either a task was executed or timeout_time has reached.
        If timeout_time has already expired, this method is equivalent to loop_once.
        If shutdown is called from another thread, this method
        returns and throws errors::shutdown_exception.
        This method is thread safe.
        Might throw std::system_error if one of the underlying synchronization primitives throws.
        Throws errors::shutdown_exception if shutdown was called before.
    */
    template<class clock_type, class duration_type>
    bool loop_once_until(std::chrono::time_point<clock_type, duration_type> timeout_time);
   
    /*
        Tries to execute max_count enqueued tasks and returns the number of tasks that were executed.
        This method does not wait: it returns when the executor
        becomes empty from tasks or max_count tasks have been executed.
        This method is thread safe.
        Might throw std::system_error if one of the underlying synchronization primitives throws.
        Throws errors::shutdown_exception if shutdown was called before.
    */
    size_t loop(size_t max_count);

    /*
        Tries to execute max_count tasks.
        This method returns when either max_count tasks were executed or a
        total amount of max_waiting_time has passed.
        If max_waiting_time is 0, the method is equivalent to loop.
        Returns the actual amount of tasks that were executed.
        If shutdown is called from another thread, this method returns
        and throws errors::shutdown_exception.
        This method is thread safe.
        Might throw std::system_error if one of the underlying synchronization primitives throws.
        Throws errors::shutdown_exception if shutdown was called before.
    */
    size_t loop_for(size_t max_count, std::chrono::milliseconds max_waiting_time);

    /*    
        Tries to execute max_count tasks.
        This method returns when either max_count tasks were executed or timeout_time has reached.
        If timeout_time has already expired, the method is equivalent to loop.
        Returns the actual amount of tasks that were executed.
        If shutdown is called from another thread, this method returns
        and throws errors::shutdown_exception.
        This method is thread safe.
        Might throw std::system_error if one of the underlying synchronization primitives throws.
        Throws errors::shutdown_exception if shutdown was called before.
    */
    template<class clock_type, class duration_type>
    size_t loop_until(size_t max_count, std::chrono::time_point<clock_type, duration_type> timeout_time);
    
    /*
        Waits for at least one task to be available for execution.
        This method should be used as a hint,
        as other threads (calling loop, for example) might empty the executor,
        before this thread has a chance to do something with the newly enqueued tasks.
        If shutdown is called from another thread, this method returns
        and throws errors::shutdown_exception.
        This method is thread safe.
        Might throw std::system_error if one of the underlying synchronization primitives throws.
        Throws errors::shutdown_exception if shutdown was called before.
    */
    void wait_for_task();

    /*
        This method returns when one or more tasks are available for
        execution or max_waiting_time has passed.    
        Returns true if at at least one task is available for execution, false otherwise.
        This method should be used as a hint, as other threads (calling loop, for example)
        might empty the executor, before this thread has a chance to do something
        with the newly enqueued tasks.
        If shutdown is called from another thread, this method
        returns and throws errors::shutdown_exception.
        This method is thread safe.
        Might throw std::system_error if one of the underlying synchronization primitives throws.
        Throws errors::shutdown_exception if shutdown was called before.
    */
    bool wait_for_task_for(std::chrono::milliseconds max_waiting_time);

    /*
        This method returns when one or more tasks are available for execution or timeout_time has reached.    
        Returns true if at at least one task is available for execution, false otherwise.
        This method should be used as a hint,
        as other threads (calling loop, for example) might empty the executor,
        before this thread has a chance to do something with the newly enqueued tasks.
        If shutdown is called from another thread, this method
        returns and throws errors::shutdown_exception.
        This method is thread safe.
        Might throw std::system_error if one of the underlying synchronization primitives throws.
        Throws errors::shutdown_exception if shutdown was called before.
    */
    template<class clock_type, class duration_type>
    bool wait_for_task_until(std::chrono::time_point<clock_type, duration_type> timeout_time);
    
    /*
        This method returns when max_count or more tasks are available for execution.    
        This method should be used as a hint, as other threads
        (calling loop, for example) might empty the executor,
        before this thread has a chance to do something with the newly enqueued tasks.
        If shutdown is called from another thread, this method returns
        and throws errors::shutdown_exception.
        This method is thread safe. 
        Might throw std::system_error if one of the underlying synchronization primitives throws.
        Throws errors::shutdown_exception if shutdown was called before.
    */
    void wait_for_tasks(size_t max_count);

    /*
        This method returns when max_count or more tasks are available for execution
        or max_waiting_time (in milliseconds) has passed.    
        Returns the number of tasks available for execution when the method returns.
        This method should be used as a hint, as other
        threads (calling loop, for example) might empty the executor,
        before this thread has a chance to do something with the newly enqueued tasks.
        If shutdown is called from another thread, this method returns
        and throws errors::shutdown_exception.
        This method is thread safe.  
        Might throw std::system_error if one of the underlying synchronization primitives throws.
        Throws errors::shutdown_exception if shutdown was called before.    
    */
    size_t wait_for_tasks_for(size_t count, std::chrono::milliseconds max_waiting_time);

    /*
        This method returns when max_count or more tasks are available for execution
        or timeout_time is reached.    
        Returns the number of tasks available for execution when the method returns.
        This method should be used as a hint, as other threads
        (calling loop, for example) might empty the executor,
        before this thread has a chance to do something with the newly enqueued tasks.
        If shutdown is called from another thread, this method returns
        and throws errors::shutdown_exception.
        This method is thread safe.  
        Might throw std::system_error if one of the underlying synchronization primitives throws.
        Throws errors::shutdown_exception if shutdown was called before.    
    */
    template<class clock_type, class duration_type>
    size_t wait_for_tasks_until(size_t count, std::chrono::time_point<clock_type, duration_type> timeout_time);
        
};

Result objects

Asynchronous values and exceptions can be consumed using concurrencpp result objects. The result type represents the asynchronous result of an eager task while lazy_result represents the deferred result of a lazy task.

When a task (eager or lazy) completes, it either returns a valid value or throws an exception. In either case, this asynchronous result is marshaled to the consumer of the result object.

result objects form asymmetric coroutines - the execution of a caller-coroutine is not effected by the execution of a callee-coroutine, both coroutines can run independently. Only when consuming the result of the callee-coroutine, the caller-coroutine might be suspended awaiting the callee to complete. Up until that point both coroutines run independently. The callee-coroutine runs whether its result is consumed or not.

lazy_result objects form symmetric coroutines - execution of a callee-coroutine happens only after the suspension of the caller-coroutine. When awaiting a lazy result, the current coroutine is suspended and the lazy task associated with the lazy result starts to run. After the callee-coroutine completes and yields a result, the caller-coroutine is resumed. If a lazy result is not consumed, its associated lazy task never starts to run.

All result objects are a move-only type, and as such, they cannot be used after their content was moved to another result object. In this case, the result object is considered to be empty and attempts to call any method other than operator bool and operator = will throw.

After the asynchronous result has been pulled out of the result object (for example, by calling get or operator co_await), the result object becomes empty. Emptiness can be tested with operator bool.

Awaiting a result means to suspend the current coroutine until the result object is ready. If a valid value was returned from the associated task, it is returned from the result object. If the associated task throws an exception, it is re-thrown. At the moment of awaiting, if the result is already ready, the current coroutine resumes immediately. Otherwise, it is resumed by the thread that sets the asynchronous result or exception.

Resolving a result is similar to awaiting it. The difference is that the co_await expression will return the result object itself, in a non empty form, in a ready state. The asynchronous result can then be pulled by using get or co_await.

Every result object has a status indicating the state of the asynchronous result. The result status varies from result_status::idle (the asynchronous result or exception haven't been produced yet) to result_status::value (the associated task terminated gracefully by returning a valid value) to result_status::exception (the task terminated by throwing an exception). The status can be queried by calling (lazy_)result::status.

result type

The result type represents the result of an ongoing, asynchronous task, similar to std::future.

Aside from awaiting and resolving result-objects, they can also be waited for by calling any of result::wait, result::wait_for, result::wait_until or result::get. Waiting for a result to finish is a blocking operation (in the case the asynchronous result is not ready), and will suspend the entire thread of execution waiting for the asynchronous result to become available. Waiting operations are generally discouraged and only allowed in root-level tasks or in contexts which allow it, like blocking the main thread waiting for the rest of the application to finish gracefully, or using concurrencpp::blocking_executor or concurrencpp::thread_executor.

Awaiting result objects by using co_await (and by doing so, turning the current function/task into a coroutine as well) is the preferred way of consuming result objects, as it does not block underlying threads.

result API

class result{
    /*
        Creates an empty result that isn't associated with any task.
    */
    result() noexcept = default;

    /*
        Destroys the result. Associated tasks are not cancelled.
        The destructor does not block waiting for the asynchronous result to become ready.
    */    
    ~result() noexcept = default;

    /*
        Moves the content of rhs to *this. After this call, rhs is empty.
    */
    result(result&& rhs) noexcept = default;

    /*
        Moves the content of rhs to *this. After this call, rhs is empty. Returns *this.        
    */
    result& operator = (result&& rhs) noexcept = default;

    /*
        Returns true if this is a non-empty result.
        Applications must not use this object if this->operator bool() is false.
    */
    explicit operator bool() const noexcept;

    /*
        Queries the status of *this.
        The returned value is any of result_status::idle, result_status::value or result_status::exception.
        Throws errors::empty_result if *this is empty.        
    */
    result_status status() const;

    /*
        Blocks the current thread of execution until this result is ready,
        when status() != result_status::idle.
        Throws errors::empty_result if *this is empty.
        Might throw std::bad_alloc if fails to allocate memory.
        Might throw std::system_error if one of the underlying synchronization primitives throws.                    
    */
    void wait();

    /*
        Blocks until this result is ready or duration has passed. Returns the status
        of this result after unblocking.
        Throws errors::empty_result if *this is empty.  
        Might throw std::bad_alloc if fails to allocate memory.
        Might throw std::system_error if one of the underlying synchronization primitives throws.
    */
    template<class duration_unit, class ratio>
    result_status wait_for(std::chrono::duration<duration_unit, ratio> duration);

    /*
        Blocks until this result is ready or timeout_time has reached. Returns the status
        of this result after unblocking.
        Throws errors::empty_result if *this is empty.         
        Might throw std::bad_alloc if fails to allocate memory.
        Might throw std::system_error if one of the underlying synchronization primitives throws.
    */
    template< class clock, class duration >
    result_status wait_until(std::chrono::time_point<clock, duration> timeout_time);

    /*
        Blocks the current thread of execution until this result is ready,
        when status() != result_status::idle.
        If the result is a valid value, it is returned, otherwise, get rethrows the asynchronous exception.        
        Throws errors::empty_result if *this is empty.         
        Might throw std::bad_alloc if fails to allocate memory.
        Might throw std::system_error if one of the underlying synchronization primitives throws.           
    */
    type get();

    /*
        Returns an awaitable used to await this result.
        If the result is already ready - the current coroutine resumes
        immediately in the calling thread of execution.
        If the result is not ready yet, the current coroutine is suspended
        and resumed when the asynchronous result is ready,
        by the thread which had set the asynchronous value or exception.
        In either way, after resuming, if the result is a valid value, it is returned.
        Otherwise, operator co_await rethrows the asynchronous exception.
        Throws errors::empty_result if *this is empty.                            
    */
    auto operator co_await();

    /*
        Returns an awaitable used to resolve this result.
        After co_await expression finishes, *this is returned in a non-empty form, in a ready state.
        Throws errors::empty_result if *this is empty.
    */    
    auto resolve();
};

lazy_result type

A lazy result object represents the result of a deferred lazy task.

lazy_result has the responsibility of both starting the associated lazy task and marshaling its deferred result back to its consumer. When awaited or resolved, the lazy result suspends the current coroutine and starts the associated lazy task. when the associated task completes, its asynchronous value is marshaled to the caller task, which is then resumed.

Sometimes, an API might return a lazy result, but applications need its associated task to run eagerly (without suspending the caller task). In this case, lazy tasks can be converted to eager tasks by calling run on its associated lazy result. In this case, the associated task will start to run inline, without suspending the caller task. The original lazy result is emptied and a valid result object that monitors the newly started task will be returned instead.

lazy_result API

class lazy_result {
    /*
        Creates an empty lazy result that isn't associated with any task.
    */
    lazy_result() noexcept = default;

    /*
        Moves the content of rhs to *this. After this call, rhs is empty.
    */
    lazy_result(lazy_result&& rhs) noexcept;

    /*
        Destroys the result. If not empty, the destructor destroys the associated task without resuming it.
    */
    ~lazy_result() noexcept;

    /*
        Moves the content of rhs to *this. After this call, rhs is empty. Returns *this.
        If *this is not empty, then operator= destroys the associated task without resuming it.
    */
    lazy_result& operator=(lazy_result&& rhs) noexcept;

    /*
        Returns true if this is a non-empty result.
        Applications must not use this object if this->operator bool() is false.
    */
    explicit operator bool() const noexcept;

    /*
        Queries the status of *this.
        The returned value is any of result_status::idle, result_status::value or result_status::exception.
        Throws errors::empty_result if *this is empty.  
    */
    result_status status() const;

    /*
        Returns an awaitable used to start the associated task and await this result.
        If the result is already ready - the current coroutine resumes immediately
        in the calling thread of execution.
        If the result is not ready yet, the current coroutine is suspended and
        resumed when the asynchronous result is ready,
        by the thread which had set the asynchronous value or exception.
        In either way, after resuming, if the result is a valid value, it is returned.
        Otherwise, operator co_await rethrows the asynchronous exception.
        Throws errors::empty_result if *this is empty.   
    */
    auto operator co_await();

    /*
        Returns an awaitable used to start the associated task and resolve this result.
        If the result is already ready - the current coroutine resumes immediately
        in the calling thread of execution.
        If the result is not ready yet, the current coroutine is suspended and resumed
        when the asynchronous result is ready, by the thread which
        had set the asynchronous value or exception.
        After co_await expression finishes, *this is returned in a non-empty form, in a ready state.    
        Throws errors::empty_result if *this is empty.
    */
    auto resolve();

    /*
        Runs the associated task inline and returns a result object that monitors the newly started task.
        After this call, *this is empty. 
        Throws errors::empty_result if *this is empty.
        Might throw std::bad_alloc if fails to allocate memory.
    */
    result<type> run();
};

Parallel coroutines

Regular eager coroutines start to run synchronously in the calling thread of execution. Execution might shift to another thread of execution if the coroutine undergoes a rescheduling, for example by awaiting an unready result object inside it. concurrencpp also provides parallel coroutines, which start to run inside a given executor, not in the invoking thread of execution. This style of scheduling coroutines is especially helpful when writing parallel algorithms, recursive algorithms and concurrent algorithms that use the fork-join model.

Every parallel coroutine must meet the following preconditions:

  1. Returns any of result / null_result .
  2. Gets executor_tag as its first argument .
  3. Gets any of type* / type& / std::shared_ptr<type>, where type is a concrete class of executor as its second argument.
  4. Contains any of co_await or co_return in its body.

If all the above applies, the function is a parallel coroutine: concurrencpp will start the coroutine suspended and immediately reschedule it to run in the provided executor. concurrencpp::executor_tag is a dummy placeholder to tell the concurrencpp runtime that this function is not a regular function, it needs to start running inside the given executor. Applications can then consume the result of the parallel coroutine by using the returned result object.

Parallel Fibonacci example:

#include "concurrencpp/concurrencpp.h"
#include <iostream>

using namespace concurrencpp;

int fibonacci_sync(int i) {
    if (i == 0) {
        return 0;
    }

    if (i == 1) {
        return 1;
    }

    return fibonacci_sync(i - 1) + fibonacci_sync(i - 2);
}

result<int> fibonacci(executor_tag, std::shared_ptr<thread_pool_executor> tpe, const int curr) {
    if (curr <= 10) {
        co_return fibonacci_sync(curr);
    }

    auto fib_1 = fibonacci({}, tpe, curr - 1);
    auto fib_2 = fibonacci({}, tpe, curr - 2);

    co_return co_await fib_1 + co_await fib_2;
}

int main() {
    concurrencpp::runtime runtime;
    auto fibb_30 = fibonacci({}, runtime.thread_pool_executor(), 30).get();
    std::cout << "fibonacci(30) = " << fibb_30 << std::endl;
    return 0;
}

In this example, we calculate the 30-th member of the Fibonacci sequence in a parallel manner. We start launching each Fibonacci step in its own parallel coroutine. The first argument is a dummy executor_tag and the second argument is the threadpool executor. Every recursive step invokes a new parallel coroutine that runs in parallel. Each result is co_returned to its parent task and acquired by using co_await.
When we deem the input to be small enough to be calculated synchronously (when curr <= 10), we stop executing each recursive step in its own task and just solve the algorithm synchronously.

To compare, this is how the same code is written without using parallel coroutines, and relying on executor::submit alone. Since fibonacci returns a result<int>, submitting it recursively via executor::submit will result a result<result<int>>.

#include "concurrencpp/concurrencpp.h"
#include <iostream>

using namespace concurrencpp;

int fibonacci_sync(int i) {
    if (i == 0) {
        return 0;
    }

    if (i == 1) {
        return 1;
    }

    return fibonacci_sync(i - 1) + fibonacci_sync(i - 2);
}

result<int> fibonacci(std::shared_ptr<thread_pool_executor> tpe, const int curr) {
    if (curr <= 10) {
        co_return fibonacci_sync(curr);
    }

    auto fib_1 = tpe->submit(fibonacci, tpe, curr - 1);
    auto fib_2 = tpe->submit(fibonacci, tpe, curr - 2);

    co_return co_await co_await fib_1 +
        co_await co_await fib_2;
}

int main() {
    concurrencpp::runtime runtime;
    auto fibb_30 = fibonacci(runtime.thread_pool_executor(), 30).get();
    std::cout << "fibonacci(30) = " << fibb_30 << std::endl;
    return 0;
}

Result-promises

Result objects are the main way to pass data between tasks in concurrencpp and we've seen how executors and coroutines produce such objects. Sometimes we want to use the capabilities of result objects with non-tasks, for example when using a third-party library. In this case, we can complete a result object by using a result_promise. result_promise resembles a std::promise object - applications can manually set the asynchronous result or exception and make the associated result object become ready.

Just like result objects, result-promises are a move only type that becomes empty after move. Similarly, after setting a result or an exception, the result promise becomes empty as well. If a result-promise gets out of scope and no result/exception has been set, the result-promise destructor sets a concurrencpp::errors::broken_task exception using the set_exception method. Suspended and blocked tasks waiting for the associated result object are resumed/unblocked.

Result promises can convert callback style of code into async/await style of code: whenever a component requires a callback to marshal the asynchronous result, we can pass a callback that calls set_result or set_exception (depending on the asynchronous result itself) on the passed result promise, and return the associated result.

result_promise API

template <class type>
class result_promise {    
    /*
        Constructs a valid result_promise.
        Might throw std::bad_alloc if fails to allocate memory.
    */
    result_promise();

    /*
        Moves the content of rhs to *this. After this call, rhs is empty.
    */        
    result_promise(result_promise&& rhs) noexcept;

    /*
        Destroys *this, possibly setting an errors::broken_task exception
        by calling set_exception if *this is not empty at the time of destruction.
    */        
    ~result_promise() noexcept;

    /*
        Moves the content of rhs to *this. After this call, rhs is empty.
    */        
    result_promise& operator = (result_promise&& rhs) noexcept;

    /*
        Returns true if this is a non-empty result-promise.
        Applications must not use this object if this->operator bool() is false.
    */
    explicit operator bool() const noexcept;

    /*
        Sets a value by constructing <<type>> from arguments... in-place.
        Makes the associated result object become ready - tasks waiting for it
        to become ready are unblocked.
        Suspended tasks are resumed inline.
        After this call, *this becomes empty.
        Throws errors::empty_result_promise exception If *this is empty.
        Might throw any exception that the constructor
        of type(std::forward<argument_types>(arguments)...) throws.
    */
    template<class ... argument_types>
    void set_result(argument_types&& ... arguments);
    
    /*
        Sets an exception.
        Makes the associated result object become ready - tasks waiting for it
        to become ready are unblocked.
        Suspended tasks are resumed inline.
        After this call, *this becomes empty.
        Throws errors::empty_result_promise exception If *this is empty.
        Throws std::invalid_argument exception if exception_ptr is null.
    */
    void set_exception(std::exception_ptr exception_ptr);

    /*
        A convenience method that invokes a callable with arguments... and calls set_result
        with the result of the invocation.
        If an exception is thrown, the thrown exception is caught and set instead by calling set_exception.
        After this call, *this becomes empty.
        Throws errors::empty_result_promise exception If *this is empty.
        Might throw any exception that callable(std::forward<argument_types>(arguments)...)
        or the contructor of type(type&&) throw. 
    */
    template<class callable_type, class ... argument_types>
    void set_from_function(callable_type&& callable, argument_types&& ... arguments);
    
    /*
        Gets the associated result object.
        Throws errors::empty_result_promise exception If *this is empty.
        Throws errors::result_already_retrieved exception if this method had been called before.
    */
    result<type> get_result();
};

Example: Marshaling asynchronous result using result_promise:

#include "concurrencpp/concurrencpp.h"

#include <iostream>

int main() {
    concurrencpp::result_promise<std::string> promise;
    auto result = promise.get_result();

    std::thread my_3_party_executor([promise = std::move(promise)] () mutable {
        std::this_thread::sleep_for(std::chrono::seconds(1)); //Imitate real work
        promise.set_result("hello world");
    });

    auto asynchronous_string = result.get();
    std::cout << "result promise returned string: " << asynchronous_string << std::endl;

    my_3_party_executor.join();
}

In this example, We use std::thread as a third-party executor. This represents a scenario when a non-concurrencpp executor is used as part of the application life-cycle. We extract the result object before we pass the promise and block the main thread until the result becomes ready. In my_3_party_executor, we set a result as if we co_returned it.

Shared result objects

Shared results are a special kind of result objects that allow multiple consumers to access the asynchronous result, similar to std::shared_future. Different consumers from different threads can call functions like await, get and resolve in a thread safe manner.

Shared results are built from regular result objects and unlike regular result objects, they are both copyable and movable. As such, shared_result behaves like an std::shared_ptr object. If the shared result was moved to another instance, the shared result is empty, and trying to access it will throw an exception.

In order to support multiple consumers, the shared-result object will return a reference to asynchronous value instead of moving it (like a regular result object). For example, a shared_result<int>will return an int& when get,await etc. are called. If the underlying type of the shared_result is void or a reference type (like int&), they are returned as usual. If the asynchronous result is a thrown-exception, it is re-thrown.

Do note that while acquiring the asynchronous result using shared_result from multiple threads is thread-safe, the actual value might not be. For example, multiple threads can acquire an asynchronous integer by receiving its reference (int&). It does not make the integer itself thread safe. It is alright to mutate the asynchronous value if the asynchronous value is already thread safe. Alternatively, applications are encouraged to use const types to begin with (like const int), and acquire constant-references (like const int&) that prevent mutation.

shared_result API

class share_result {
    /*
        Creates an empty shared-result that isn't associated with any task.
    */
    shared_result() noexcept = default;

    /*
        Destroys the shared-result. Associated tasks are not cancelled.
        The destructor does not block waiting for the asynchronous result to become ready.
    */    
    ~shared_result() noexcept = default;

    /*
        Converts a regular result object to a shared-result object.
        After this call, rhs is empty.
        Might throw std::bad_alloc if fails to allocate memory.
    */
    shared_result(result<type> rhs);

    /*
        Copy constructor. Creates a copy of the shared result object that monitors the same task.
    */
    shared_result(const shared_result&) noexcept = default;
        
    /*
        Move constructor. Moves rhs to *this. After this call, rhs is empty.
    */
    shared_result(shared_result&& rhs) noexcept = default;
        
    /*
        Copy assignment operator. Copies rhs to *this and monitors the same task that rhs monitors.  
    */        
    shared_result& operator=(const shared_result& rhs) noexcept;

    /*
        Move assignment operator. Moves rhs to *this. After this call, rhs is empty.
    */
    shared_result& operator=(shared_result&& rhs) noexcept;

    /*
        Returns true if this is a non-empty shared-result.
        Applications must not use this object if this->operator bool() is false.
    */
    explicit operator bool() const noexcept;

    /*
        Queries the status of *this.
        The return value is any of result_status::idle, result_status::value or result_status::exception.
        Throws errors::empty_result if *this is empty.        
    */
    result_status status() const;

    /*
        Blocks the current thread of execution until this shared-result is ready,
        when status() != result_status::idle.
        Throws errors::empty_result if *this is empty.  
        Might throw std::system_error if one of the underlying synchronization primitives throws.                   
    */
    void wait();

    /*
        Blocks until this shared-result is ready or duration has passed.
        Returns the status of this shared-result after unblocking.
        Throws errors::empty_result if *this is empty.                    
        Might throw std::system_error if one of the underlying synchronization primitives throws.
    */
    template<class duration_type, class ratio_type>
    result_status wait_for(std::chrono::duration<duration_type, ratio_type> duration);

    /*
        Blocks until this shared-result is ready or timeout_time has reached.
        Returns the status of this result after unblocking.
        Throws errors::empty_result if *this is empty.  
        Might throw std::system_error if one of the underlying synchronization primitives throws.
    */
    template<class clock_type, class duration_type>
    result_status wait_until(std::chrono::time_point<clock_type, duration_type> timeout_time);

    /*
        Blocks the current thread of execution until this shared-result is ready,
        when status() != result_status::idle.
        If the result is a valid value, a reference to it is returned,
        otherwise, get rethrows the asynchronous exception.        
        Throws errors::empty_result if *this is empty.
        Might throw std::system_error if one of the underlying synchronization primitives throws.
    */
    std::add_lvalue_reference_t<type> get();

    /*
        Returns an awaitable used to await this shared-result.
        If the shared-result is already ready - the current coroutine resumes
        immediately in the calling thread of execution.
        If the shared-result is not ready yet, the current coroutine is
        suspended and resumed when the asynchronous result is ready,
        by the thread which had set the asynchronous value or exception.
        In either way, after resuming, if the result is a valid value, a reference to it is returned.
        Otherwise, operator co_await rethrows the asynchronous exception.
        Throws errors::empty_result if *this is empty.                            
    */
    auto operator co_await();
  
    /*
        Returns an awaitable used to resolve this shared-result.
        After co_await expression finishes, *this is returned in a non-empty form, in a ready state.
        Throws errors::empty_result if *this is empty.
    */    
    auto resolve();
};

shared_result example

#include "concurrencpp/concurrencpp.h"

#include <iostream>
#include <chrono>

concurrencpp::result<void> consume_shared_result(concurrencpp::shared_result<int> shared_result,
    std::shared_ptr<concurrencpp::executor> resume_executor) {
    std::cout << "Awaiting shared_result to have a value" << std::endl;

    const auto& async_value = co_await shared_result;
    concurrencpp::resume_on(resume_executor);

    std::cout << "In thread id " << std::this_thread::get_id() << ", got: " << async_value << ", memory address: " << &async_value << std::endl;
}

int main() {
    concurrencpp::runtime runtime;
    auto result = runtime.background_executor()->submit([] {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        return 100;
    });

    concurrencpp::shared_result<int> shared_result(std::move(result));
    concurrencpp::result<void> results[8];

    for (size_t i = 0; i < 8; i++) {
        results[i] = consume_shared_result(shared_result, runtime.thread_pool_executor());
    }

    std::cout << "Main thread waiting for all consumers to finish" << std::endl;

    auto tpe = runtime.thread_pool_executor();
    auto all_consumed = concurrencpp::when_all(tpe, std::begin(results), std::end(results)).run();
    all_consumed.get();

    std::cout << "All consumers are done, exiting" << std::endl;
    return 0;
}

Termination in concurrencpp

When the runtime object gets out of scope of main, the application terminates. The runtime iterates each stored executor and calls its shutdown method. Trying to access either the timer-queue or any executor throws errors::runtime_shutdown exception. When an executor shuts down, it clears its inner task queues, destroying un-executed task objects. If a task object stores a concurrencpp-coroutine, that coroutine is resumed inline and an errors::broken_task exception is thrown. In any case where a runtime_shutdown or a broken_task exception is thrown, applications should terminate their current code-flow gracefully as soon as possible. Those exceptions should not be ignored.

Resume executors

Many concurrencpp asynchronous actions will require an executor as their resume executor. When an asynchronous action (implemented as a coroutine) can finish synchronously, it resumes immediately in the calling thread of execution. If the asynchronous action can't finish synchronously, it will be resumed when it finishes, inside the given resume-executor. For example, when_any utility function requires a resume-executor as its first argument. when_any returns a lazy_result which becomes ready when at least one given result becomes ready. If one of the results is already ready at the moment of calling when_any, the calling coroutine is resumed synchronously in the calling thread of execution. If not, the calling coroutine will be resumed when at least of result is finished, inside the given resume-executor. Resume executors are important because they mandate where coroutines are resumed in cases where it's not clear where a coroutine is supposed to be resumed (for example, in the case of when_any and when_all), or in cases where the asynchronous action is processed inside one of the concurrencpp workers, which are only used to process that specific action, and not application code.

Utility functions

make_ready_result function

make_ready_result creates a ready result object from given arguments. Awaiting such result will cause the current coroutine to resume immediately. get and operator co_await will return the constructed value.

/*
    Creates a ready result object by building <<type>> from arguments&&... in-place.
    Might throw any exception that the constructor
    of type(std::forward<argument_types>(arguments)...) throws.
    Might throw std::bad_alloc exception if fails to allocate memory.
*/
template<class type, class ... argument_types>
result<type> make_ready_result(argument_types&& ... arguments);

/*
    An overload for void type.
    Might throw std::bad_alloc exception if fails to allocate memory.
*/
result<void> make_ready_result();

make_exceptional_result function

make_exceptional_result creates a ready result object from a given exception. Awaiting such result will cause the current coroutine to resume immediately. get and operator co_await will re-throw the given exception.

/*
    Creates a ready result object from an exception pointer.
    The returned result object will re-throw exception_ptr when calling get or await.
    Throws std::invalid_argument if exception_ptr is null.
    Might throw std::bad_alloc exception if fails to allocate memory.
*/
template<class type>
result<type> make_exceptional_result(std::exception_ptr exception_ptr);

/*
    Overload. Similar to make_exceptional_result(std::exception_ptr),
    but gets an exception object directly.
    Might throw any exception that the constructor of exception_type(std::move(exception)) might throw. 
    Might throw std::bad_alloc exception if fails to allocate memory.
*/
template<class type, class exception_type>
result<type> make_exceptional_result(exception_type exception);

when_all function

when_all is a utility function that creates a lazy result object which becomes ready when all input results are completed. Awaiting this lazy result returns all input-result objects in a ready state, ready to be consumed.

when_all function comes with three flavors - one that accepts a heterogeneous range of result objects, another that gets a pair of iterators to a range of result objects of the same type, and lastly an overload that accepts no results objects at all. In the case of no input result objects - the function returns a ready result object of an empty tuple.

If one of the passed result-objects is empty, an exception will be thrown. In this case, input-result objects are unaffected by the function and can be used again after the exception was handled. If all input result objects are valid, they are emptied by this function, and returned in a valid and ready state as the output result.
Currently, when_all only accepts result objects.

All overloads accept a resume executor as their first parameter. When awaiting a result returned by when_all, the caller coroutine will be resumed by the given resume executor.

/*
    Creates a result object that becomes ready when all the input results become ready.
    Passed result objects are emptied and returned as a tuple.
    Throws std::invalid_argument if any of the passed result objects is empty.
    Might throw an std::bad_alloc exception if no memory is available.
*/
template<class ... result_types>
lazy_result<std::tuple<typename std::decay<result_types>::type...>>
   when_all(std::shared_ptr<executor_type> resume_executor,
              result_types&& ... results);

/*
    Overload. Similar to when_all(result_types&& ...) but receives a pair of iterators referencing a range.
    Passed result objects are emptied and returned as a vector.
    If begin == end, the function returns immediately with an empty vector.
    Throws std::invalid_argument if any of the passed result objects is empty.
    Might throw an std::bad_alloc exception if no memory is available.
*/
template<class iterator_type>
lazy_result<std::vector<typename std::iterator_traits<iterator_type>::value_type>>
   when_all(std::shared_ptr<executor_type> resume_executor,
               iterator_type begin, iterator_type end);

/*
    Overload. Returns a ready result object that doesn't monitor any asynchronous result.
    Might throw an std::bad_alloc exception if no memory is available.
*/
lazy_result<std::tuple<>> when_all(std::shared_ptr<executor_type> resume_executor);

when_any function

when_any is a utility function that creates a lazy result object which becomes ready when at least one input result is completed. Awaiting this result will return a helper struct containing all input-result objects plus the index of the completed task. It could be that by the time of consuming the ready result, other results might have already completed asynchronously. Applications can call when_any repeatedly in order to consume ready results as they complete until all results are consumed.

when_any function comes with only two flavors - one that accepts a heterogeneous range of result objects and another that gets a pair of iterators to a range of result-objects of the same type. Unlike when_all, there is no meaning in awaiting at least one task to finish when the range of results is completely empty. Hence, there is no overload with no arguments. Also, the overload of two iterators will throw an exception if those iterators reference an empty range (when begin == end).

If one of the passed result-objects is empty, an exception will be thrown. In any case an exception is thrown, input-result objects are unaffected by the function and can be used again after the exception was handled. If all input result objects are valid, they are emptied by this function, and returned in a valid state as the output result.
Currently, when_any only accepts result objects.

All overloads accept a resume executor as their first parameter. When awaiting a result returned by when_any, the caller coroutine will be resumed by the given resume executor.

/*
    Helper struct returned from when_any.
    index is the position of the ready result in results sequence.
    results is either an std::tuple or an std::vector of the results that were passed to when_any.
*/
template <class sequence_type>
struct when_any_result {
    std::size_t index;
    sequence_type results;
};

/*
    Creates a result object that becomes ready when at least one of the input results is ready.
    Passed result objects are emptied and returned as a tuple.
    Throws std::invalid_argument if any of the passed result objects is empty.
    Might throw an std::bad_alloc exception if no memory is available.
*/
template<class ... result_types>
lazy_result<when_any_result<std::tuple<result_types...>>>
   when_any(std::shared_ptr<executor_type> resume_executor,
              result_types&& ... results);

/*
    Overload. Similar to when_any(result_types&& ...) but receives a pair of iterators referencing a range.
    Passed result objects are emptied and returned as a vector.
    Throws std::invalid_argument if begin == end.
    Throws std::invalid_argument if any of the passed result objects is empty.
    Might throw an std::bad_alloc exception if no memory is available.
*/
template<class iterator_type>
lazy_result<when_any_result<std::vector<typename std::iterator_traits<iterator_type>::value_type>>>
   when_any(std::shared_ptr<executor_type> resume_executor,
              iterator_type begin, iterator_type end);

resume_on function

resume_on returns an awaitable that suspends the current coroutine and resumes it inside given executor. This is an important function that makes sure a coroutine is running in the right executor. For example, applications might schedule a background task using the background_executor and await the returned result object. In this case, the awaiting coroutine will be resumed inside the background executor. A call to resume_on with another cpu-bound executor makes sure that cpu-bound lines of code will not run on the background executor once the background task is completed. If a coroutine was re-scheduled to run on another executor using resume_on, but that executor is shut down before it can resume it, that coroutine is resumed and an erros::broken_task exception is thrown. In this case, applications need to quite gracefully.

/*
    Returns an awaitable that suspends the current coroutine and resumes it inside executor.
    Might throw any exception that executor_type::enqueue throws.
*/
template<class executor_type>
auto resume_on(std::shared_ptr<executor_type> executor);

Timers and Timer queues

concurrencpp also provides timers and timer queues. Timers are objects that define asynchronous actions running on an executor within a well-defined interval of time. There are three types of timers - regular timers, onshot-timers and delay objects.

Regular timers have four properties that define them:

  1. Callable - a callable that will be scheduled to run as a task periodically.
  2. Executor - an executor that schedules the callable to run periodically.
  3. Due time - from the time of creation, the interval in milliseconds the timer will be scheduled to run for the first time.
  4. Frequency - from the time the timer was scheduled to run for the first time, the interval in milliseconds the callable will be scheduled to run periodically, until the timer is destructed or cancelled.

Like other objects in concurrencpp, timers are a move only type that can be empty. When a timer is destructed or timer::cancel is called, the timer cancels its scheduled but not yet executed tasks. Ongoing tasks are uneffected. The timer callable must be thread safe. It is recommended to set the due time and the frequency of timers to a granularity of 50 milliseconds.

A timer queue is a concurrencpp worker that manages a collection of timers and processes them in just one thread of execution. It is also the agent used to create new timers. When a timer deadline (whether it is the timer's due-time or frequency) has reached, the timer queue "fires" the timer by scheduling its callable to run on the associated executor as a task.

Just like executors, timer queues also adhere to the RAII concept. When the runtime object gets out of scope, It shuts down the timer queue, cancelling all pending timers. After a timer queue has been shut down, any subsequent call to make_timer, make_onshot_timer and make_delay_object will throw an errors::runtime_shutdown exception. Applications must not try to shut down timer queues by themselves.

timer_queue API:

class timer_queue {
    /*
        Destroys this timer_queue.
    */
    ~timer_queue() noexcept;
    
    /*
        Shuts down this timer_queue:
        Tells the underlying thread of execution to quit and joins it.
        Cancels all pending timers.
        After this call, invocation of any method besides shutdown
        and shutdown_requested will throw an errors::runtime_shutdown.
        If shutdown had been called before, this method has no effect.
    */
    void shutdown() noexcept;

    /*
        Returns true if shutdown had been called before, false otherwise.
    */
    bool shutdown_requested() const noexcept;

    /*
        Creates a new running timer where *this is the associated timer_queue.
        Throws std::invalid_argument if executor is null.
        Throws errors::runtime_shutdown if shutdown had been called before.
        Might throw std::bad_alloc if fails to allocate memory.
        Might throw std::system_error if the one of the underlying synchronization primitives throws.
    */
    template<class callable_type, class ... argumet_types>
    timer make_timer(
        std::chrono::milliseconds due_time,
        std::chrono::milliseconds frequency,
        std::shared_ptr<concurrencpp::executor> executor,
        callable_type&& callable,
        argumet_types&& ... arguments);

    /*
        Creates a new one-shot timer where *this is the associated timer_queue.
        Throws std::invalid_argument if executor is null.
        Throws errors::runtime_shutdown if shutdown had been called before.
        Might throw std::bad_alloc if fails to allocate memory.
        Might throw std::system_error if the one of the underlying synchronization primitives throws.
    */
    template<class callable_type, class ... argumet_types>
    timer make_one_shot_timer(
        std::chrono::milliseconds due_time,
        std::shared_ptr<concurrencpp::executor> executor,
        callable_type&& callable,
        argumet_types&& ... arguments);

    /*
        Creates a new delay object where *this is the associated timer_queue.
        Throws std::invalid_argument if executor is null.
        Throws errors::runtime_shutdown if shutdown had been called before.
        Might throw std::bad_alloc if fails to allocate memory.
        Might throw std::system_error if the one of the underlying synchronization primitives throws.
    */
    result<void> make_delay_object(
        std::chrono::milliseconds due_time,
        std::shared_ptr<concurrencpp::executor> executor);
};

timer API:

class timer {
    /*
        Creates an empty timer.
    */
    timer() noexcept = default;

    /*
        Cancels the timer, if not empty.
    */
    ~timer() noexcept;

    /*
        Moves the content of rhs to *this.
        rhs is empty after this call.
    */
    timer(timer&& rhs) noexcept = default;

    /*
        Moves the content of rhs to *this.
        rhs is empty after this call.
        Returns *this.
    */
    timer& operator = (timer&& rhs) noexcept;

    /*
        Cancels this timer.
        After this call, the associated timer_queue will not schedule *this
        to run again and *this becomes empty.
        Scheduled, but not yet executed tasks are cancelled.
        Ongoing tasks are uneffected.
        This method has no effect if *this is empty or the associated timer_queue has already expired.
        Might throw std::system_error if one of the underlying synchronization primitives throws.
    */
    void cancel();

    /*
        Returns the associated executor of this timer.    
        Throws concurrencpp::errors::empty_timer is *this is empty.
    */
    std::shared_ptr<executor> get_executor() const;

    /*
        Returns the associated timer_queue of this timer.
        Throws concurrencpp::errors::empty_timer is *this is empty.
    */
    std::weak_ptr<timer_queue> get_timer_queue() const;

    /*
        Returns the due time of this timer.
        Throws concurrencpp::errors::empty_timer is *this is empty.
    */
    std::chrono::milliseconds get_due_time() const;

    /*
        Returns the frequency of this timer.    
        Throws concurrencpp::errors::empty_timer is *this is empty.
    */
    std::chrono::milliseconds get_frequency() const;

    /*
        Sets new frequency for this timer.
        Callables already scheduled to run at the time of invocation are not affected.    
        Throws concurrencpp::errors::empty_timer is *this is empty.
    */
    void set_frequency(std::chrono::milliseconds new_frequency);

    /*
        Returns true is *this is not an empty timer, false otherwise.
        The timer should not be used if this->operator bool() is false.
    */
   explicit operator bool() const noexcept;
};

Regular timer example:

#include "concurrencpp/concurrencpp.h"

#include <iostream>

using namespace std::chrono_literals;

int main() {
    concurrencpp::runtime runtime;
    std::atomic_size_t counter = 1;
    concurrencpp::timer timer = runtime.timer_queue()->make_timer(
        1500ms,
        2000ms,
        runtime.thread_pool_executor(),
        [&] {
            const auto c = counter.fetch_add(1);
            std::cout << "timer was invoked for the " << c << "th time" << std::endl;
        });

    std::this_thread::sleep_for(12s);
    return 0;
}

In this example we create a regular timer by using the timer queue. The timer schedules its callable after 1.5 seconds, then fires its callable every 2 seconds. The given callable runs in the threadpool executor.

Oneshot timers

A oneshot timer is a one-time timer with only a due time - after it schedules its callable to run once it never reschedules it to run again.

Oneshot timer example:

#include "concurrencpp/concurrencpp.h"

#include <iostream>

using namespace std::chrono_literals;

int main() {
    concurrencpp::runtime runtime;
    concurrencpp::timer timer = runtime.timer_queue()->make_one_shot_timer(
        3000ms,
        runtime.thread_executor(),
        [&] {
            std::cout << "hello and goodbye" << std::endl;
        });

    std::this_thread::sleep_for(4s);
    return 0;
}

In this example, we create a timer that runs only once - after 3 seconds from its creation, the timer will schedule to run its callable on a new thread of execution (using concurrencpp::thread_executor).

Delay objects

A delay object is a result object that becomes ready when its due time is reached. Applications can co_await this result object to delay the current coroutine in a non-blocking way. The current coroutine is resumed by the executor that was passed to make_delay_object.

Delay object example:

#include "concurrencpp/concurrencpp.h"

#include <iostream>

using namespace std::chrono_literals;

concurrencpp::null_result delayed_task(
    std::shared_ptr<concurrencpp::timer_queue> tq,
    std::shared_ptr<concurrencpp::thread_pool_executor> ex) {
    size_t counter = 1;

    while(true) {
        std::cout << "task was invoked " << counter << " times." << std::endl;
        counter++;

        co_await tq->make_delay_object(1500ms, ex);
    }
}

int main() {
    concurrencpp::runtime runtime;
    delayed_task(runtime.timer_queue(), runtime.thread_pool_executor());

    std::this_thread::sleep_for(10s);
    return 0;
}

In this example, we created a coroutine (that does not marshal any result or thrown exception), which delays itself in a loop by calling co_await on a delay object.

Generators

A generator is a lazy, synchronous coroutine that is able to produce a stream of values to consume. Generators use the co_yield keyword to yield values back to their consumers.

Example:

A generator that yields the n-th member of the Sequence S(n) = 1 + 2 + 3 + ... + n where n <= 100:

concurrencpp::generator<int> sequence() {
    int i = 1;
    int sum = 0;
    while (i <= 100) {
        sum += i;
        ++i;
        co_yield sum;
    }
}

int main() {
    for (auto value : sequence()) {
        std::cout << value << std::end;
    }
    return 0;
} 

Generators are meant to be used synchronously - they can only use the co_yield keyword and must not use the co_await keyword. A generator will continue to produce values as long as the co_yield keyword is called. If the co_return keyword is called (explicitly or implicitly), then the generator will stop producing values. Similarly, if an exception is thrown then the generator will stop producing values and the thrown exception will be re-thrown to the consumer of the generator.

Generators are meant to be used in a range-for loop: Generators implicitly produce two iterators - begin and end which control the execution of the for loop. These iterators should not be handled or accessed manually.

When a generator is created, it starts as a lazy task. When its begin method is called, the generator is resumed for the first time and an iterator is returned. The lazy task is resumed repeatedly by calling operator++ on the returned iterator. The returned iterator will be equal to end iterator when the generator finishes execution either by exiting gracefully or throwing an exception. As mentioned earlier, this happens behind the scenes by the inner mechanism of the loop and the generator, and should not be called directly.

Like other objects in concurrencpp, Generators are a move-only type. After a generator was moved, it is considered empty and trying to access its inner methods (other than operator bool) will throw an exception. The emptiness of a generator should not generally occur - it is advised to consume generators upon their creation in a for loop and not to try to call its methods individually.

generator API

class generator {
    /*
        Move constructor. After this call, rhs is empty.
    */
    generator(generator&& rhs) noexcept;

    /*
        Destructor. Invalidates existing iterators.
    */
    ~generator() noexcept;

    generator(const generator& rhs) = delete;
    generator& operator=(generator&& rhs) = delete;
    generator& operator=(const generator& rhs) = delete;
    
    /*
        Returns true if this generator is not empty.
        Applications must not use this object if this->operator bool() is false.
    */
    explicit operator bool() const noexcept;

    /*
        Starts running this generator and returns an iterator.
        Throws errors::empty_generator if *this is empty.
        Re-throws any exception that is thrown inside the generator code.
    */
    iterator begin();

    /*
        Returns an end iterator.
    */
    static generator_end_iterator end() noexcept;
};

class generator_iterator {
  
    using value_type = std::remove_reference_t<type>;
    using reference = value_type&;
    using pointer = value_type*;
    using iterator_category = std::input_iterator_tag;
    using difference_type = std::ptrdiff_t;

    /*
        Resumes the suspended generator and returns *this.
        Re-throws any exception that was thrown inside the generator code.
    */
    generator_iterator& operator++();

    /*
        Post-increment version of operator++. 
    */
    void operator++(int);

    /*
        Returns the latest value produced by the associated generator.
    */
    reference operator*() const noexcept;
      
    /*
        Returns a pointer to the latest value produced by the associated generator. 
    */
    pointer operator->() const noexcept;

    /*
        Comparision operators. 
    */
    friend bool operator==(const generator_iterator& it0, const generator_iterator& it1) noexcept;
    friend bool operator==(const generator_iterator& it, generator_end_iterator) noexcept;
    friend bool operator==(generator_end_iterator end_it, const generator_iterator& it) noexcept;
    friend bool operator!=(const generator_iterator& it, generator_end_iterator end_it) noexcept;
    friend bool operator!=(generator_end_iterator end_it, const generator_iterator& it) noexcept;
};

The runtime object

The concurrencpp runtime object is the agent used to acquire, store and create new executors.
The runtime must be created as a value type as soon as the main function starts to run. When the concurrencpp runtime gets out of scope, it iterates over its stored executors and shuts them down one by one by calling executor::shutdown. Executors then exit their inner work loop and any subsequent attempt to schedule a new task will throw a concurrencpp::runtime_shutdown exception. The runtime also contains the global timer queue used to create timers and delay objects. Upon destruction, stored executors will destroy unexecuted tasks, and wait for ongoing tasks to finish. If an ongoing task tries to use an executor to spawn new tasks or schedule its own task continuation - an exception will be thrown. In this case, ongoing tasks need to quit as soon as possible, allowing their underlying executors to quit. The timer queue will also be shut down, cancelling all running timers. With this RAII style of code, no tasks can be processed before the creation of the runtime object, and while/after the runtime gets out of scope. This frees concurrent applications from needing to communicate termination messages explicitly. Tasks are free use executors as long as the runtime object is alive.

runtime API

class runtime {
    /*
        Creates a runtime object with default options.    
    */
    runtime();

    /*
        Creates a runtime object with user defined options.
    */
    runtime(const concurrencpp::runtime_options& options);

    /*
        Destroys this runtime object.
        Calls executor::shutdown on each monitored executor.
        Calls timer_queue::shutdown on the global timer queue.
    */
    ~runtime() noexcept;

    /*
        Returns this runtime timer queue used to create new times.
    */
    std::shared_ptr<concurrencpp::timer_queue> timer_queue() const noexcept;

    /*
        Returns this runtime concurrencpp::inline_executor
    */
    std::shared_ptr<concurrencpp::inline_executor> inline_executor() const noexcept;

    /*
        Returns this runtime concurrencpp::thread_pool_executor
    */
    std::shared_ptr<concurrencpp::thread_pool_executor> thread_pool_executor() const noexcept;

    /*
        Returns this runtime concurrencpp::background_executor
    */
    std::shared_ptr<concurrencpp::thread_pool_executor> background_executor() const noexcept;

    /*
        Returns this runtime concurrencpp::thread_executor
    */
    std::shared_ptr<concurrencpp::thread_executor> thread_executor() const noexcept;

    /*
        Creates a new concurrencpp::worker_thread_executor and registers it in this runtime.
        Might throw std::bad_alloc or std::system_error if any underlying memory or system resource could not have been acquired.
    */
    std::shared_ptr<concurrencpp::worker_thread_executor> make_worker_thread_executor();

    /*
        Creates a new concurrencpp::manual_executor and registers it in this runtime.
        Might throw std::bad_alloc or std::system_error if any underlying memory or system resource could not have been acquired.
    */
    std::shared_ptr<concurrencpp::manual_executor> make_manual_executor();

    /*
        Creates a new user defined executor and registers it in this runtime.
        executor_type must be a valid concrete class of concurrencpp::executor.
        Might throw std::bad_alloc if no memory is available.
        Might throw any exception that the constructor of <<executor_type>> might throw.
    */
    template<class executor_type, class ... argument_types>
    std::shared_ptr<executor_type> make_executor(argument_types&& ... arguments);

    /*
        returns the version of concurrencpp that the library was built with.
    */
    static std::tuple<unsigned int, unsigned int, unsigned int> version() noexcept;
};

Creating user-defined executors

As mentioned before, Applications can create their own custom executor type by inheriting the derivable_executor class. There are a few points to consider when implementing user defined executors: The most important thing is to remember that executors are used from multiple threads, so implemented methods must be thread-safe.

New executors can be created using runtime::make_executor. Applications must not create new executors with plain instantiation (such as std::make_shared or plain new), only by using runtime::make_executor. Also, applications must not try to re-instantiate the built-in concurrencpp executors, like the thread_pool_executor or the thread_executor, those executors must only be accessed through their existing instance in the runtime object.

Another important point is to handle shutdown correctly: shutdown, shutdown_requested and enqueue should all monitor the executor state and behave accordingly when invoked:

  • shutdown should tell underlying threads to quit and then join them.
  • shutdown might be called multiple times, and the method must handle this scenario by ignoring any subsequent call to shutdown after the first invocation.
  • enqueue must throw a concurrencpp::errors::runtime_shutdown exception if shutdown had been called before.

task objects

Implementing executors is one of the rare cases applications need to work with concurrencpp::task class directly. concurrencpp::task is a std::function like object, but with a few differences. Like std::function, the task object stores a callable that acts as the asynchronous operation. Unlike std::function, task is a move only type. On invocation, task objects receive no parameters and return void. Moreover, every task object can be invoked only once. After the first invocation, the task object becomes empty. Invoking an empty task object is equivalent to invoking an empty lambda ([]{}), and will not throw any exception. Task objects receive their callable as a forwarding reference (type&& where type is a template parameter), and not by copy (like std::function). Construction of the stored callable happens in-place. This allows task objects to contain callables that are move-only type (like std::unique_ptr and concurrencpp::result). Task objects try to use different methods to optimize the usage of the stored types, for example, task objects apply the short-buffer-optimization (sbo) for regular, small callables, and will inline calls to std::coroutine_handle<void> by calling them directly without virtual dispatch.

task API

  class task {
    /*
        Creates an empty task object.
    */
    task() noexcept;
        
    /*
        Creates a task object by moving the stored callable of rhs to *this.
        If rhs is empty, then *this will also be empty after construction.
        After this call, rhs is empty.
    */
    task(task&& rhs) noexcept;

    /*
        Creates a task object by storing callable in *this.
        <<typename std::decay<callable_type>::type>> will be in-place-
        constructed inside *this by perfect forwarding callable.
    */
    template<class callable_type>
    task(callable_type&& callable);

    /*
        Destroys stored callable, does nothing if empty.
    */
     ~task() noexcept;
    
    /*
        If *this is empty, does nothing.
        Invokes stored callable, and immediately destroys it.
        After this call, *this is empty.
        May throw any exception that the invoked callable may throw.
    */
    void operator()();

    /*
        Moves the stored callable of rhs to *this.
        If rhs is empty, then *this will also be empty after this call.    
        If *this already contains a stored callable, operator = destroys it first.
    */
    task& operator=(task&& rhs) noexcept;

    /*
        If *this is not empty, task::clear destroys the stored callable and empties *this.
        If *this is empty, clear does nothing.
    */
    void clear() noexcept;

    /*
        Returns true if *this stores a callable. false otherwise.
    */
    explicit operator bool() const noexcept;

    /*
        Returns true if *this stores a callable,
        and that stored callable has the same type as <<typename std::decay<callable_type>::type>>  
    */
    template<class callable_type>
    bool contains() const noexcept;

};

When implementing user-defined executors, it is up to the implementation to store tasks (when enqueue is called), and execute them according to the executor inner-mechanism.

Example: using a user-defined executor:

#include "concurrencpp/concurrencpp.h"

#include <iostream>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>

class logging_executor : public concurrencpp::derivable_executor<logging_executor> {

private:
    mutable std::mutex _lock;
    std::queue<concurrencpp::task> _queue;
    std::condition_variable _condition;
    bool _shutdown_requested;
    std::thread _thread;
    const std::string _prefix;

    void work_loop() {
        while (true) {
            std::unique_lock<std::mutex> lock(_lock);
            if (_shutdown_requested) {
                return;
            }

            if (!_queue.empty()) {
                auto task = std::move(_queue.front());
                _queue.pop();
                lock.unlock();
                std::cout << _prefix << " A task is being executed" << std::endl;
                task();
                continue;
            }

            _condition.wait(lock, [this] {
                return !_queue.empty() || _shutdown_requested;
            });
        }
    }

public:
    logging_executor(std::string_view prefix) :
        derivable_executor<logging_executor>("logging_executor"),
        _shutdown_requested(false),
        _prefix(prefix) {
        _thread = std::thread([this] {
            work_loop();
        });
    }

    void enqueue(concurrencpp::task task) override {
        std::cout << _prefix << " A task is being enqueued!" << std::endl;

        std::unique_lock<std::mutex> lock(_lock);
        if (_shutdown_requested) {
            throw concurrencpp::errors::runtime_shutdown("logging executor - executor was shutdown.");
        }

        _queue.emplace(std::move(task));
        _condition.notify_one();
    }

    void enqueue(std::span<concurrencpp::task> tasks) override {
        std::cout << _prefix << tasks.size() << " tasks are being enqueued!" << std::endl;

        std::unique_lock<std::mutex> lock(_lock);
        if (_shutdown_requested) {
            throw concurrencpp::errors::runtime_shutdown("logging executor - executor was shutdown.");
        }

        for (auto& task : tasks) {
            _queue.emplace(std::move(task));
        }

        _condition.notify_one();
    }

    int max_concurrency_level() const noexcept override {
        return 1;
    }

    bool shutdown_requested() const noexcept override {
        std::unique_lock<std::mutex> lock(_lock);
        return _shutdown_requested;
    }

    void shutdown() noexcept override {
        std::cout << _prefix << " shutdown requested" << std::endl;

        std::unique_lock<std::mutex> lock(_lock);
        if (_shutdown_requested) return; //nothing to do.
        _shutdown_requested = true;
        lock.unlock();

        _condition.notify_one();
        _thread.join();
    }
};

int main() {
    concurrencpp::runtime runtime;
    auto logging_ex = runtime.make_executor<logging_executor>("Session #1234");

    for (size_t i = 0; i < 10; i++) {
        logging_ex->post([] {
            std::cout << "hello world" << std::endl;
        });
    }

    std::getchar();
    return 0;
}

In this example, we created an executor which logs actions like enqueuing a task or executing it. We implement the executor interface, and we request the runtime to create and store an instance of it by calling runtime::make_executor. The rest of the application behaves exactly the same as if we were to use non user-defined executors.

Supported platforms and tools

  • Operating systems: Linux, macOS, Windows (Windows 10 and above)
  • Compilers: MSVC (Visual Studio 2019 version 16.8.2 and above), Clang (Clang-11 and above)
  • Tools: CMake (3.16 and above)

Building, installing and testing

Building the library on Windows (release mode)

$ git clone https://github.com/David-Haim/concurrencpp.git
$ cd concurrencpp
$ cmake -S . -B build/lib
$ cmake --build build/lib --config Release

Running the tests on Windows (debug + release mode)

$ git clone https://github.com/David-Haim/concurrencpp.git
$ cd concurrencpp
$ cmake -S test -B build/test
$ cmake --build build/test
    <# for release mode: cmake --build build/test --config Release #>
$ cd build/test
$ ctest . -V -C Debug
    <# for release mode: ctest . -V -C Release #>

Building the library on *nix platforms (release mode)

$ git clone https://github.com/David-Haim/concurrencpp.git
$ cd concurrencpp
$ cmake -DCMAKE_BUILD_TYPE=Release -S . -B build/lib
$ cmake --build build/lib
    #optional, install the library: sudo cmake --install build/lib

Running the tests on *nix platforms

With clang, it is also possible to run the tests with TSAN (thread sanitizer) support.

$ git clone https://github.com/David-Haim/concurrencpp.git
$ cd concurrencpp
$ cmake -S test -B build/test
  #for release mode: cmake -DCMAKE_BUILD_TYPE=Release -S test -B build/test
  #for TSAN mode: cmake -DCMAKE_BUILD_TYPE=Release -DENABLE_THREAD_SANITIZER=Yes -S test -B build/test
$ cmake --build build/test  
$ cd build/test
$ ctest . -V

Via vcpkg on Windows and *nix platforms

Alternatively to building and installing the library manually, developers may get stable releases of concurrencpp as vcpkg packages:

$ vcpkg install concurrencpp

Experimenting with the built-in sandbox

concurrencpp comes with a built-in sandbox program which developers can modify and experiment, without having to install or link the compiled library to a different code-base. In order to play with the sandbox, developers can modify sandbox/main.cpp and compile the application using the following commands:

Building and running the sandbox on Windows:

$ cmake -S sandbox -B build/sandbox
$ cmake --build build/sandbox
    <# for release mode: cmake --build build/sandbox --config Release #>
$ ./build/sandbox <# runs the sandbox>

Building and running the sandbox on *nix platforms:

$ cmake -S sandbox -B build/sandbox
  #for release mode: cmake -DCMAKE_BUILD_TYPE=Release -S sandbox -B build/sandbox
$ cmake --build build/sandbox  
$ ./build/sandbox #runs the sandbox

Author: David-Haim
Source Code: https://github.com/David-Haim/concurrencpp
License: MIT License

#cpluplus 

Ruthie  Bugala

Ruthie Bugala

1651204800

Nbind: Magical Headers That Make Your C++ Library Accessible From JS

nbind is a set of headers that make your C++11 library accessible from JavaScript. With a single #include statement, your C++ compiler generates the necessary bindings without any additional tools. Your library is then usable as a Node.js addon or, if compiled to asm.js with Emscripten, directly in web pages without any plugins.

nbind works with the autogypi dependency management tool, which sets up node-gyp to compile your library without needing any configuration (other than listing your source code file names).

nbind is MIT licensed and based on templates and macros inspired by embind.

Quick start

C++ everywhere in 5 easy steps using Node.js, nbind and autogypi:

Starting pointStep 1 - bindStep 2 - prepare
Original C++ code hello.cc:
#include <string> #include <iostream>   struct Greeter {  static void sayHello(    std::string name  ) {    std::cout      << "Hello, "      << name << "!\n";  } };
List your classes and methods:
// Your original code here   // Add these below it:   #include "nbind/nbind.h"   NBIND_CLASS(Greeter) {  method(sayHello); }
Add scripts to package.json:
{  "scripts": {    "autogypi": "autogypi",    "node-gyp": "node-gyp",    "emcc-path": "emcc-path",    "copyasm": "copyasm",    "ndts": "ndts"  } }
Step 3 - installStep 4 - buildStep 5 - use!
Run on the command line:
npm install --save \  nbind autogypi node-gyp   npm run -- autogypi \  --init-gyp \  -p nbind -s hello.cc
Compile to native binary:
npm run -- node-gyp \  configure buildOr to Asm.js:
npm run -- node-gyp \  configure build \  --asmjs=1
Call from Node.js:
var nbind = require('nbind'); var lib = nbind.init().lib;   lib.Greeter.sayHello('you');Or from a web browser (see below).

The above is all of the required code. Just copy and paste in the mentioned files and prompts or take a shortcut:

git clone https://github.com/charto/nbind-example-minimal.git
cd nbind-example-minimal
npm install && npm test

See it run!

(Note: nbind-example-universal is a better starting point for development)

Requirements

You need:

And one of the following C++ compilers:

  • GCC 4.8 or above.
  • Clang 3.6 or above.
  • Emscripten 1.35.0 or above.
  • Visual Studio 2015 (the Community version is fine).

Features

nbind allows you to:

  • Use your C++ API from JavaScript without any extra effort.
    • From Node.js, Electron and web browsers (using asm.js on Chrome, Firefox and Edge).
    • On Linux, OS X and Windows.
    • Without changes to your C++ code. Simply add a separate short description at the end.
  • Distribute both native code and an asm.js fallback binary.
  • Automatically generate TypeScript .d.ts definition files from C++ code for IDE autocompletion and compile-time checks of JavaScript side code.

In more detail:

  • Export multiple C++ classes, even ones not visible from other files.
  • Export C++ methods simply by mentioning their names.
  • Auto-detect argument and return types from C++ declarations.
  • Automatically convert types and data structures between languages.
  • Call C++ methods from JavaScript with type checking.
  • Pass JavaScript callbacks to C++ and call them with any types.
  • Pass instances of compatible classes by value between languages (through the C++ stack).

The goal is to provide a stable API for binding C++ to JavaScript. All internals related to JavaScript engines are hidden away, and a single API already supports extremely different platforms.

Works on your platform

TargetDevelopment platform
 Linux / OS XWindows
NativeBuild status Build status 
Asm.jsBuild status Tested manually

Roadmap

More is coming! Work is ongoing to:

  • Precompile to a single native library for all versions Node.js and Electron on the same platform
    • Precompiled addons for different Node.js versions for efficiently calling the library will be provided with nbind
  • Support native Android and iPhone apps.

Future 0.x.y versions should remain completely backwards-compatible between matching x and otherwise with minor changes. Breaking changes will be listed in release notes of versions where y equals 0.

Contributing

Please report issues through Github and mention the platform you're targeting (Node.js, asm.js, Electron or something else). Pull requests are very welcome.

Warning: rebase is used within develop and feature branches (but not master).

When developing new features, writing tests first works best. If possible, please try to get them working on both Node.js and asm.js. Otherwise your pull request will get merged to Master only after maintainer(s) have fixed the other platform.

Installing Emscripten to develop for asm.js can be tricky. It will require Python 2.7 and setting paths correctly, please refer to Emscripten documentation. The bin/emcc script in this package is just a wrapper, the actual emcc compiler binary should be in your path.

You can rebuild the asm.js library and run tests as follows:

npm run clean-asm && npm run prepublish && npm run test-asm

User guide

Installing the examples

nbind examples shown in this user guide are also available to download for easier testing as follows:

Extract this zip package or run:

git clone https://github.com/charto/nbind-examples.git

Enter the examples directory and install:

cd nbind-examples
npm install

Creating your project

Once you have all requirements installed, run:

npm init
npm install --save nbind autogypi node-gyp

nbind, autogypi and node-gyp are all needed to compile a native Node.js addon from source when installing it. If you only distribute an asm.js version, you can use --save-dev instead of --save because users won't need to compile it.

Next, to run commands without installing them globally, it's practical to add them in the scripts section of your package.json that npm init just generated. Let's add an install script as well:

  "scripts": {
    "autogypi": "autogypi",
    "node-gyp": "node-gyp",
    "emcc-path": "emcc-path",
    "copyasm": "copyasm",

    "install": "autogypi && node-gyp configure build"
  }

emcc-path is needed internally by nbind when compiling for asm.js. It fixes some command line options that node-gypi generates on OS X and the Emscripten compiler doesn't like. You can leave it out if only compiling native addons.

The install script runs when anyone installs your package. It calls autogypi and then uses node-gyp to compile a native addon.

autogypi uses npm package information to set correct include paths for C/C++ compilers. It's needed when distributing addons on npm so the compiler can find header files from the nbind and nan packages installed on the user's machine. Initialize it like this:

npm run -- autogypi --init-gyp -p nbind -s hello.cc

Replace hello.cc with the name of your C++ source file. You can add multiple -s options, one for each source file.

The -p nbind means the C++ code uses nbind. Multiple -p options can be added to add any other packages compatible with autogypi.

The --init-gyp command generates files binding.gyp and autogypi.json that you should distribute with your package, so that autogypi and node-gyp will know what to do when the install script runs.

Now you're ready to start writing code and compiling.

Configuration

Refer to autogypi documentation to set up dependencies of your package, and how other packages should include it if it's a library usable directly from C++.

--asmjs=1 is the only existing configuration option for nbind itself. You pass it to node-gyp by calling it like node-gyp configure build --asmjs=1. It compiles your package using Emscripten instead of your default C++ compiler and produces asm.js output.

Calling from Node.js

First nbind needs to be initialized by calling nbind.init which takes the following optional arguments:

  • Base path under which to look for compiled binaries. Default is process.cwd() and __dirname is a good alternative.
  • Binary code exports object. Any classes from C++ API exported using nbind will be added as members. Default is an empty object. Any existing options will be seen by asm.js code and can be used to configure Emscripten output. Must follow base path (which may be set to null or undefined).
  • Node-style callback with 2 parameters:
    • Error if present, otherwise null.
    • Binary code exports object containing C++ classes.

nbind can be initialized synchronously on Node.js and asynchronously on browsers and Node.js. Purely synchronous is easier but not as future-proof:

var nbind = require('nbind');
var lib = nbind.init().lib;

// Use the library.

Using a callback also supports asynchronous initialization:

var nbind = require('nbind');

nbind.init(function(err, binding) {
  var lib = binding.lib;

  // Use the library.
});

The callback passed to init currently gets called synchronously in Node.js and asynchronously in browsers. To avoid releasing zalgo you can for example wrap the call in a bluebird promise:

var bluebird = require('bluebird');
var nbind = require('nbind');

bluebird.promisify(nbind.init)().then(function(binding) {
  var lib = binding.lib;

  // Use the library.
});

Using nbind headers

There are two possible files to include:

  • nbind/api.h for using types from the nbind namespace such as JavaScript callbacks inside your C++ code.
    • #include before your own class definitions.
    • Causes your code to depend on nbind.
  • nbind/nbind.h for exposing your C++ API to JavaScript.
    • #include after your own class definitions to avoid accidentally invoking its macros.
    • The header automatically hides itself if not targeting Node.js or asm.js.
    • Safe to use in any projects.

Use #include "nbind/nbind.h" at the end of your source file with only the bindings after it. The header defines macros with names like construct and method that may otherwise break your code or conflict with other headers.

It's OK to include nbind/nbind.h also when not targeting any JavaScript environment. node-gyp defines a BUILDING_NODE_EXTENSION macro and Emscripten defines an EMSCRIPTEN macro so when those are undefined, the include file does nothing.

Use #include "nbind/api.h" in your header files to use types in the nbind namespace if you need to report errors without throwing exceptions, or want to pass around callbacks or objects.

You can use an #ifdef NBIND_CLASS guard to skip your nbind export definitions when the headers weren't loaded.

Example that uses an nbind callback in C++ code:

1-headers.cc

#include <string>
#include <iostream>

// For nbind::cbFunction type.
#include "nbind/api.h"

class HeaderExample {

public:

  static void callJS(nbind::cbFunction &callback) {
    std::cout << "JS says: " << callback.call<std::string>(1, 2, 3);
  }

};

// For NBIND_CLASS() and method() macros.
#include "nbind/nbind.h"

#ifdef NBIND_CLASS

NBIND_CLASS(HeaderExample) {
  method(callJS);
}

#endif

Example used from JavaScript:

1-headers.js

var nbind = require('nbind');

var lib = nbind.init().lib;

lib.HeaderExample.callJS(function(a, b, c) {
  return('sum = ' + (a + b + c) + '\n');
});

Run the example with node 1-headers.js after installing. It prints:

JS says: sum = 6

Functions

Functions not belonging to any class are exported inside an NBIND_GLOBAL block with a macro call function(functionName); which takes the name of the function as an argument (without any quotation marks). The C++ function gets exported to JavaScript with the same name, or it can be renamed by adding a second argument (with quotation marks): function(cppFunctionName, "jsExportedName");

If the C++ function is overloaded, multifunction macro must be used instead. See overloaded functions.

Note: you cannot put several function(...); calls on the same line! Otherwise you'll get an error about redefining a symbol.

Example:

6-functions.cc

#include <iostream>

void sayHello(std::string name) {
  std::cout << "Hello, " << name << "!\n";
}

#include "nbind/nbind.h"

NBIND_GLOBAL() {
  function(sayHello);
}

Example used from JavaScript:

6-functions.js

var nbind = require('nbind');
var lib = nbind.init().lib;

lib.sayHello('you');

Classes and constructors

The NBIND_CLASS(className) macro takes the name of your C++ class as an argument (without any quotation marks), and exports it to JavaScript using the same name. It's followed by a curly brace enclosed block of method exports, as if it was a function definition.

The class can be renamed on the JavaScript side by passing a string as a second argument. This is especially useful for binding a template class specialization with a more reasonable name: NBIND_CLASS(Data<int>, "IntData")

Constructors are exported with a macro call construct<types...>(); where types is a comma-separated list of arguments to the constructor, such as int, int. Calling construct multiple times allows overloading it, but each overload must have a different number of arguments.

Constructor arguments are the only types that nbind cannot detect automatically.

Example with different constructor argument counts and types:

2-classes.cc

#include <iostream>

class ClassExample {

public:

  ClassExample() {
    std::cout << "No arguments\n";
  }
  ClassExample(int a, int b) {
    std::cout << "Ints: " << a << " " << b << "\n";
  }
  ClassExample(const char *msg) {
    std::cout << "String: " << msg << "\n";
  }

};

#include "nbind/nbind.h"

NBIND_CLASS(ClassExample) {
  construct<>();
  construct<int, int>();
  construct<const char *>();
}

Example used from JavaScript:

2-classes.js

var nbind = require('nbind');

var lib = nbind.init().lib;

var a = new lib.ClassExample();
var b = new lib.ClassExample(42, 54);
var c = new lib.ClassExample("Don't panic");

Run the example with node 2-classes.js after installing. It prints:

No arguments
Ints: 42 54
String: Don't panic

Inheritance

When a C++ class inherits another, the inherit macro can be used to allow calling parent class methods on the child class, or passing child class instances to C++ methods expecting parent class instances.

Internally JavaScript only has prototype-based single inheritance while C++ supports multiple inheritance. To simulate it, nbind will use one parent class as the child class prototype, and copy the contents of the other parents to the prototype. This has otherwise the same effect, except the JavaScript instanceof operator will return true for only one of the parent classes.

Example:

NBIND_CLASS(Child) {
    inherit(FirstParent);
    inherit(SecondParent);
}

Methods and properties

Methods are exported inside an NBIND_CLASS block with a macro call method(methodName); which takes the name of the method as an argument (without any quotation marks). The C++ method gets exported to JavaScript with the same name.

If the C++ method is overloaded, multimethod macro must be used instead. See overloaded functions.

Properties should be accessed through getter and setter functions.

Data types of method arguments and its return value are detected automatically so you don't have to specify them. Note the supported data types because using other types may cause compiler errors that are difficult to understand.

If the method is static, it becomes a property of the JavaScript constructor function and can be accessed like className.methodName(). Otherwise it becomes a property of the prototype and can be accessed like obj = new className(); obj.methodName();

Example with a method that counts a cumulative checksum of ASCII character values in strings, and a static method that processes an entire array of strings:

3-methods.cc

#include <string>
#include <vector>

class MethodExample {

public:

  unsigned int add(std::string part) {
    for(char &c : part) sum += c;

    return(sum);
  }

  static std::vector<unsigned int> check(std::vector<std::string> list) {
    std::vector<unsigned int> result;
    MethodExample example;

    for(auto &&part : list) result.push_back(example.add(part));

    return(result);
  }

  unsigned int sum = 0;

};

#include "nbind/nbind.h"

NBIND_CLASS(MethodExample) {
  construct<>();

  method(add);
  method(check);
}

Example used from JavaScript, first calling a method in a loop from JS and then a static method returning an array:

3-methods.js

var nbind = require('nbind');

var lib = nbind.init().lib;

var parts = ['foo', 'bar', 'quux'];

var checker = new lib.MethodExample();

console.log(parts.map(function(part) {
  return(checker.add(part));
}));

console.log(lib.MethodExample.check(parts));

Run the example with node 3-methods.js after installing. It prints:

[ 324, 633, 1100 ]
[ 324, 633, 1100 ]

The example serves to illustrate passing data. In practice, such simple calculations are faster to do in JavaScript rather than calling across languages because copying data is quite expensive.

Overloaded functions

The function() and method() macroes cannot distinguish between several overloaded versions of the same function or method, causing an error. In this case the multifunction() and multimethod() macroes must be used.

Their second parameter is a list of argument types wrapped in an args() macro to select a single overloaded version.

For example consider an overloaded method:

void test(unsigned int x) const;
void test(unsigned int x, unsigned int y) const;

In bindings, one of the versions needs to be explicitly selected. The second of the two would be referenced like:

multimethod(test, args(unsigned int, unsigned int));

As always, the return type and method constness are autodetected.

For calling from JavaScript, additionally each overload needs to have a distinct name. For renaming an overload JavaScript will see, the binding code is like:

multimethod(test, args(unsigned int, unsigned int), "test2");

You can then write a JavaScript wrapper to inspect arguments and select which overload to call. The reason for this is, that nbind binds a JavaScript property to a single C++ function pointer, which wraps one overloaded version of the function with type conversion code.

Otherwise, it would need to generate a new C++ function that also checks the arguments. This would result in a larger native binary without any speed advantage.

Getters and setters

Property getters are exported inside an NBIND_CLASS block with a macro call getter(getterName) with the name of the getter method as an argument. nbind automatically strips a get/Get/get_/Get_ prefix and converts the next letter to lowercase, so for example getX and get_x both would become getters of x to be accessed like obj.x

Property setters are exported together with getters using a macro call getset(getterName, setterName) which works much like getter(getterName) above. Both getterName and setterName are mangled individually so you can pair getX with set_x if you like. From JavaScript, ++obj.x would then call both of them to read and change the property.

Example class and property with a getter and setter:

4-getset.cc

class GetSetExample {

public:

  void setValue(int value) { this->value = value; }
  int getValue() { return(value); }

private:

  int value = 42;

};

#include "nbind/nbind.h"

NBIND_CLASS(GetSetExample) {
  construct<>();

  getset(getValue, setValue);
}

Example used from JavaScript:

4-getset.js

var nbind = require('nbind');

var lib = nbind.init().lib;

var obj = new lib.GetSetExample();

console.log(obj.value++); // 42
console.log(obj.value++); // 43

Run the example with node 4-getset.js after installing.

Passing data structures

nbind supports automatically converting between JavaScript arrays and C++ std::vector or std::array types. Just use them as arguments or return values in C++ methods.

Note that data structures don't use the same memory layout in both languages, so the data always gets copied which takes more time for more data. For example the strings in an array of strings also get copied, one character at a time. In asm.js data is copied twice, first to a temporary space using a common format both languages can read and write.

Callbacks

Callbacks can be passed to C++ methods by simply adding an argument of type nbind::cbFunction & to their declaration.

They can be called with any number of any supported types without having to declare in any way what they accept. The JavaScript code will receive the parameters as JavaScript variables to do with them as it pleases.

A callback argument arg can be called like arg("foobar", 42); in which case the return value is ignored. If the return value is needed, the callback must be called like arg.call<type>("foobar", 42); where type is the desired C++ type that the return value should be converted to. This is because the C++ compiler cannot otherwise know what the callback might return.

Warning: while callbacks are currently passed by reference, they're freed after the called C++ function returns! That's intended for synchronous functions like Array.map which calls a callback zero or more times and then returns. For asynchronous functions like setTimeout which calls the callback after it has returned, you need to copy the argument to a new nbind::cbFunction and store it somewhere.

Using objects

C++ objects can be passed to and from JavaScript using different parameter and return types in C++ code:

  • by reference using pointers or references (optionally const)
  • by value

Note: currently passing objects by pointer on Node.js requires the class to have a "copy constructor" initializing itself from a pointer. This will probably be fixed later.

Returned pointers and references can be const, in which case calling their non-const methods or passing them as non-const parameters will throw an error. This prevents causing undefined behaviour corresponding to C++ code that wouldn't even compile.

Using pointers and references is particularly:

  • dangerous because the pointer may become invalid without JavaScript noticing it.
  • annoying in asm.js because browsers give no access to the garbage collector, so memory may leak when pointers become garbage without C++ noticing it. Smart pointers are not supported until a workaround for this is implemented.

Passing data by value using value objects solves both issues. They're based on a toJS function on the C++ side and a fromJS function on the JavaScript side. Both receive a callback as an argument, and calling it with any parameters calls the constructor of the equivalent type in the other language.

The callback on the C++ side is of type nbind::cbOutput. Value objects are passed through the C++ stack to and from the exported function. nbind uses C++11 move semantics to avoid creating some additional copies on the way.

The equivalent JavaScript constructor must be registered on the JavaScript side by calling binding.bind('CppClassName', JSClassName) so that nbind knows which types to translate between each other.

Example with a class Coord used as a value object, and a class ObjectExample which uses objects passed by values and references:

5-objects.cc

#include <iostream>

#include "nbind/api.h"

class Coord {

public:

  Coord(signed int x = 0, signed int y = 0) : x(x), y(y) {}
  explicit Coord(const Coord *other) : x(other->x), y(other->y) {}

  void toJS(nbind::cbOutput output) {
    output(x, y);
  }

  signed int getX() { std::cout << "Get X\n"; return(x); }
  signed int getY() { std::cout << "Get Y\n"; return(y); }

  void setX(signed int x) { this->x = x; }
  void setY(signed int y) { this->y = y; }

  signed int x, y;

};

class ObjectExample {

public:

  static void showByValue(Coord coord) {
    std::cout << "C++ value " << coord.x << ", " << coord.y << "\n";
  }

  static void showByRef(Coord *coord) {
    std::cout << "C++ ref " << coord->x << ", " << coord->y << "\n";
  }

  static Coord getValue() {
    return(Coord(12, 34));
  }

  static Coord *getRef() {
    static Coord coord(56, 78);
    return(&coord);
  }

};

#include "nbind/nbind.h"

NBIND_CLASS(Coord) {
  construct<>();
  construct<const Coord *>();
  construct<signed int, signed int>();

  getset(getX, setX);
  getset(getY, setY);
}

NBIND_CLASS(ObjectExample) {
  method(showByValue);
  method(showByRef);
  method(getValue);
  method(getRef);
}

Example used from JavaScript:

5-objects.js

var nbind = require('nbind');

var binding = nbind.init();
var lib = binding.lib;

function Coord(x, y) {
  this.x = x;
  this.y = y;
}

Coord.prototype.fromJS = function(output) {
  output(this.x, this.y);
}

Coord.prototype.show = function() {
  console.log('JS value ' + this.x + ', ' + this.y);
}

binding.bind('Coord', Coord);

var value1 = new Coord(123, 456);
var value2 = lib.ObjectExample.getValue();
var ref = lib.ObjectExample.getRef();

lib.ObjectExample.showByValue(value1);
lib.ObjectExample.showByValue(value2);
value1.show();
value2.show();

lib.ObjectExample.showByRef(ref);
console.log('JS ref ' + ref.x + ', ' + ref.y);

Run the example with node 5-objects.js after installing. It prints:

C++ value 123, 456
C++ value 12, 34
JS value 123, 456
JS value 12, 34
C++ ref 56, 78
Get X
Get Y
JS ref 56, 78

Type conversion

Parameters and return values of function calls between languages are automatically converted between equivalent types:

JavaScriptC++
number(un)signed char, short, int, long
numberfloat, double
number or bignum(un)signed long, long long
booleanbool
stringconst (unsigned) char *
stringstd::string
Arraystd::vector<type>
Arraystd::array<type, size>
Functionnbind::cbFunction
(only as a parameter)
See Callbacks
nbind-wrapped pointerPointer or reference to an
instance of any bound class
See Using objects
Instance of any prototype
(with a fromJS method)
Instance of any bound class
(with a toJS method)
See Using objects
ArrayBuffer(View), Int*Array
or Buffer
nbind::Buffer struct
(data pointer and length)
See Buffers

Type conversion is customizable by passing policies as additional arguments to construct, function or method inside an NBIND_CLASS or NBIND_GLOBAL block. Currently supported policies are:

  • nbind::Nullable() allows passing null as an argument when a C++ class instance is expected. The C++ function will then receive a nullptr.
  • nbind::Strict() enables stricter type checking. Normally anything in JavaScript can be converted to number, string or boolean when expected by a C++ function. This policy requires passing the exact JavaScript type instead.

Type conversion policies are listed after the method or function names, for example:

NBIND_CLASS(Reference) {
    method(reticulateSplines, "reticulate", nbind::Nullable());
    method(printString, nbind::Strict());
}

Buffers

Transferring large chunks of data between languages is fastest using typed arrays or Node.js buffers in JavaScript. Both are accessible from C++ as plain blocks of memory if passed in through the nbind::Buffer data type which has the methods:

  • data() returns an unsigned char * pointing to a block of memory also seen by JavaScript.
  • length() returns the length of the block in bytes.
  • commit() copies data from C++ back to JavaScript (only needed with Emscripten).

This is especially useful for passing canvas.getContext('2d').getImageData(...).data to C++ and drawing to an on-screen bitmap when targeting Emscripten or Electron.

Example:

#include "nbind/api.h"

void range(nbind::Buffer buf) {
  size_t length = buf.length();
  unsigned char *data = buf.data();

  if(!data || !length) return;

  for(size_t pos = 0; pos < length; ++pos) {
    data[pos] = pos;
  }

  buf.commit();
}

#include "nbind/nbind.h"

NBIND_GLOBAL() {
  function(range);
}

Example used from JavaScript:

var nbind = require('nbind');
var lib = nbind.init().lib;

var data = new Uint8Array(16);
lib.range(data);

console.log(data.join(' '));

It prints:

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

64-bit integers

Normally C++ 64-bit integer types are first converted to double and then to JavaScript number which can only hold 53 bits of precision, but it's possible to preserve all bits by using a bignum class. It should have a constructor taking the following arguments:

  • Integer containing 32 bits from the least important half.
  • Integer containing 32 bits from the most important half.
  • Boolean, true if the number is negative.

It should also have a fromJS function which takes a callback, and calls it with those same arguments to pass the data back to C++ when needed.

An example implementation also capable of printing 64-bit numbers to strings in bases 2, 4, 10 and 16 is included.

Error handling

You can use the NBIND_ERR("message here"); macro to report an error before returning from C++ (#include "nbind/api.h" first). It will be thrown as an error on the JavaScript side (C++ environments like Emscripten may not support throwing exceptions, but the JavaScript side will).

Publishing on npm

Make sure your package.json file has at least the required emcc-path and install scripts:

  "scripts": {
    "emcc-path": "emcc-path",

    "install": "autogypi && node-gyp configure build"
  }

The dependencies section should have at least:

  "dependencies": {
    "autogypi": "^0.2.2",
    "nbind": "^0.2.1",
    "node-gyp": "^3.3.1"
  }

Your package should also include binding.gyp and autogypi.json files.

Shipping an asm.js fallback

nbind-example-universal is a good minimal example of compiling a native Node.js addon if possible, and otherwise using a pre-compiled asm.js version.

It has two temporary build directories build/native and build/asmjs, for compiling both versions. nbind provides a binary copyasm that can then be used to copy the compiled asm.js library into a nicer location for publishing inside the final npm package.

Note that the native version should be compiled in the install script so it runs for all users of the package, and the asm.js version should be compiled in the prepublish script so it gets packaged in npm for usage without the Emscripten compiler. See the example package.json file.

Using in web browsers

nbind-example-universal is a good minimal example also of calling compiled asm.js code from inside web browsers. The simplest way to get nbind working is to add these scripts in your HTML code as seen in the example index.html:

<script src="nbind.js"></script>

<script>
  nbind.init(function(err, binding) {
    var lib = binding.lib;

    // Use the library.
  });
</script>

Make sure to fix the path to nbind.js on the first line if necessary.

Using with TypeScript

nbind has a fully typed API for interacting with C++ code and it can also automatically generate .d.ts files for your C++ classes and functions. This gives you effortless bindings with compile time type checking for calls from JavaScript to Node.js addons and asm.js modules.

All you have to do is compile your C++ code and run the included ndts tool to create the type definitions:

npm run -- node-gyp configure build
npm run -s -- ndts . > lib-types.d.ts

When run in this way, the first argument of ndts is a path from the package root to the binding.gyp file. Typically the file is in the root so the correct path is .

Now you can load the C++ code from TypeScript in three different ways. First import nbind (which also loads the C++ code) and types generated by ndts:

import * as nbind from 'nbind';
import * as LibTypes from './lib-types';

Then choose your favorite way to initialize it:

Purely synchronous:

const lib = nbind.init<typeof LibTypes>().lib;

// Use the library.

Asynchronous-aware:

nbind.init((err: any, binding: nbind.Binding<typeof LibTypes>) => {
  const lib = binding.lib;

  // Use the library.
});

Promise-based:

import * as bluebird from 'bluebird';

bluebird.promisify(nbind.init)().then((binding: nbind.Binding<typeof LibTypes>) => {
  const lib = binding.lib;

  // Use the library.
});

Note how there is a type argument <typeof LibTypes> for the init call in all of the examples. It defines types of binding.lib contents, which coming from C++ are otherwise unknown to the TypeScript compiler. You can import the types from a file generated by ndts or just use <any> to disable typing.

For example if you have a C++ class:

struct C : public A, public B {
    A *getA();

    static uint32_t reticulate();
};

And bind it like:

NBIND_CLASS(C) {
    inherit(A);
    inherit(B);

    construct<>();

    method(reticulate);

    getter(getA);
}

ndts will generate the following typings:

export interface _C extends A, B {}
export var _C: { new(): _C };

export class C extends _C {
    /** C(); */
    constructor();

    /** static uint32_t reticulate(); */
    static reticulate(): number;

    /** A * a; -- Read-only */
    a: A;
}

The additional interface _C is generated in this case to support multiple inheritance, because C extends both A and B.

All the tests are written in TypeScript so if you run:

git clone https://github.com/charto/nbind.git
cd nbind
npm install
npm test

You can then open test/test.ts in a TypeScript IDE and see the generated typings in action.

Binding plain C

nbind generates bindings using C++ templates for compile-time introspection of argument and return types of functions and methods.

Since plain C doesn't have templates, there's no standard way to have a C compiler generate new wrapper code for type conversion and output type information available at run-time.

The easiest way to use nbind with C is to write a C++ wrapper calling the C code, and use nbind with that.

Mapping idiomatic C to JavaScript classes may require some manual work, since it's common to reinvent new ways to do object-oriented programming, usually by using structs as classes and simulating methods by passing struct pointers to functions. C++ classes and methods should be used for these.

A good example is libui-node which uses nbind to generate bindings for libui, mainly a C library.

Binding external libraries

If you have external library source code, you should compile it separately into a library first, and then link your Node.js addon with it. If the library has an installation script and the addon is only intended for your own use or other users are willing to do some extra steps, it's easiest to install the library globally first.

For best user experience, libui-node is an example of distributing an external library together with your package.

For creating the actual bindings, see for example this and this message and a tutorial for getting the vg library working.

Debugging

In the browser it can be difficult to stop and debug at the correct spot in optimized C++ code. nbind provides an _nbind_debug() function in api.h that you can call from C++ to invoke the browser's debugger when using asm.js.

For debugging a Node.js addon, if you would normally test it like node test.js, you can instead use gdb node and type run test.js in the GDB prompt. Then in case of a crash, it will show where it happened, inspect the stack etc.

You should also modify nbind.gypi (inside nbind's src directory) and possibly your own binding.gyp, to remove any -O? flags and instead add a -g flag, then remove the build directory and recompile. This allows GDB to show much more information.

Alternatives

Very similar:

Less similar:

Authors

  • Juha Järvi, befunge

Author: charto
Source Code: https://github.com/charto/nbind
License: MIT License
#cpluplus #c