network library based on io_uring and C++20 coroutine
Experimental C++ network library based on io_uring and coroutine. This project is developed for understanding and practice C++20 new features(ranges, coroutine) and new Linux I/O API io_uring.
file_descriptor
is designed in the CRTP Mixin pattern, all operations(recv, send, listen...) are modularized and thus optional.buffer_sequence
is designed to be compatible with C++20 std::span
<filesystem>
, support error handling in both std::error_code
and exceptionfile_descriptor
template <template <typename...> typename ... Modules>
class file_descriptor<detail::module_list<Modules ...>>
: public file_descriptor_base
, public Modules<file_descriptor<detail::module_list<Modules ...>>>...
{};
file_descriptor
is a handler that has unique ownership of file descriptor, like std::unique_ptr
.
::close()
is called in its destructor if the underlying file descriptor is valid.Modules
are interfaces that are mixin'ed into the base class. Modules
access file_descriptor
by static polymorphism, i.e. cast this
into the base class type.set()
and get()
to work with the file_descriptor.socket_t
using socket_t = file_descriptor
<
detail::module_list
<
socket_init,
address,
operation_shutdown,
operation_set_options,
operation_bind,
operation_listen,
operation_accept,
operation_connect,
operation_send,
operation_recv,
operation_close
>
>;
convenient type alias that has all modules for socket. You can define you own type alias. For example, an acceptor does not need to do send or recv.
using acceptor_t = file_descriptor
<
detail::module_list
<
socket_init,
operation_set_options,
operation_bind,
operation_listen,
operation_accept
>
>;
buffer_sequence
and const_buffer_sequence
buffer_sequence
takes a sequences of Containers which satisfy the concept std::ranges::contiguous_range
then transform them into iovec.
buffer_sequence
could be constructed using:
template<typename... Containers>
buffer_sequence(Containers&&... containers)
where Containers
could be
std::span<std::byte, size or std::dynamic_extent>
std::span<Ts, size or std::dynamic_extent>
or
template<typename BufferRange>
requires std::ranges::viewable_range<BufferRange&>
&& std::ranges::contiguous_range<std::ranges::range_value_t<BufferRange>>
buffer_sequence(BufferRange& buffer_range)
where BufferRange
is a range of contiguous ranges. Notice that, different from the former one, iovec
s are stored in a std::vector
where dynamic allocation is inevitable.
Modules
All modules support error handling by
std::error_code
: by passing an lvalue reference of std::error_code
(like C++17 <filesystem>
), should there be an error, the lvalue reference passed will assign with a new error_code, otherwise, it will be clear()
.
exception
. An std::system_error
constructed by the reason encapsulated in a std::error_code
will be throwed if there is an error.
init()
, init(std::erro_code& error)
create a IPv4/TCP socket and set the file_descriptor
with the file_descriptor returned form ::socket()
.the size of file_descriptor
will increase if this module is used.
Getters and setters are provided: set_local_address(const socket_address& address)
, set_peer_address(const socket_address& address)
, get_local_address()
, get_peer_address()
.
Moudles like operation_accept
, operation_connect
, will set the address
on success.
synchronous operation modules
shutdown(int how = SHUTWR)
, shutdown(int how, std::error_code& error)
, shutdown(std::error_code& error)
bind the socket to a given address. If module address
is used, set the local address on success. If bind
with socket_address{0}
, then a random port will be assigned and the local address will also be set on success.
bind(const socket_address& address)
, bind(const socket_address& address, std::error_code& error)
call ::setsockopt
.
void setsockopt(int level, int optname, const void* optval, socklen_t optlen, std::error_code& error)
, void setsockopt(int level, int optname, const void* optval, socklen_t optlen)
,
void reuse_address(std::error_code& error)
,
void reuse_address()
put the socket into listen state
listen(int backlog = SOMAXCONN)
, listen(int backlog, std::error_code& error)
, listen(std::error_code& error)
.
asynchronous operation modules
all asynchronous operations provides an optional argument duration
to impose a timeout.
template<typename F2, typename... Args> [[nodiscard]]
decltype(auto) accept(F2& peer_socket, Args&&... args) noexcept
peer_socket
The socket into which the new connection will be accpeted.
Args is void: The awaiter returned by the function will throw exception to report the error. A std::system_error constructed with the corresponding std::error_code will be throwed if the accept operation is failed after being co_await'ed.
Args is a Duration, i.e. std::chrono::duration<Rep, Period>. This duration will be treated as the timeout for the operation. If the operation does not finish within the given duration, the operation will be canneled and an error_code (std::errc::operation_canceled) will be returned.
Args is an lvalue reference of a std::error_code The awaiter returned by the function will use std::error_code to report the error. Should there be an error in the operation, the std::error_code passed by lvalue reference will be reset. Otherwise, it will be clear.
Args are first a Duration, second an lvalue reference of a std::error_code The operation will have the features described in 2 and 3.
template<typename... Args> [[nodiscard]]
decltype(auto) connect(const socket_address& address, Args&&... args) noexcept
address
: the address with which the connection will be established.
args
: same as args
desribed in operation_accept
This operation will read from the socket until it reads a 0(eof). Then it will call close(2) on the socket.
template<typename... Args>
[[nodiscard]]
decltype(auto) close(Args&&... args) noexcept
args
: same as args
desribed in operation_accept
recv([std::error_code& error], [Duration&& duration], Args&&... args)
where args
will be forwarded to the constructor of buffer_sequence
.
recv_some([std::error_code& error], [Duration&& duration], Args&&... args)
where args
will be forwarded to the constructor of buffer_sequence
.
this awaiter will resume the coroutine after the first recv operation finished, regardless of whether the buffers are filled up or not.
recv([std::error_code& error], [Duration&& duration], Args&&... args)
where args
will be forwarded to the constructor of const_buffer_sequence