Skip to content

Commit 8606f71

Browse files
committed
Added basic support for C++20 coroutines
- Added net::awaitable and net::task - Added read/write functions to all socket types that return an net::awaitable to be used inside coroutines - net::task can be used as a general-purpose coroutine handle that is lazily evaluated
1 parent 6d851f4 commit 8606f71

24 files changed

+933
-415
lines changed

.gitignore

+13-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,16 @@
33
*.a
44
*.so
55
.idea
6-
.vscode
6+
.vscode
7+
example/*.dSYM
8+
example/udp_example
9+
example/tcp_example
10+
example/tls_example
11+
example/async_udp_example
12+
example/async_tcp_example
13+
example/async_tls_example
14+
example/coroutine_udp_example
15+
example/coroutine_tcp_example
16+
example/coroutine_tls_example
17+
example/span_example
18+
example/options_example

README.md

+64-22
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ Socket/connection classes all are not copyable but moveable and templated to dis
4848
### span<typename TYPE>
4949
>#include "socketwrapper/span.hpp" (also included by all socket headers)
5050

51-
Non owning abstraction of a view to memory used to generalize the interface to the reading and sending methods of the socket classes. Can be created from various container/array types.
51+
Non owning abstraction of a view to memory used to generalize the interface to the reading and sending methods of the socket classes.
52+
Can be created from all container types that can be represented by a pointer and a length.
5253
The interface of the span class is the same as for most std container classes (providing begin(), end(), front(), back(), empty(), size(), get(), data()).
5354
Methods:
5455
- Constructor:
@@ -170,15 +171,12 @@ Methods:
170171
// Default constructor of a not connected tcp connection
171172
tcp_connection();
172173

173-
// Construct a tcp connection that immediately connects to the remote in the constructor defined by the parameters.
174-
tcp_connection(const std::string_view remote_address, const uint16_t remote_port);
175-
176174
// Construct a tcp connection from a net::endpoint<IP_VER>
177175
tcp_connection(const endpoint<IP_VER>& endpoint);
178176
```
179177
- Config:
180178
```cpp
181-
// Connect a not connected socket to a given endpoint
179+
// Connect a default constructed socket to a given endpoint
182180
void connect(const endpoint<IP_VER>& endpoint);
183181
```
184182
- Reading:
@@ -192,6 +190,10 @@ Methods:
192190
// Immediately return and call the callback function after there is data available.
193191
void async_read(net::span<T> buffer, CALLBACK_TYPE&& callback) const;
194192

193+
// Immediately returns an awaitable that can be co_awaited in a C++20 coroutine
194+
// Only available when compiling with C++20 or higher
195+
net::op_awaitable<size_t, net::tcp_connection::stream_read_operation<T>> async_read(net::span<T> buffer) const;
196+
195197
// Immediately return and get a future to get the number of elements received at a later timepoint
196198
std::future<size_t> promised_read(net::span<T> buffer) const;
197199
```
@@ -203,6 +205,10 @@ Methods:
203205
// Immediately returns and invokes the callback after all in the given buffer is send. Caller is responsible to keep the data the span shows alive.
204206
void async_send(net::span<T> buffer, CALLBACK_TYPE&& callback) const;
205207

208+
// Immediately returns an awaitable that can be co_awaited in a C++20 coroutine
209+
// Only available when compiling with C++20 or higher
210+
net::op_awaitable<size_t, net::tcp_connection::stream_write_operation<T>> async_send(net::span<T> buffer) const;
211+
206212
// Immediately return and get a future to get the number of elements written at a later point in time
207213
std::future<size_t> promised_send(net::span<T> buffer) const;
208214
```
@@ -223,7 +229,7 @@ Methods:
223229
tcp_acceptor();
224230

225231
// Immediately creates a socket that listens on the given address and port with a connection backlog of `backlog`
226-
tcp_acceptor(const std::string_view bind_addr, const uint16_t port, const size_t backlog = 5);
232+
tcp_acceptor(const endpoint<IP_VER>& endpoint, const size_t backlog = 5);
227233
```
228234
- Config:
229235
```cpp
@@ -241,6 +247,10 @@ Methods:
241247
// Immediately returns and invokes the callback when a new connection is established
242248
void async_accept(CALLBACK_TYPE&& callback) const;
243249

250+
// Immediately returns an awaitable that can be co_awaited in a C++20 coroutine
251+
// Only available when compiling with C++20 or higher
252+
net::op_awaitable<net::tcp_connection<IP_VER>, net::tcp_acceptor::stream_accept_operation> async_accept() const;
253+
244254
// Immediately return and get a future to access the accepted socket at a later point in time
245255
std::future<net::tcp_connection<IP_VER>> promised_accept() const;
246256
```
@@ -260,9 +270,6 @@ Methods:
260270
// Construct a non connected tls connection
261271
tls_connection(std::string_view cert_path, std::string_view key_path);
262272

263-
// Construct a tls connection that immediately connects to the remote in the constructor defined by the parameters.
264-
tls_connection(std::string_view cert_path, std::string_view key_path, std::string_view conn_addr, uint16_t port);
265-
266273
// Construct a tls connection from an endpoint and immediately connect it
267274
tls_connection(std::string_view cert_path, std::string_view key_path, const endpoint<IP_VER>& endpoint);
268275
```
@@ -285,9 +292,6 @@ Methods:
285292
// Construct a non-bound tls_acceptor
286293
tls_acceptor(std::string_view cert_path, std::string_view key_path);
287294

288-
// Construct a tls acceptor from address string and port and set it into listening state
289-
tls_acceptor(std::string_view cert_path, std::string_view key_path, std::string_view bind_addr, uint16_t port, size_t backlog = 5);
290-
291295
// Construct a tls acceptor from an endpoint and set it into listening state
292296
tls_acceptor(std::string_view cert_path, std::string_view key_path, const endpoint<IP_VER>& endpoint);
293297
```
@@ -309,9 +313,6 @@ Methods:
309313
// Creates a non-bound UDP socket that is ready to send data but can not receive data.
310314
udp_socket();
311315

312-
// Creates a UDP socket that is bound to a given address and port so it can send and receive data after construction.
313-
udp_socket(const std::string_view bind_addr, const uint16_t port);
314-
315316
// Creates a UDP socket that is bound to a given endpoint so it can send and receive data directly after construction
316317
udp_socket(const endpoint<IP_VER>& endpoint);
317318
```
@@ -331,21 +332,26 @@ Methods:
331332
// Immediately return and invoke the callback when data is read into the buffer. Caller is responsible to keep the underlying buffer alive.
332333
void async_read(span<T> buffer, CALLBACK_TYPE&& callback) const;
333334

335+
// Immediately returns an awaitable that can be co_awaited in a C++20 coroutine
336+
// Only available when compiling with C++20 or higher
337+
net::op_awaitable<std::pair<size_t, std::optional<endpoint<IP_VER>>>, net::udp_socket::dgram_read_operation<T>> async_read(span<T> buffer) const;
338+
334339
// Immediately return and get a future to get the number of elements read and the connection info of the sender at a later point in time
335340
std::future<std::pair<size_t, endpoint<IP_VER>>> promised_read(span<T> buffer) const;
336341
```
337342
- Writing:
338343
```cpp
339344
// Send all data in the given buffer to a remote endpoint.
340-
size_t send(const std::string_view addr, const uint16_t port, span<T>&& buffer) const;
341345
size_t send(const endpoint<IP_VER>& endpoint_to, span<T> buffer) const;
342346

343347
// Immediately return and invoke the callback after the data is sent to a remote represented by the given address and port parameter.
344-
void async_send(const std::string_view addr, const uint16_t port, span<T>&& buffer, CALLBACK_TYPE&& callback) const;
345348
void async_send(const endpoint<IP_VER>& endpoint_to, span<T> buffer, CALLBACK_TYPE&& callback) const;
346349

350+
// Immediately returns an awaitable that can be co_awaited in a C++20 coroutine
351+
// Only available when compiling with C++20 or higher
352+
net::op_awaitable<size_t, net::udp_socket::dgram_write_operation<T>> async_write(const endpoint<IP_VER>& endpoint_to, span<T> buffer) const;
353+
347354
// Immediately return and get a future to get the number of elements written at a later point in time
348-
std::future<size_t> promised_send(const std::string_view addr, const uint16_t port, span<T>&& buffer) const;
349355
std::future<size_t> promised_send(const endpoint<IP_VER>& endpoint_to, span<T> buffer) const;
350356
```
351357
- Shorthand identifier:
@@ -354,6 +360,29 @@ Methods:
354360
using udp_socket_v6 = udp_socket<net::ip_version::v6>;
355361
```
356362

363+
### task<return_type>
364+
>#include "socketwrapper/task.hpp"
365+
366+
Representation of a lazily evaluated coroutine without any special functionality that holds a `std::coroutine_handle` of the parent coroutine frame.
367+
It defines a `promise_type` and implements the `awaitable` which allows awaiting this type.
368+
To start execution, this type needs to be awaited by a parent coroutine.
369+
User can use `net::block_on()` or `net::to_future()` to transform a coroutine that returns a `net::task` into a synchronous function.
370+
This is a helper class to give a user a coroutine class to utilize the networking functions that return an `net::op_awaitable`.
371+
This class is only available when compiling with C++20 or higher
372+
373+
Example of a coroutine that returns `net::task`:
374+
```cpp
375+
net::task<size_t> example(size_t input)
376+
{
377+
co_return input * 2;
378+
}
379+
380+
net::task<void> example_two()
381+
{
382+
auto number = co_await example(5);
383+
}
384+
```
385+
357386
## Utility Functions:
358387
>#include "socketwrapper/utility.hpp"
359388

@@ -378,12 +407,25 @@ All of the following functions live in the namespace `net`
378407
inline constexpr T network_to_host(T in);
379408
```
380409

381-
## Async helper functions:
382-
This functions are implicitly included with every socket class.
410+
## Runtime helper functions:
411+
This functions are implicitly included with every socket header file.
383412

384413
- Run the asynchronous context until all callbacks are handled:
385414
```cpp
386-
// Blocks until the asynchronous context runs out of registered callbacks.
415+
// Blocks until all registered async operations are handled and all completion handlers finished execution.
387416
void async_run();
388417
```
389-
418+
- Block the current thread until the coroutine represented by the `net::task` parameter is completely evaluated.
419+
Only available when compiling with C++20 or higher
420+
```cpp
421+
template <typename return_type>
422+
return_type block_on(net::task<return_type> awaitable_task);
423+
```
424+
- Convert a lazily evaluated coroutine that is represented by `net::task` into an eagerly evaluated future.
425+
By performing this conversion the task starts execution right away until it reaches its first suspension point while
426+
the task itself would normally be suspended right away and only starts execution if it is awaited.
427+
Only available when compiling with C++20 or higher
428+
```cpp
429+
template <typename return_type>
430+
std::future<return_type> spawn(net::task<return_type> awaitable_task);
431+
```

compile_flags.txt

Whitespace-only changes.

example/Makefile

+12-2
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
# cc = g++
22
cc = clang++
33

4-
CFLAGS = -std=c++17 -fpic -Wall -Werror -Wextra -pedantic -g -fsanitize=undefined -fsanitize=address
4+
CFLAGS = -std=c++20 -fpic -Wall -Werror -Wextra -pedantic -g -fsanitize=undefined -fsanitize=address
5+
# -fsanitize=thread
6+
57
LDFLAGS = -lpthread
68

79
%.o: %.cpp
810
$(cc) -c $< -o $@ $(CFLAGS)
911

1012
.PHONY: all
11-
all: tls async_tls tcp async_tcp udp async_udp span options
13+
all: tls async_tls tcp async_tcp udp async_udp coroutine_udp span options
1214

1315
.PHONY: tls
1416
tls: tls_example.cpp
@@ -26,6 +28,10 @@ tcp: tcp_example.cpp
2628
async_tcp: async_tcp_example.cpp
2729
$(cc) -o async_tcp_example $^ $(CFLAGS) $(LDFLAGS)
2830

31+
.PHONY: coroutine_tcp
32+
coroutine_tcp: coroutine_tcp_example.cpp
33+
$(cc) -o coroutine_tcp_example $^ $(CFLAGS) $(LDFLAGS)
34+
2935
.PHONY: udp
3036
udp: udp_example.cpp
3137
$(cc) -o udp_example $^ $(CFLAGS) $(LDFLAGS)
@@ -34,6 +40,10 @@ udp: udp_example.cpp
3440
async_udp: async_udp_example.cpp
3541
$(cc) -o async_udp_example $^ $(CFLAGS) $(LDFLAGS)
3642

43+
.PHONY: coroutine_udp
44+
coroutine_udp: coroutine_udp_example.cpp
45+
$(cc) -o coroutine_udp_example $^ $(CFLAGS) $(LDFLAGS)
46+
3747
.PHONY: span
3848
span: span_example.cpp
3949
$(cc) -o span_example $^ $(CFLAGS) $(LDFLAGS)

example/async_tcp_example.cpp

+14-16
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44
#include <thread>
55
#include <vector>
66

7+
extern "C" {
8+
int pid_shutdown_sockets(int, int);
9+
}
10+
711
int main(int argc, char** argv)
812
{
913
if (argc <= 1)
@@ -12,8 +16,8 @@ int main(int argc, char** argv)
1216
if (strcmp(argv[1], "r") == 0)
1317
{
1418
std::cout << "--- Receiver ---\n";
15-
auto acceptor = net::tcp_acceptor<net::ip_version::v4>("0.0.0.0", 4433);
16-
auto acceptor_two = net::tcp_acceptor<net::ip_version::v4>("0.0.0.0", 4556);
19+
auto acceptor = net::tcp_acceptor<net::ip_version::v4>(net::endpoint_v4("0.0.0.0", 4433));
20+
auto acceptor_two = net::tcp_acceptor<net::ip_version::v4>(net::endpoint_v4("0.0.0.0", 4556));
1721

1822
std::cout << "Waiting for accept\n";
1923

@@ -23,27 +27,21 @@ int main(int argc, char** argv)
2327
conns.reserve(2);
2428

2529
acceptor.async_accept(
26-
[&acceptor, &conns](net::tcp_connection<net::ip_version::v4>&& conn, std::exception_ptr)
30+
[&acceptor, &conns](net::tcp_connection<net::ip_version::v4>&& conn, std::exception_ptr ex)
2731
{
2832
auto buffer_one = std::array<char, 1024>{};
2933
auto buffer_two = std::array<char, 1024>{};
3034

3135
std::cout << "Accepted\n";
36+
if (ex != nullptr)
37+
{
38+
std::cout << "But with error so not really accepted :(\n";
39+
return;
40+
}
3241

3342
conns.push_back(std::move(conn));
3443
auto& sock = conns.back();
3544

36-
// sock.async_read(net::span{buffer},
37-
// [&sock, &buffer](size_t br, std::exception_ptr)
38-
// {
39-
// std::cout << "Received: " << br << " - " << std::string_view{buffer.data(), br} << '\n';
40-
41-
// sock.async_read(net::span{buffer},
42-
// [&buffer](size_t br) {
43-
// std::cout << "Inner receive: " << br << " - " << std::string_view{buffer.data(), br}
44-
// << '\n';
45-
// });
46-
// });
4745
auto read_fut_one = sock.promised_read(net::span{buffer_one});
4846

4947
acceptor.async_accept(
@@ -91,7 +89,7 @@ int main(int argc, char** argv)
9189
{
9290
std::cout << "--- Sender ---\n";
9391
{
94-
auto sock = net::tcp_connection<net::ip_version::v4>("127.0.0.1", 4433);
92+
auto sock = net::tcp_connection<net::ip_version::v4>(net::endpoint_v4("127.0.0.1", 4433));
9593
std::cout << "Connected\n";
9694
auto vec = std::vector<char>{'H', 'e', 'l', 'l', 'o'};
9795

@@ -105,7 +103,7 @@ int main(int argc, char** argv)
105103
std::cout << "Sent first connection second message\n";
106104
}
107105
{
108-
auto sock = net::tcp_connection<net::ip_version::v4>("127.0.0.1", 4433);
106+
auto sock = net::tcp_connection<net::ip_version::v4>(net::endpoint_v4("127.0.0.1", 4433));
109107
std::cout << "Connected again\n";
110108
std::vector<char> vec{'H', 'e', 'l', 'l', 'o'};
111109

example/async_tls_example.cpp

+3-2
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ int main(int argc, char** argv)
1212
if (strcmp(argv[1], "r") == 0)
1313
{
1414
std::cout << "--- Receiver ---\n";
15-
auto acceptor = net::tls_acceptor_v4("./cert.pem", "./key.pem", "0.0.0.0", 4433);
15+
auto acceptor = net::tls_acceptor_v4("./cert.pem", "./key.pem", net::endpoint_v4("0.0.0.0", 4433));
1616

1717
auto sock_fut = acceptor.promised_accept();
1818
std::cout << "Got future socket\n";
@@ -28,7 +28,8 @@ int main(int argc, char** argv)
2828
else if (strcmp(argv[1], "s") == 0)
2929
{
3030
std::cout << "--- Sender ---\n";
31-
auto tls_sock = net::tls_connection<net::ip_version::v4>("./cert.pem", "./key.pem", "127.0.0.1", 4433);
31+
auto tls_sock =
32+
net::tls_connection<net::ip_version::v4>("./cert.pem", "./key.pem", net::endpoint_v4("127.0.0.1", 4433));
3233

3334
tls_sock.promised_send(net::span{"Hello world"}).get();
3435
std::cout << "TLS encrypted message sent\n";

example/async_udp_example.cpp

+13-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "../include/socketwrapper/udp.hpp"
22
#include <cstring>
33
#include <iostream>
4+
#include <string>
45
#include <thread>
56

67
int main(int argc, char** argv)
@@ -11,7 +12,7 @@ int main(int argc, char** argv)
1112
if (strcmp(argv[1], "r") == 0)
1213
{
1314
std::cout << "--- Receiver ---\n";
14-
auto sock = net::udp_socket<net::ip_version::v4>("0.0.0.0", 4433);
15+
auto sock = net::udp_socket<net::ip_version::v4>(net::endpoint_v4("0.0.0.0", 4433));
1516

1617
auto buffer = std::array<char, 1024>{};
1718

@@ -67,23 +68,29 @@ int main(int argc, char** argv)
6768
}
6869
else if (strcmp(argv[1], "s") == 0)
6970
{
71+
auto io_loop = std::thread([]() { net::async_run(); });
72+
73+
int port = (argc > 2) ? std::stoi(argv[2]) : 4433;
74+
std::cout << "Port: " << port << '\n';
75+
7076
std::cout << "--- Sender ---\n";
7177
auto sock = net::udp_socket<net::ip_version::v4>();
7278

7379
auto str = std::string("Hello async UDP world!");
74-
// sock.send("127.0.0.1", 4433, net::span{str});
75-
auto first_send = sock.promised_send("127.0.0.1", 4433, net::span{str});
80+
// sock.send(net::endpoint_v4("127.0.0.1", port), net::span{str});
81+
auto first_send = sock.promised_send(net::endpoint_v4("127.0.0.1", port), net::span{str});
7682
first_send.wait();
7783
std::cout << "First message send\n";
7884

79-
sock.send("127.0.0.1", 4433, net::span{"KekW"});
85+
sock.send(net::endpoint_v4("127.0.0.1", port), net::span{"KekW"});
8086
std::cout << "Second message send\n";
8187

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

84-
sock.send("127.0.0.1", 4433, net::span{"Third message"});
90+
sock.send(net::endpoint_v4("127.0.0.1", port), net::span{"Third message"});
8591
std::cout << "Last message sent!\n";
8692

87-
net::async_run();
93+
// net::async_run();
94+
io_loop.join();
8895
}
8996
}

0 commit comments

Comments
 (0)