Skip to content

Hackerl/asyncio

Repository files navigation

Contributors Forks Stargazers Issues Apache 2.0 License


asyncio

C++23 coroutine network framework
Explore the docs »

View Demo · Report Bug · Request Feature

Table of Contents
  1. About The Project
  2. Getting Started
  3. Usage
  4. Roadmap
  5. Contributing
  6. License
  7. Contact
  8. Acknowledgments

About The Project

Based on the libuv event loop, use C++20 stackless coroutines to implement network components, and provide channel to send and receive data between tasks.

(back to top)

Built With

  • CMake
  • vcpkg
  • C++23

(back to top)

Getting Started

Prerequisites

Required compiler:

  • GCC >= 14
  • LLVM >= 18
  • MSVC >= 19.38

Export environment variables:

  • VCPKG_INSTALLATION_ROOT
  • ANDROID_NDK_HOME(Android)

Build

  • Linux

    cmake -B build -DCMAKE_TOOLCHAIN_FILE="${VCPKG_INSTALLATION_ROOT}/scripts/buildsystems/vcpkg.cmake" && cmake --build build -j$(nproc)
  • Android

    # set "ANDROID_PLATFORM" for dependencies installed by vcpkg: echo 'set(VCPKG_CMAKE_SYSTEM_VERSION 24)' >> "${VCPKG_INSTALLATION_ROOT}/triplets/community/arm64-android.cmake"
    cmake -B build -DCMAKE_TOOLCHAIN_FILE="${VCPKG_INSTALLATION_ROOT}/scripts/buildsystems/vcpkg.cmake" -DVCPKG_CHAINLOAD_TOOLCHAIN_FILE="${ANDROID_NDK_HOME}/build/cmake/android.toolchain.cmake" -DVCPKG_TARGET_TRIPLET=arm64-android -DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=android-24 && cmake --build build -j$(nproc)
  • Windows(Developer PowerShell)

    cmake -B build -DCMAKE_TOOLCHAIN_FILE="$Env:VCPKG_INSTALLATION_ROOT/scripts/buildsystems/vcpkg.cmake" && cmake --build build -j $env:NUMBER_OF_PROCESSORS

(back to top)

Installation

Install asyncio from the vcpkg private registry:

  1. Create a vcpkg-configuration.json file in the project root directory:

    {
      "registries": [
        {
          "kind": "git",
          "repository": "https://github.com/Hackerl/vcpkg-registry",
          "baseline": "56e949885c4f22d5711523f360ac92d73f7a8495",
          "packages": [
            "asyncio",
            "zero"
          ]
        }
      ]
    }

    The baseline defines the minimum version of asyncio that will be installed. The one used above might be outdated, so please update it as necessary.

  2. Create a vcpkg.json file in the project root directory:

    {
      "name": "project name",
      "version-string": "1.0.0",
      "builtin-baseline": "9224b3bbd8df24999d85720b1d005dd6f969ade0",
      "dependencies": [
        "asyncio"
      ]
    }
  3. Add the following to the CMakeLists.txt file:

    find_package(asyncio CONFIG REQUIRED)
    target_link_libraries(custom_target PRIVATE asyncio::asyncio-main)

Usage

HTTP Client

Using the HTTP Client in a C++ project has never been easier:

#include <asyncio/http/request.h>

asyncio::task::Task<void, std::error_code> asyncMain(const int argc, char *argv[]) {
    const auto url = asyncio::http::URL::from("https://www.google.com");
    CO_EXPECT(url);

    auto requests = asyncio::http::Requests::make();
    CO_EXPECT(requests);

    auto response = co_await requests->get(*url);
    CO_EXPECT(response);

    const auto content = co_await response->string();
    CO_EXPECT(content);

    fmt::print("{}", *content);
    co_return {};
}

We use asyncMain instead of main as the entry point, and CO_EXPECT is used to check for errors and throw them upwards.

TCP

Server

#include <asyncio/net/stream.h>
#include <asyncio/signal.h>
#include <zero/cmdline.h>

asyncio::task::Task<void, std::error_code> handle(asyncio::net::TCPStream stream) {
    const auto address = stream.remoteAddress();
    CO_EXPECT(address);

    fmt::print("connection[{}]\n", *address);

    while (true) {
        std::string message;
        message.resize(1024);

        const auto n = co_await stream.read(std::as_writable_bytes(std::span{message}));
        CO_EXPECT(n);

        if (*n == 0)
            break;

        message.resize(*n);

        fmt::print("receive message: {}\n", message);
        CO_EXPECT(co_await stream.writeAll(std::as_bytes(std::span{message})));
    }

    co_return {};
}

asyncio::task::Task<void, std::error_code> serve(asyncio::net::TCPListener listener) {
    std::expected<void, std::error_code> result;
    asyncio::task::TaskGroup group;

    while (true) {
        auto stream = co_await listener.accept();

        if (!stream) {
            result = std::unexpected{stream.error()};
            break;
        }

        auto task = handle(*std::move(stream));

        group.add(task);
        task.future().fail([](const auto &ec) {
            fmt::print(stderr, "unhandled error: {} ({})\n", ec.message(), ec);
        });
    }

    co_await group;
    co_return result;
}

asyncio::task::Task<void, std::error_code> asyncMain(const int argc, char *argv[]) {
    zero::Cmdline cmdline;

    cmdline.add<std::string>("host", "remote host");
    cmdline.add<std::uint16_t>("port", "remote port");

    cmdline.parse(argc, argv);

    const auto host = cmdline.get<std::string>("host");
    const auto port = cmdline.get<std::uint16_t>("port");

    auto listener = asyncio::net::TCPListener::listen(host, port);
    CO_EXPECT(listener);

    auto signal = asyncio::Signal::make();
    CO_EXPECT(signal);

    co_return co_await race(
        serve(*std::move(listener)),
        signal->on(SIGINT).transform([](const int) {
        })
    );
}

Start the server with ./server 127.0.0.1 8000, and gracefully exit by pressing ctrl + c in the terminal.

Client

#include <asyncio/net/stream.h>
#include <asyncio/time.h>
#include <zero/cmdline.h>

asyncio::task::Task<void, std::error_code> asyncMain(const int argc, char *argv[]) {
    using namespace std::chrono_literals;
    using namespace std::string_view_literals;

    zero::Cmdline cmdline;

    cmdline.add<std::string>("host", "remote host");
    cmdline.add<std::uint16_t>("port", "remote port");

    cmdline.parse(argc, argv);

    const auto host = cmdline.get<std::string>("host");
    const auto port = cmdline.get<std::uint16_t>("port");

    auto stream = co_await asyncio::net::TCPStream::connect(host, port);
    CO_EXPECT(stream);

    while (true) {
        CO_EXPECT(co_await stream->writeAll(std::as_bytes(std::span{"hello world"sv})));

        std::string message;
        message.resize(1024);

        const auto n = co_await stream->read(std::as_writable_bytes(std::span{message}));
        CO_EXPECT(n);

        if (*n == 0)
            break;

        message.resize(*n);

        fmt::print("receive message: {}\n", message);
        co_await asyncio::sleep(1s);
    }

    co_return {};
}

Connect to the server with ./client 127.0.0.1 8000.

For more examples, please refer to the Documentation

(back to top)

Roadmap

  • HTTP Server

See the open issues for a full list of proposed features (and known issues).

(back to top)

Contributing

Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are greatly appreciated.

If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". Don't forget to give the project a star! Thanks again!

  1. Fork the Project
  2. Create your Feature Branch (git checkout -b feature/AmazingFeature)
  3. Commit your Changes (git commit -m 'Add some AmazingFeature')
  4. Push to the Branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

(back to top)

License

Distributed under the Apache 2.0 License. See LICENSE for more information.

(back to top)

Contact

Hackerl - @Hackerl - [email protected]

Project Link: https://github.com/Hackerl/asyncio

(back to top)

Acknowledgments

(back to top)

About

C++23 coroutine network framework

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published