Skip to content

Easy Asynchronous Programming with cppcomponents_libuv

lab-notes edited this page Oct 12, 2013 · 48 revisions

Easy Asynchronous C++ Programming

Introduction

Have you heard of asynchronous programming, and are looking at easy ways to do it in C++? Have you heard C# programmers bragging about how async and await make asynchronous programming so easy? How about Go programmers bragging about how goroutines and channels? Did you wish C++ provided tools like this? that were available now? with a permissive open source license? If you answered yes to any of the questions above, you will be interested in cppcomponents_libuv.

Using the cppcomponents_libuv library you can do the following

  1. Asynchronously access files, create servers, connect and communicate with servers, start timers
  2. Easily run code in a thread pool
  3. Create resumable functions that allow you to await the results of an asynchronous operation instead of using a callback (inspired by C#)
  4. Create channels (inspired by Go) to communicate with running functions

Who is this article for?

This article assumes the following

  1. You are comfortable with C++11 and modern C++ idioms
  2. You have at least heard of node.js and/or its underlying library libuv
  3. You have at least heard of Go and Channels
  4. You have access to either a Windows x86 or x64 computer or Linux x64 computer for running the samples
  5. You can install either Visual C++ 2013 RC or Mingw GCC with threading support on Windows, or GCC 4.7.3 or higher or Clang 3.2 or higher if on Linux.
  6. If you have a Mac, please contact me. The software is written to be cross-platform and the only reason there is not a Mac port is that I do not have a Mac to compile and test to use to compile and test.

Quick Start

This will guide you through the process of downloading and setting up the library and compiling and running the sample program

  1. Go to https://github.com/jbandela/cppcomponents_libuv/releases and download cppcomponents_libuv_starter.zip or cppcomponents_libuv_starter.tar.gz from the latest release. (Both files have the same content, pick the format you prefer to work with), and extract them to a directory. I will refer to that directory as cppcomponents_libuv_starter
  2. cd into cppcomponents_libuv_starter 3.ls or dir and you should see 3 directories: binaries,example, and include
  3. cd into example 5a. On Linux enter g++ -std=c++11 -ldl -pthread -I ../include/ -O3 main.cpp. If you do not have enough memory you may get a memory exhausted error or a compiler crash. If that is the case, try using clang++ (3.2 or higher) as clang tends to use less memory. The command line will be the same except for clang++ instead of g++ 5b. On Windows, make sure cl is in your path by running vcvarsall.bat and then enter cl /EHsc /bigobj /wd"4503" /I ..\include main.cpp 6a. On Windows copy the files from cppcomponents_libuv_starter\Windows_x86\ to cppcomponents_libuv_starter\example. If you compiled with the x64 compiler, copy from Windows_x64. You should now have 2 .dll files in the example directory 6b. On Linux copy the files from cppcomponents_libuv_starter\Linux_x64\ to cppcomponents_libuv_starter\example. You should now have 2 .sofiles in the example directory
  4. Run the program by typing ./a.out on Linux or main.exe on Windows
  5. Play with the program by typing Sha1Calculate with a filename to calculate the SHA1 digest. Try it out on a large file. If it does not display the digest immediately, type Sha1Status repeatedly to follow the status. Also try EchoStart 7777 (or use a port number of your choice) to start an http echo server on that port. Try connecting to http://localhost:7777 in a browser to see the output. Type Timer 5000 to start a Timer for 5 seconds. Notice how all of these commands are non-blocking.
  6. Type Quit to quit the program

Background

This library used libuv and cppcomponents to work its magic. libuv is the underlying platform abstraction library for node.js. It is written in C and uses function pointer callbacks to allow asynchronous programming. An invaluable resource for learning more about libuv is http://nikhilm.github.io/uvbook/. cppcomponents is a header-only C++ library that allows you to write C++ code that can be easily used from different compilers. Previously, you either had to build a library for each compiler (and many times each version of the compiler) or else write a C interface to your C++ code. With cppcomponents, you can build a .dll or .so file that can be used with any C++11 compiler on that platform.

A Note on Naming Conventions

In cppcomponents names of Interfaces, runtime classes, interfaces methods, are all UpperCamelCase. utility functions that are not the above are c_style_lower_case. In cppcomponents_libuv, all classes, interfaces, methods, are CamelCase. The naming of the methods attempts to follow libuv except CamelCase is used instead of an underscore.

Tutorial

Event Loop

Asynchronous programming can be poorly summed up as "don't call us, we'll call you." It is the responsibility of the event loop to call you. Here is how you set up an event loop.

#include <cppcomponents_libuv/cppcomponents_libuv.hpp>

int main(){
    cppcomponents_libuv::Uv::DefaultExecutor().Loop();
}

Save the file as event_loop.cpp and compile that file just like you did main.cpp previously. Make sure you still have the .dlls or .so files from binaries in the same directory and run the resulting program. The program does nothing. This is because we need to tell the event loop to stop.

A note on the program listing. The include file just includes the header that we need for the library. The cppcomponents_libuv library is in namespace cppcomponents_libuv. DefaultExecutor() is a static function of class Uv that returns the default event loop executor. The returned executor is a model of the executors proposed for C++ standardization in http://isocpp.org/files/papers/n3562.pdf. (Note whereas the paper uses lower_case for the member functions, cppcomponents_libuv uses UpperCamelCase.) The semantics try to be the same. The event loop encapsulates running a libuv uv_looop_t event loop along with providing a way to add functions to the event loop. We will look at more of this later.

Exiting the event loop

Our program just keeps on running. We need a way to exit the program. The next listing demonstrates how to exit the program. This involves adding a function to the DefaultExecutor queue that will exit the event loop.

#include <cppcomponents_libuv/cppcomponents_libuv.hpp>

int main(){
    auto exiter = [](){cppcomponents_libuv::Uv::DefaultExecutor().MakeLoopExit();};
    cppcomponents_libuv::Uv::DefaultExecutor().Add(exiter);
    cppcomponents_libuv::Uv::DefaultExecutor().Loop();
}

Although the program does not do anything now except exit, it introduces some important concepts. First we define a lambda that calls DefaultExecutor().MakeLoopExit(). MakeLoopExit will stop the running loop. If you call it while the loop is not running, it will have no effect. After defining the lambda exiter, we then tell the DefaultExecutor that we want to execute it. Note that adding, does not execute it immediately. When the event loop is running, it will then be executed.

Futures and output

Our above program is pretty boring. It does not even do output. Let us make a hello world program.

#include <cppcomponents_libuv/cppcomponents_libuv.hpp>

using cppcomponents::Future;

int main(){
    cppcomponents_libuv::Tty out{1,false};
    Future<int> output_fut = out.Write("Hello world\n");
    output_fut.Then(
        [](Future<int> f){
            cppcomponents_libuv::Uv::DefaultExecutor().MakeLoopExit();
        }
     );
    cppcomponents_libuv::Uv::DefaultExecutor().Loop();
}

The Tty class implements ITty which provides access to the console. The constructor takes the integer file descriptor and a boolean that tells whether it is for input (which in this case it is not, so false). out.Write returns a Future<int>. The int represents the status code of the write.

Future<T> is a template provided by cppcomponents that implements most of the functionality described in http://isocpp.org/files/papers/N3558.pdf but with different naming conventions. Here are some of the main operations supported by Future

  1. T Get() returns the value stored in the future, or an exception if an error was stored. Note: Unlike C++11 Get() is non-blocking, if the operation is not yet done, Get() will throw an exception.
  2. Future<result_of<F(Future<T>)>::type> Then(F f) where F is a continuation function of the form void(Future<T> fut) where T is the type of the future
  3. std::int32_t ErrorCode() Returns the error code associated with the future, 0 if no error. All error codes are negative 32-bit integers
  4. bool Ready() Returns true if the Futures is complete, false otherwise.

So in the above code we call out.Write to write the output. When the output has been written our function that we passed to Future.Then will get called at which point we call DefaultExecutor().MakeLoopExit() to exit the event loop and exit the program.

Timers and Chaining Futures

Ok we have now successfully printed "Hello World". Let's extend this by adding a timer. We want to say "Hello World" wait 5 seconds and then say "See you later". The code is below.

#include <cppcomponents_libuv/cppcomponents_libuv.hpp>

using cppcomponents::Future;

int main(){
    cppcomponents_libuv::Tty out{1,false};
    Future<int> output_fut = out.Write("Hello world...");
    output_fut.Then(
        [&](Future<int> f){
           cppcomponents_libuv::Timer::WaitFor(std::chrono::seconds{5}).Then([&](Future<int> f){
              out.Write("See you later").Then([](Future<int> f){ 
                  cppcomponents_libuv::Uv::DefaultExecutor().MakeLoopExit();
          });
           });
        }
     );
    cppcomponents_libuv::Uv::DefaultExecutor().Loop();
}

Resumable functions and await

The above code works, but it is starting to get complicated. There are functions within functions and it seems like there is a lot of boilerplate. There are other ways to chain futures with returning futures and unwrapping them. There is however, a better way. In C# you can mark a function as async. Inside that function, you can then use await to wait for a future. There is a proposal to provide a similar mechanism for C++ described in http://isocpp.org/files/papers/N3564.pdf . However, that is still at proposal stage and at this point no available compilers implement it. However, we can use some magic provided by the boost coroutine library and implement pretty much most of it using C++11. Let us look at how we would write the code for this.

#include <cppcomponents_libuv/cppcomponents_libuv.hpp>
#include <cppcomponents_async_coroutine_wrapper/cppcomponents_resumable_await.hpp>

using cppcomponents::Future;

void async_main(cppcomponents::awaiter await){
    cppcomponents_libuv::Tty out{1,false};
    await(out.Write("Hello world..."));
    await(cppcomponents_libuv::Timer::WaitFor(std::chrono::seconds{5}));
    await( out.Write("See you later"));
    cppcomponents_libuv::Uv::DefaultExecutor().MakeLoopExit();
}

int main(){
    auto resumable_main = cppcomponents::resumable(async_main);
    resumable_main();
    cppcomponents_libuv::Uv::DefaultExecutor().Loop();
}

When you run the code, make sure you have cppcomponents_async_coroutine_wrapper_dll.dll (or .so) in your directory.

Notice how much easier the above code is to follow. To make a function resumable, just declare it as you would a regular function but then add the parameter cppcomponents::awaiter await at the last parameter. Then to call it, pass the function to cppcomponents::resumable. You will get back a function object that has the form Future<return value of original function>(all the parameters of original function except awaiter)

awaiter has the following member functions

  • Future<T> as_future(Future<T> future) This member function takes a Future. If the future is Ready() it returns the future. Otherwise, it suspends execution of the function and returns to the caller. Then when, the future is completed, it resumes the functions and returns the future. At completion, it is guaranteed that future.Ready() returns true.

  • T operator()(Future<T> future) This is the same as calling .as_future(future).Get()

Input and channels

So far, we have output and a timer, how about some input.

#include <cppcomponents_libuv/cppcomponents_libuv.hpp>
#include <cppcomponents_async_coroutine_wrapper/cppcomponents_resumable_await.hpp>

using cppcomponents::Future;

void async_main(cppcomponents::awaiter await){
    cppcomponents_libuv::Tty out{1,false};
    cppcomponents_libuv::Tty in{0,true};
    auto input_channel = in.ReadStartWithChannel();
    await(out.Write("Enter your name\n"));
    auto buffer = await(input_channel.Read());
    await(out.Write("Hello " + std::string(buffer.Begin(),buffer.End()) + " nice to meet you..."));
    await(cppcomponents_libuv::Timer::WaitFor(std::chrono::seconds{5}));
    await( out.Write("See you later"));
    cppcomponents_libuv::Uv::DefaultExecutor().MakeLoopExit();
}

int main(){
    auto resumable_main = cppcomponents::resumable(async_main);
    resumable_main();
    cppcomponents_libuv::Uv::DefaultExecutor().Loop();
}

Asynchronous programming as mentioned earlier is driven by "don't call us, we'll call you". Libuv on which this library is based, does not provide a way to read a single value from an input stream. Instead it provides a function called read_start along with a callback that will get called whenever input is available. However, dealing with that can be inconvenient as you have to provide a separate callback function.

Channels provide a way around this. Channels convert an event handler into a sequence of Futures. Channels were inspired by Go channels, however with some differences. Channels are provided by cppcomponents::Channel<T> and has the following member functions

  • Future<T> Read() This reads a value from the channel. The returned future becomes Ready() when a value is written to the channel.

  • Future<void> Write(T value) Writes value to the channel and returns a future. The Future<void> will become Ready() when does the correspond Read() from the channel. This will cause the Future<T> returned from the corresponding Read() to be Ready(). A call to .Get() on the Future<T> returned from Read() will return value;

  • Future<void> WriteError(cppcomponents::error_code ec) Writes an error to the channel. Note the error_code must be less than 0. The Read() corresponding the this, will return Future<T> with an error. A call to .ErrorCode() on the future returned from Read() will return the error code. A call to .Get will throw an exception derived from cppcomponents::error_base with an error code of ec.

  • void Complete() Signals that no more values will be written to the channel. This causes WriteError(cppcomponents::error_abort::ec) to be called for all the pending reads. Attempting to Read() after a channel is complete returns a Future<T> that will return the enumeration value cppcomponents::error_abort::ec on calls to ErrorCode() and throw exception cppcomponents::error_abort on calls to Get()

The Channel that ReadStartWithChannel returns is Channel<cppcomponents::use<cppcomponents::IBuffer>>. When we await the Channel.Read() we assign the result to buffer. use<IBuffer> has methods Begin() End() and Size(). It also will automatically release the memory when it goes out of scope.

Timeouts during reads

Sometimes, when you are waiting for input, you don't want to wait forever. You want time out if the input is not available. Here is our previous program with a 5 second timeout to enter the name.

#include <cppcomponents_libuv/cppcomponents_libuv.hpp>
#include <cppcomponents_async_coroutine_wrapper/cppcomponents_resumable_await.hpp>

using cppcomponents::Future;

void async_main(cppcomponents::awaiter await){
    cppcomponents_libuv::Tty out{1,false};
    cppcomponents_libuv::Tty in{0,true};
    auto input_channel = in.ReadStartWithChannel();
    await(out.Write("Please enter your name in the next 5 seconds \n"));
    auto timeout_future = cppcomponents_libuv::Timer::WaitFor(std::chrono::seconds{5});
    auto input_future = input_channel.Read();
    await(cppcomponents::when_any(timeout_future,input_future));
    if(timeout_future.Ready()){
    await(out.Write("You did not answer fast enough. Goodbye.\n"));
        cppcomponents_libuv::Uv::DefaultExecutor().MakeLoopExit();
    return;
    }
    auto buffer = input_future.Get();
    await(out.Write("Hello " + std::string(buffer.Begin(),buffer.End()) + " nice to meet you..."));
    await(cppcomponents_libuv::Timer::WaitFor(std::chrono::seconds{5}));
    await( out.Write("See you later"));
    cppcomponents_libuv::Uv::DefaultExecutor().MakeLoopExit();
}

int main(){
    auto resumable_main = cppcomponents::resumable(async_main);
    resumable_main();
    cppcomponents_libuv::Uv::DefaultExecutor().Loop();
}

Reporting errors from async_main

So far we have pretty much ignored error handling. However, if we look at the code, if async_main has an error and throws an exception, it will not get reported. Worse, it will cause the MakeLoopExit() statement not to be reached, and will loop forever. We fix both of these problems in the following code.

#include <cppcomponents_libuv/cppcomponents_libuv.hpp>
#include <cppcomponents_async_coroutine_wrapper/cppcomponents_resumable_await.hpp>
#include <iostream>

using cppcomponents::Future;

void async_main(cppcomponents::awaiter await){
    cppcomponents_libuv::Tty out{1,false};
    cppcomponents_libuv::Tty in{0,true};
    auto input_channel = in.ReadStartWithChannel();
    await(out.Write("Please enter your name in the next 5 seconds \n"));
    auto timeout_future = cppcomponents_libuv::Timer::WaitFor(std::chrono::seconds{5});
    auto input_future = input_channel.Read();
    await(cppcomponents::when_any(timeout_future,input_future));
    if(timeout_future.Ready()){
    await(out.Write("You did not answer fast enough. Goodbye.\n"));
    return;
    }
    auto buffer = input_future.Get();
    await(out.Write("Hello " + std::string(buffer.Begin(),buffer.End()) + " nice to meet you..."));
    await(cppcomponents_libuv::Timer::WaitFor(std::chrono::seconds{5}));
    await( out.Write("See you later"));
}

int main(){
    auto resumable_main = cppcomponents::resumable(async_main);
    resumable_main().Then([&](Future<void> f){
    if(f.ErrorCode()){
        std::cerr << "\nasync_main had error with error code " << f.ErrorCode() << "\n";
    }
    else{
        std::cerr << "\nasync_main returned without throwing an exception\n";
    }
        cppcomponents_libuv::Uv::DefaultExecutor().MakeLoopExit();
    return f.ErrorCode();
    });
    cppcomponents_libuv::Uv::DefaultExecutor().Loop();
}

Recall from our previous discussion of cppcomponents::resumable that it returns a function object that returns a future when it is called. Thus, resumable_main() returns Future<void>. In this code we remove the call to DefaultExecutor().MakeLoopExit() from async_main and move it the resumable_main().Then continuation function. Inside that function we check if there was an exception. (cppcomponents converts all exceptions to error_codes) by checking the error code. If there was an error, we print out the error code, if not we say there was no error and then we make the call the MakeLoopExit()

Running tasks in the background

Sometimes we want to run a task in another thread and communicate with it from the main thread. In this example, we will calculate the fibonacci numbers up to 93 (the largest that fits in a 64bit number) in a background thread. In the main thread, you can type "Fib" to get the latest fibonacci available. When you have gotten Fibonacci(93) the program exits. The source code is below.

#include <cppcomponents_libuv/cppcomponents_libuv.hpp>
#include <cppcomponents_async_coroutine_wrapper/cppcomponents_resumable_await.hpp>
#include <iostream>
#include <sstream>
#include <thread>

using cppcomponents::Future;
using cppcomponents::Channel;

const std::uint16_t largest_64bit_fibonacci = 93;

void fibonacci(Channel < std::pair<std::uint16_t, std::uint64_t> > out_channel){
    auto write_future = out_channel.Write({ 0, 0 });
    std::uint64_t fibs[largest_64bit_fibonacci+1];
    fibs[0] = 0;
    fibs[1] = 1;
    for (std::uint16_t i = 2; i <= largest_64bit_fibonacci; ++i){
        fibs[i] = fibs[i - 2] + fibs[i - 1];
        if (write_future.Ready() || i==largest_64bit_fibonacci){
            write_future = out_channel.Write({ i, fibs[i] });
        }
        std::this_thread::sleep_for(std::chrono::milliseconds(50));
    }
}

void async_main(cppcomponents::awaiter await){
    cppcomponents_libuv::Tty out{1,false};
    cppcomponents_libuv::Tty in{0,true};
    auto input_channel = in.ReadStartWithChannel();
    await(out.Write("Enter Fib to see how far along the fibonacci calculation is\n"));
    cppcomponents_libuv::ThreadPoolExecutor pool;
    auto fib_channel =  cppcomponents::make_channel < std::pair<std::uint16_t, std::uint64_t> > ();
    pool.Add(std::bind(fibonacci,fib_channel));
    while(true){
    auto buf = await(input_channel.Read());
    std::string s{buf.Begin(),buf.End()};
    if(s.substr(0,3) == "Fib"){
        std::uint16_t fib = 0;
            std::uint64_t value = 0;
        std::tie(fib,value) = await(cppcomponents_libuv::Uv::DefaultExecutor(),fib_channel.Read());
        std::stringstream os;
        os << "Fibonacci(" << fib << ") = " << value << "\n";
        await(out.Write(os.str()));
        if(fib == largest_64bit_fibonacci){
        await(out.Write("Fibonacci calculations complete\n"));
        return;
        }
    }
    }
}

int main(){
    auto resumable_main = cppcomponents::resumable(async_main);
    resumable_main().Then([&](Future<void> f){
    if(f.ErrorCode()){
        std::cerr << "\nasync_main had error with error code " << f.ErrorCode() << "\n";
    }
    else{
        std::cerr << "\nasync_main returned without throwing an exception\n";
    }
        cppcomponents_libuv::Uv::DefaultExecutor().MakeLoopExit();
    return f.ErrorCode();
    });
    cppcomponents_libuv::Uv::DefaultExecutor().Loop();
}

In the code above, we include for std::stringstream to help us format the output, and for std::this_thread::sleep. In the fibonacci function we take an argument of std::pair<std::uint16_t, std::uint64_t> >. We will write out our output as a pair(n, fibonacci(n)). We prime the channel by writing the 0th fibonacci number and get a Future<void> back which we call write_future. When write_future is Ready() that means our channel has been read from. We then write our latest calcuation to the channel and assign write_future the result ofWrite()`

In async_main we create a ThreadPoolExecutor. We use cppcomponents::make_channel<T>() which creates a channel of type T to make a channel. We then use std::bind to bind the channel to our fibonacci function and add that to our threadpool. We then loop reading input. We check that the first 3 letters are "Fib", if so, we read the fibonacci pair from the channel, and then output the result. If the fibonacci number is for fibonacci 93, we know we are done, and then return from async_main.

Writing a TCP client

The following code demonstrates how to connect to a tcp server and issue a simple HTTP GET request and output the results. During the reads for the server, we use a 2 second timeout.

#include <cppcomponents_libuv/cppcomponents_libuv.hpp>
#include <cppcomponents_async_coroutine_wrapper/cppcomponents_resumable_await.hpp>
#include <iostream>
#include <sstream>
#include <thread>

using cppcomponents::Future;
using cppcomponents::Channel;

std::string get_resource(const std::string& server,const std::string& port, const std::string& resource, 
        cppcomponents::awaiter await){

    auto addr = await(cppcomponents_libuv::Uv::Getaddrinfo(server, port, nullptr));
    if (addr == nullptr){
        return "ERROR: Address not found\n";
    }
    cppcomponents_libuv::TcpStream stream;
    await(stream.Connect(addr->ai_addr));
    cppcomponents_libuv::Uv::Freeaddrinfo(addr);
    std::string request = "GET " + resource + " HTTP/1.0\r\nHost: " + server + "\r\n\r\n";
    await(stream.Write(request));
    auto chan = stream.ReadStartWithChannel();
    std::string response;
    while (true){
        auto timerfut = cppcomponents_libuv::Timer::WaitFor(std::chrono::seconds{ 2 });
        auto fut = chan.Read();
        await(when_any(timerfut, fut));
        if (timerfut.Ready()){
            return "Error " + server + " timed out while reading\n";
        }   
        if (fut.ErrorCode() != 0){
            if(fut.ErrorCode() == static_cast<int>(cppcomponents_libuv::ErrorCodes::Eof)){
               return response;
            }
            return "Error " + server + " return an error while reading\n";
        }
        auto buf = fut.Get();
        if(buf.Size() == 0){
            return response;
        }
        response.append(buf.Begin(), buf.End());
    }
}

void async_main(cppcomponents::awaiter await){
    cppcomponents_libuv::Tty out{1,false};
    cppcomponents_libuv::Tty in{0,true};
    auto input_channel = in.ReadStartWithChannel();
    await(out.Write("Enter <server> <port> <resource> to get a http resource\n"));
    auto buf = await(input_channel.Read());
    std::string s{buf.Begin(),buf.End()};
    std::stringstream is{s};
    std::string server, port, resource;
    is >> server >> port >> resource;
    auto resumable_get_resource = cppcomponents::resumable(get_resource);
    auto result = await(resumable_get_resource(server,port,resource));
    await(out.Write(result));
}

int main(){
    auto resumable_main = cppcomponents::resumable(async_main);
    resumable_main().Then([&](Future<void> f){
    if(f.ErrorCode()){
        std::cerr << "\nasync_main had error with error code " << f.ErrorCode() << "\n";
    }
    else{
        std::cerr << "\nasync_main returned without throwing an exception\n";
    }
        cppcomponents_libuv::Uv::DefaultExecutor().MakeLoopExit();
    return f.ErrorCode();
    });
    cppcomponents_libuv::Uv::DefaultExecutor().Loop();
}

Other than get_resource the rest of the code should look familiar. In get_resource we first asynchronously resolve the address using Uv::Getaddrinfo. This will return a Future<addrinfo*>. If that is null we cannot resolve the address and then exit. Otherwise the ai_addr is the address. We then Connect to the address. Then it is a simple matter of writing the HTTP GET request, looping and reading the results. We use when_any as we did for the timeout example to implement a timeout. If the channel read returns a future with an error, we check if the error is cppcomponents_libuv::ErrorCodes::Eof (we need the static_cast because ErrorCodes in an enum class). If we have EOF we return the response we have so far, if not, we return a string stating we had an error. If the read was successful, we add the results to our response string.

TCP Server

The following code demonstrates a simple TCP server. This server, echoes back as an HTTP message whatever is sent to it. To try the program, echo a port number when prompted. Then browse to that port number in browser to see the results of the echo.

#include <cppcomponents_libuv/cppcomponents_libuv.hpp>
#include <cppcomponents_async_coroutine_wrapper/cppcomponents_resumable_await.hpp>
#include <iostream>
#include <sstream>
#include <thread>

using cppcomponents::Future;
using cppcomponents::Channel;

void echo_server(int port, Channel<int> stopchan, cppcomponents::awaiter await){

    auto stopfut = stopchan.Read();

    cppcomponents_libuv::TcpStream server;
    auto server_addr = cppcomponents_libuv::Uv::Ip4Addr("0.0.0.0", port);
    server.Bind(server_addr);

    server.Listen(1, cppcomponents::resumable([](cppcomponents::use<cppcomponents_libuv::IUvStream> is,
                    int, cppcomponents::awaiter await){
        cppcomponents_libuv::TcpStream client;
        is.Accept(client);
        auto readchan = client.ReadStartWithChannel();
        while ( true ){
            auto buf = await(readchan.Read());
            std::stringstream strstream;
            strstream <<
            "HTTP/1.1 200 OK\r\n"
            "Content-Type: text/plain\r\n"
            "Content-Length: " << buf.Size() << "\r\n"
            "\r\n";
            strstream.write(buf.Begin(), buf.Size());
            await(client.Write(strstream.str()));
        }
    }));
    await(stopfut);
}

void async_main(cppcomponents::awaiter await){
    cppcomponents_libuv::Tty out{1,false};
    cppcomponents_libuv::Tty in{0,true};
    auto input_channel = in.ReadStartWithChannel();
    await(out.Write("Enter <port> to start an echo server\n"));
    auto buf = await(input_channel.Read());
    std::string s{buf.Begin(),buf.End()};
    std::stringstream is{s};
    int port = -1;
    is >> port;
    if(port==-1){
        await(out.Write("Invalid port entered, exiting\n"));
        return;
    }
    auto server_chan = cppcomponents::make_channel<int>();
    auto resumable_echo_server = cppcomponents::resumable(echo_server);
    auto server_fut = resumable_echo_server(port,server_chan);
    auto read_fut = input_channel.Read();
    await(out.Write("Starting server, enter Quit to stop server\n"));
    await(when_any(server_fut,read_fut));
    if(server_fut.Ready()){
        await(out.Write("Error starting server, exiting\n"));
        server_fut.Get();
    }
    while(true){
        buf = await(read_fut);
        s.assign(buf.Begin(),buf.End());
        if(s.substr(0,4) == "Quit"){
            server_chan.Write(1);
            await(server_fut);
            return;
        }
        read_fut = input_channel.Read();
    }
}

int main(){
    auto resumable_main = cppcomponents::resumable(async_main);
    resumable_main().Then([&](Future<void> f){
        if(f.ErrorCode()){
            std::cerr << "\nasync_main had error with error code " << f.ErrorCode() << "\n";
        }
        else{
            std::cerr << "\nasync_main returned without throwing an exception\n";
        }
        cppcomponents_libuv::Uv::DefaultExecutor().MakeLoopExit();
        return f.ErrorCode();
    });
    cppcomponents_libuv::Uv::DefaultExecutor().Loop();
}

In the echo_server function we take the port to use and a channel. When the channel is written to we will stop the server. We get a future stopfut by calling stopchan.Read(). When stopfut is complete, we will stop the server. We then set up a TcpStream and bind it to the port. We then call Listen The first parameter of listen is the backlog, the second is a function that takes IUvStream and an int (which we ignore). Inside the listen function we create a client TcpStream and Accept it. Then we use channels to read the input from the client, and write back an http response.

After calling server.Listen we then await on stopfut.

In our async_main function, we prompt the user for the port. We then call resumable_echo_server to start the server and pass in port and server_chan. We then also query for input. If the server future finishes before we get input, that means the server had an error. we call server_fut.Get() to throw the error as an exception so our main can print out the error. Otherwise, we read from the input until the user types "Quit". At that point me Write(1) to the server_chan to signal the server to stop. We then await for the server_fut and return.

Conclusion

Thanks for taking the time to read through this. I hope you enjoy using this library. Please feel free to give me feedback.

Take a look at the main.cpp file at https://github.com/jbandela/cppcomponents_libuv/tree/master/examples/example1. It combines with documented code most of the examples from above into one big example. It also has an example of reading a file and calculating the SHA1 digest, if you are interested in reading files.

Please feel free the check out the github repositories for cppcomponents,cppcomponents_libuv, and cppcomponents_async_coroutine_wrapper. The latest code will be there. Just remember when you run code compiled with those programs, have the latest versions of cppcomponents_libuv_dll (.dll or .so) and cppcomponents_async_coroutine_wrapper_dll(.dll or .so). in the same directory as the executable.

License

cppcomponents, cppcomponents_libuv, cppcomponents_async_coroutine_wrapper are Copyright 2013 John R. Bandela and released under the Boost Software License. libuv is released under a permissive license as detailed in https://github.com/joyent/libuv/blob/master/LICENSE.

Something went wrong with that request. Please try again.