Networking library using C++20 coroutines
BSD-2-CLAUSE License
This library leverages C++20 coroutines for asynchronous programming, providing efficient and non-blocking I/O operations. It offers a range of polling mechanisms and utilities for handling sockets and files, making it suitable for various networking and file I/O tasks.
⭐ If you find COROIO useful, please consider giving us a star on GitHub! Your support helps us continue to innovate and deliver exciting features.
Coroutines for Asynchronous Code:
Polling Mechanisms:
TSelect
: Utilizes the select
system call, suitable for a wide range of platforms.TPoll
: Uses the poll
system call, offering another general-purpose polling solution.TEPoll
: Employs epoll
, available exclusively on Linux systems for high-performance I/O.TUring
: Integrates with liburing
for advanced I/O operations, specific to Linux.TKqueue
: Uses kqueue
, available on FreeBSD and macOS.TDefaultPoll
: Automatically selects the best polling mechanism based on the platform (TEPoll on Linux, TKqueue on macOS/FreeBSD).Socket and File Handling:
TSocket
and TFileHandle
: Core entities for handling network sockets and file operations.ReadSome
and WriteSome
methods for reading and writing data. These methods read or write up to a specified number of bytes, returning the number of bytes processed or -1 on error. A return value of 0 indicates a closed socket.Utility Wrappers:
TByteReader
and TByteWriter
: Ensure the specified number of bytes is read or written, useful for guaranteed data transmission.TLineReader
: Facilitates line-by-line reading, simplifying the handling of text-based protocols or file inputs.Setup: Include the library in your project and ensure C++20 support is enabled in your compiler settings.
Selecting a Poller:
TDefaultPoll
can automatically select the appropriate poller.Implementing Network Operations:
TSocket
for network communication. Initialize a socket with the desired address and use ReadSome
/WriteSome
for data transmission.TFileHandle
for file I/O operations with similar read/write methods.Reading and Writing Data:
ReadSome
and WriteSome
.TByteReader
or TByteWriter
.TLineReader
offers a convenient way to process data line by line.// Example of creating a socket and reading/writing data
TSocket socket{/* initialize with address and poller */};
// Writing data
socket.WriteSome(data, dataSize);
// Reading data
socket.ReadSome(buffer, bufferSize);
ReadSome
and WriteSome
to handle partial reads/writes and errors appropriately.TByteReader
, TByteWriter
, TLineReader
) to simplify common I/O patterns.TLineReader
, TByteReader
, and TByteWriter
#include <coroio/all.hpp>
#include <iostream>
#include <string>
#include <vector>
using namespace NNet;
template<typename TPoller>
TFuture<void> client(TPoller& poller, TAddress addr)
{
static constexpr int maxLineSize = 4096;
using TSocket = typename TPoller::TSocket;
using TFileHandle = typename TPoller::TFileHandle;
std::vector<char> in(maxLineSize);
try {
TFileHandle input{0, poller}; // stdin
TSocket socket{std::move(addr), poller};
TLineReader lineReader(input, maxLineSize);
TByteWriter byteWriter(socket);
TByteReader byteReader(socket);
co_await socket.Connect();
while (auto line = co_await lineReader.Read()) {
co_await byteWriter.Write(line);
co_await byteReader.Read(in.data(), line.Size());
std::cout << "Received: " << std::string_view(in.data(), line.Size()) << "\n";
}
} catch (const std::exception& ex) {
std::cout << "Exception: " << ex.what() << "\n";
}
co_return;
}
int main() {
// Initialize your poller (e.g., TSelect, TEpoll)
// ...
// Run the Echo Client
// ...
}
Line Reading:
TLineReader
is used to read lines from standard input. It handles lines split into two parts (Part1
and Part2
) due to the internal use of a fixed-size circular buffer.Data Writing:
TByteWriter
is utilized to write the line parts to the socket, ensuring that the entire line is sent to the server.Data Reading:
TByteReader
reads the server's response into a buffer, which is then printed to the console.Socket Connection:
TSocket
is connected to the server at "127.0.0.1" on port 8000.Processing Loop:
The benchmark methodology was taken from the libevent library.
There are two benchmarks. The first one measures how long it takes to serve one active connection and exposes scalability issues of traditional interfaces like select or poll. The second benchmark measures how long it takes to serve one hundred active connections that chain writes to new connections until thousand writes and reads have happened. It exercises the event loop several times.
Performance comparison using different event notification mechansims in Libevent and coroio as follows.