├── .clang-format ├── .gitignore ├── LICENSE ├── README.md ├── compile_flags.txt ├── example ├── Makefile ├── async_tcp_example.cpp ├── async_tls_example.cpp ├── async_udp_example.cpp ├── options_example.cpp ├── span_example.cpp ├── tcp_example.cpp ├── tls_example.cpp └── udp_example.cpp └── include └── socketwrapper ├── awaitable.hpp ├── detail ├── base_socket.hpp ├── callbacks.hpp ├── event_loop.hpp ├── event_notifier.hpp ├── event_notifier_epoll.hpp ├── event_notifier_kqueue.hpp ├── socket_option.hpp ├── threadpool.hpp └── utility.hpp ├── endpoint.hpp ├── socketwrapper.hpp ├── span.hpp ├── task.hpp ├── tcp.hpp ├── tls.hpp ├── udp.hpp └── utility.hpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | AccessModifierOffset: -4 4 | AlignAfterOpenBracket: DontAlign 5 | # AlignArrayOfStructures: None 6 | AlignConsecutiveAssignments: None 7 | AlignConsecutiveDeclarations: None 8 | AlignConsecutiveMacros: None 9 | AlignEscapedNewlines: Right 10 | AlignOperands: DontAlign 11 | AlignTrailingComments: false 12 | AllowAllConstructorInitializersOnNextLine: false 13 | AllowShortBlocksOnASingleLine: Never 14 | AllowShortCaseLabelsOnASingleLine: false 15 | AllowShortEnumsOnASingleLine: false 16 | AllowShortFunctionsOnASingleLine: None 17 | AllowShortIfStatementsOnASingleLine: Never 18 | AllowShortLambdasOnASingleLine: All 19 | AllowShortLoopsOnASingleLine: false 20 | AllowAllParametersOfDeclarationOnNextLine: false 21 | AlwaysBreakBeforeMultilineStrings: false 22 | AlwaysBreakTemplateDeclarations: Yes 23 | BinPackArguments: false 24 | BinPackParameters: false 25 | BitFieldColonSpacing: Both 26 | # BreakBeforeBraces: Allman 27 | BreakBeforeBraces: Custom 28 | BraceWrapping: 29 | AfterCaseLabel: false 30 | AfterClass: true 31 | AfterControlStatement: Always 32 | AfterEnum: true 33 | AfterFunction: true 34 | AfterNamespace: false 35 | AfterStruct: true 36 | AfterUnion: true 37 | AfterExternBlock: false 38 | BeforeCatch: true 39 | BeforeElse: true 40 | BeforeLambdaBody: true 41 | BeforeWhile: false 42 | SplitEmptyFunction: false 43 | SplitEmptyRecord: false 44 | SplitEmptyNamespace: false 45 | BreakBeforeConceptDeclarations: true 46 | BreakBeforeTernaryOperators: true 47 | BreakConstructorInitializers: BeforeComma # BeforeColon 48 | BreakStringLiterals: true 49 | ColumnLimit: 120 50 | CompactNamespaces: false 51 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 52 | Cpp11BracedListStyle: true 53 | # EmptyLineAfterAccessModifier: Always 54 | EmptyLineBeforeAccessModifier: Always 55 | FixNamespaceComments: true 56 | IncludeBlocks: Preserve 57 | # IndentAccessModifiers: false 58 | IndentCaseBlocks: false 59 | IndentCaseLabels: true 60 | IndentExternBlock: Indent 61 | IndentGotoLabels: false 62 | IndentPPDirectives: None 63 | IndentRequires: false 64 | IndentWidth: 4 65 | InsertTrailingCommas: None 66 | # LambdaBodyIndentation: Signature 67 | NamespaceIndentation: None 68 | PointerAlignment: Left 69 | # ReferenceAlignment: Left 70 | # SortIncludes: Never 71 | SortUsingDeclarations: false 72 | SpaceAfterCStyleCast: true 73 | SpaceAfterLogicalNot: false 74 | SpaceAfterTemplateKeyword: true 75 | SpaceBeforeParens: ControlStatements 76 | SpaceBeforeAssignmentOperators: true 77 | SpaceBeforeCaseColon: false 78 | SpaceBeforeCpp11BracedList: false 79 | SpaceBeforeCtorInitializerColon: true 80 | SpaceBeforeInheritanceColon: true 81 | SpaceBeforeRangeBasedForLoopColon: true 82 | SpaceBeforeSquareBrackets: false 83 | SpaceInEmptyParentheses: false 84 | # SpacesInAngles: Never 85 | SpacesInCStyleCastParentheses: false 86 | SpacesInConditionalStatement: false 87 | SpacesInContainerLiterals: false 88 | SpacesInParentheses: false 89 | SpacesInSquareBrackets: false 90 | Standard: Latest 91 | ... 92 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ./src/*.o 2 | *.o 3 | *.a 4 | *.so 5 | .idea 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 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Timo Glane 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Socketwrapper - Simple to use linux socket/networking library 2 | [Documentation is work in progress] 3 | 4 | Currently this is a header-only library containing classes for TCP and UDP 5 | network connections. There are also classes for TLS encrypted TCP sockets, which requires to link 6 | against OpenSSL (use the compile flags `-lssl -lcrypto`) and some other utility 7 | functions. 8 | 9 | The only requirements are a C++17 compliant compiler (make sure to compile with 10 | this version!), and `pthreads` (you need to link with `lpthread`) and OpenSSL 11 | (but only if you use the `tls.hpp` header). 12 | 13 | There are some examples for all socket/connection types in the `examples` directory. 14 | 15 | ## Asyncronous functionality: 16 | *TODO Describe the design of the asynchronous system* 17 | 18 | ## Class Documentation: 19 | All of the following classes and enum classes live in the namespace `net`. 20 | Socket/connection classes all are not copyable but moveable and templated to distinguish between IPv4 and IPv6 by using a enum class: 21 | ```cpp 22 | enum class ip_version 23 | { 24 | v4, 25 | v6 26 | }; 27 | ``` 28 | 29 | ```cpp 30 | enum class socket_type : uint8_t 31 | { 32 | unspecified = AF_UNSPEC, 33 | stream = SOCK_STREAM, 34 | datagram = SOCK_DGRAM 35 | }; 36 | ``` 37 | 38 | ```cpp 39 | enum class option_level : int 40 | { 41 | socket = SOL_SOCKET, 42 | ipv4 = IPPROTO_IP 43 | ipv6 = IPPROTO_IPV6 44 | tcp = IPPROTO_TCP 45 | }; 46 | ``` 47 | 48 | ### span 49 | >#include "socketwrapper/span.hpp" (also included by all socket headers) 50 | 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. 53 | The interface of the span class is the same as for most std container classes (providing begin(), end(), front(), back(), empty(), size(), get(), data()). 54 | Methods: 55 | - Constructor: 56 | ```cpp 57 | span(T* start, size_t length) noexcept; 58 | 59 | span(T* start, T* end) noexcept; 60 | 61 | span(T (&buffer)[S]) noexcept; 62 | 63 | // Create a span from a start and an end iterator. 64 | span(ITER start, ITER end) noexcept; 65 | 66 | // Create a span from a class that provides the same interface as the std container classes. 67 | span(CONTAINER&& con) noexcept; 68 | ``` 69 | 70 | ### endpoint 71 | >#include "socketwrapper/endpoint.hpp" 72 | 73 | Represents a endpoint of a IP/socket connection. 74 | Methods: 75 | - Constructor: 76 | ```cpp 77 | // Constructs an endpoint from a string representation of a IP address, a port and the type of the connection (stream or datagram) 78 | endpoint(std::string_view address_string, uint16_t port, socket_type connection_type); 79 | 80 | // Constructs an endpoint from a POSIX struct sockaddr_in 81 | endpoint(const sockaddr_in& address); 82 | 83 | // Constructs an endpoint from a POSIX struct sockaddr_in6 84 | endpoint(const sockaddr_in6& address); 85 | ``` 86 | - Accessor: 87 | ```cpp 88 | // Returns the IP address of the represented endpoint in string representation 89 | const std::string& get_addr_string() const; 90 | 91 | // Returns the port of the represented endpoint as a uint16_t 92 | uint16_t get_port() const; 93 | 94 | // Returns information of the endpoint represented by a const reference to a POSIX struct sockaddr 95 | const sockaddr& get_addr() const; 96 | 97 | // Returns information of the endpoint represented by a reference to a POSIX struct sockaddr 98 | sockaddr& get_addr(); 99 | ``` 100 | 101 | ### option 102 | This class is used in the methods ```base_socket::get_option```, ```base_socket::get_option_value``` and ```base_socket::set_option``` to set and get socket options. 103 | It is included implicitly with the class ```base_socket```. 104 | 105 | Methods: 106 | - Constructors: 107 | ```cpp 108 | option() = default; 109 | 110 | option(int value); 111 | ``` 112 | - Accessors/Modifiers: 113 | ```cpp 114 | size_t size() const; 115 | 116 | int name() const; 117 | 118 | option_level level() const; 119 | 120 | int level_native() const; 121 | 122 | const int* value() const; 123 | 124 | int* value(); 125 | ``` 126 | 127 | Valid template specializations for parameter T are: 128 | * int 129 | * bool 130 | * linger 131 | * sockaddr 132 | 133 | ### base_socket 134 | This class is implicitly included with every socket class that inherits from the class ```base_socket```. 135 | 136 | Represents the basic functionalities of the native socket handle. The other high-level socket abstractions are all dervived from this class. 137 | Methods: 138 | - Set/get socket options: 139 | ```cpp 140 | // Set a socket option where the option is represented by a valid template specialization of net::option 141 | template ::value, bool>> 143 | void set_option(OPTION_TYPE&& opt_val); 144 | 145 | // Get a current socket option where the option type needs to be a valid template specialization of net::option 146 | template ::value, bool>> 148 | OPTION_TYPE get_option() const 149 | 150 | // Get the current value of a socket option where the option type needs to be a valid template specialization of net::option 151 | template ::value, bool>> 153 | typename OPTION_TYPE::value_type get_option_value() const 154 | ``` 155 | - Other: 156 | ```cpp 157 | // Get the underlying socket handle 158 | int get() const; 159 | 160 | // Get the ip version of the represented socket 161 | ip_version family() const; 162 | ``` 163 | 164 | ### tcp_connection : public base_socket 165 | >#include "socketwrapper/tcp.hpp" 166 | 167 | Represents a TCP connection that can either be constructed with the IP address and port of the remote host or by a `tcp_acceptor`s accept method. 168 | Methods: 169 | - Constructor: 170 | ```cpp 171 | // Default constructor of a not connected tcp connection 172 | tcp_connection(); 173 | 174 | // Construct a tcp connection from a net::endpoint 175 | tcp_connection(const endpoint& endpoint); 176 | ``` 177 | - Config: 178 | ```cpp 179 | // Connect a default constructed socket to a given endpoint 180 | void connect(const endpoint& endpoint); 181 | ``` 182 | - Reading: 183 | ```cpp 184 | // Read as much bytes as fit into buffer and block until the read operation finishes. 185 | size_t read(net::spanbuffer) const; 186 | 187 | // Read as much bytes as fit into buffer and block until the read operation finishes or the delay is over. 188 | size_t read(net::span buffer, const std::chrono::duration& delay) const; 189 | 190 | // Immediately return and call the callback function after there is data available. 191 | void async_read(net::span buffer, CALLBACK_TYPE&& callback) const; 192 | 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> async_read(net::span buffer) const; 196 | 197 | // Immediately return and get a future to get the number of elements received at a later timepoint 198 | std::future promised_read(net::span buffer) const; 199 | ``` 200 | - Sending: 201 | ```cpp 202 | // Sends all data that is stored in the given buffer and blocks until all data is sent. 203 | size_t send(net::span buffer) const; 204 | 205 | // 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. 206 | void async_send(net::span buffer, CALLBACK_TYPE&& callback) const; 207 | 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> async_send(net::span buffer) const; 211 | 212 | // Immediately return and get a future to get the number of elements written at a later point in time 213 | std::future promised_send(net::span buffer) const; 214 | ``` 215 | - Shorthand identifier: 216 | ```cpp 217 | using tcp_connection_v4 = tcp_connection; 218 | using tcp_connection_v6 = tcp_connection; 219 | ``` 220 | 221 | ### tcp_acceptor public base_socket 222 | >#include "socketwrapper/tcp.hpp" 223 | 224 | Represents a listening TCP socket that accepts incoming connections. Returns a `tcp_connection` for each accepted connection. 225 | Methods: 226 | - Constructor: 227 | ```cpp 228 | // Default constructor of a non-bound tcp acceptor 229 | tcp_acceptor(); 230 | 231 | // Immediately creates a socket that listens on the given address and port with a connection backlog of `backlog` 232 | tcp_acceptor(const endpoint& endpoint, const size_t backlog = 5); 233 | ``` 234 | - Config: 235 | ```cpp 236 | // Bind a non-bound acceptor to a internal endpoint and set the socket in listening state 237 | void activate(const endpoint& endpoint, const size_t backlog = 5); 238 | ``` 239 | - Accepting: 240 | ```cpp 241 | // Blocks until a connection request is available and returns a constructed and connected tcp_connection instance 242 | tcp_connection accept() const; 243 | 244 | // Blocks until a connection request is available or the delay is over and returns a constructed and connected tcp_connection instance or std::nullopt(if no connection was established) 245 | std::optional> accept(const std::chrono::duration& delay) const; 246 | 247 | // Immediately returns and invokes the callback when a new connection is established 248 | void async_accept(CALLBACK_TYPE&& callback) const; 249 | 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_acceptor::stream_accept_operation> async_accept() const; 253 | 254 | // Immediately return and get a future to access the accepted socket at a later point in time 255 | std::future> promised_accept() const; 256 | ``` 257 | - Shorthand identifier: 258 | ```cpp 259 | using tcp_acceptor_v4 = tcp_acceptor; 260 | using tcp_acceptor_v6 = tcp_acceptor; 261 | ``` 262 | 263 | ### tls_connection : public tcp_connection 264 | >#include "socketwrapper/tls.hpp" 265 | 266 | Represents a TLS encrypted TCP connection that can either be constructed with the IP address and port of the remote host or by a `tcp_acceptor`s accept method. 267 | Methods: 268 | - Constructor: 269 | ```cpp 270 | // Construct a non connected tls connection 271 | tls_connection(std::string_view cert_path, std::string_view key_path); 272 | 273 | // Construct a tls connection from an endpoint and immediately connect it 274 | tls_connection(std::string_view cert_path, std::string_view key_path, const endpoint& endpoint); 275 | ``` 276 | - Reading: 277 | Same interface as `tcp_connection` 278 | - Writing: 279 | Same interface as `tcp_connection` 280 | - Shorthand identifier: 281 | ```cpp 282 | using tls_connection_v4 = tls_connection; 283 | using tls_connection_v6 = tls_connection; 284 | ``` 285 | 286 | ### tls_acceptor : public tcp_acceptor 287 | >#include "socketwrapper/tls.hpp" 288 | Represents a listening TCP socket with TLS encryption that accepts incoming connections. Returns a `tcp_connection` for each accepted connection. 289 | Methods: 290 | - Constructor: 291 | ```cpp 292 | // Construct a non-bound tls_acceptor 293 | tls_acceptor(std::string_view cert_path, std::string_view key_path); 294 | 295 | // Construct a tls acceptor from an endpoint and set it into listening state 296 | tls_acceptor(std::string_view cert_path, std::string_view key_path, const endpoint& endpoint); 297 | ``` 298 | - Accepting: 299 | Same interface as `tcp_acceptor` 300 | - Shorthand identifier: 301 | ```cpp 302 | using tls_acceptor_v4 = tls_acceptor; 303 | using tls_acceptor_v6 = tls_acceptor; 304 | ``` 305 | 306 | ### udp_socket : public base_socket 307 | >#include "socketwrapper/udp.hpp" 308 | 309 | Represents an UDP socket that can either be in "server" or "client" position. 310 | Methods: 311 | - Constructor: 312 | ```cpp 313 | // Creates a non-bound UDP socket that is ready to send data but can not receive data. 314 | udp_socket(); 315 | 316 | // Creates a UDP socket that is bound to a given endpoint so it can send and receive data directly after construction 317 | udp_socket(const endpoint& endpoint); 318 | ``` 319 | - Config: 320 | ```cpp 321 | // Bind a non-bound udp socket to a given endpoint so that it is able to receive data afterwards 322 | void bind(const endpoint& endpoint); 323 | ``` 324 | - Reading: 325 | ```cpp 326 | // Block until data is read into the given buffer. Reads max the amount of elements that fits into the buffer. 327 | std::pair> read(span buffer) const; 328 | 329 | // Block until data is read into the given buffer or the delay is over. Reads max the amount of elements that fits into the buffer. 330 | std::pair>> read(span buffer, const std::chrono::duration& delay) const; 331 | 332 | // Immediately return and invoke the callback when data is read into the buffer. Caller is responsible to keep the underlying buffer alive. 333 | void async_read(span buffer, CALLBACK_TYPE&& callback) const; 334 | 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>>, net::udp_socket::dgram_read_operation> async_read(span buffer) const; 338 | 339 | // 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 340 | std::future>> promised_read(span buffer) const; 341 | ``` 342 | - Writing: 343 | ```cpp 344 | // Send all data in the given buffer to a remote endpoint. 345 | size_t send(const endpoint& endpoint_to, span buffer) const; 346 | 347 | // Immediately return and invoke the callback after the data is sent to a remote represented by the given address and port parameter. 348 | void async_send(const endpoint& endpoint_to, span buffer, CALLBACK_TYPE&& callback) const; 349 | 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> async_write(const endpoint& endpoint_to, span buffer) const; 353 | 354 | // Immediately return and get a future to get the number of elements written at a later point in time 355 | std::future promised_send(const endpoint& endpoint_to, span buffer) const; 356 | ``` 357 | - Shorthand identifier: 358 | ```cpp 359 | using udp_socket_v4 = udp_socket; 360 | using udp_socket_v6 = udp_socket; 361 | ``` 362 | 363 | ### task 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 example(size_t input) 376 | { 377 | co_return input * 2; 378 | } 379 | 380 | net::task example_two() 381 | { 382 | auto number = co_await example(5); 383 | } 384 | ``` 385 | 386 | ## Utility Functions: 387 | >#include "socketwrapper/utility.hpp" 388 | 389 | All of the following functions live in the namespace `net` 390 | 391 | - Change byte order: 392 | ```cpp 393 | // Change byte order from little-endian to big-endian 394 | template 395 | inline constexpr T to_big_endian(T little); 396 | 397 | // Change byte order from big-endian to little-endian 398 | template 399 | inline constexpr T to_little_endian(T big); 400 | 401 | // Change byteorder from host byte order to network byte order if they differ 402 | template 403 | inline constexpr T host_to_network(T in); 404 | 405 | // Change byteorder from network byte order to host byte order if they differ 406 | template 407 | inline constexpr T network_to_host(T in); 408 | ``` 409 | 410 | ## Runtime helper functions: 411 | This functions are implicitly included with every socket header file. 412 | 413 | - Run the asynchronous context until all callbacks are handled: 414 | ```cpp 415 | // Blocks until all registered async operations are handled and all completion handlers finished execution. 416 | void async_run(); 417 | ``` 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 422 | return_type block_on(net::task 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 430 | std::future spawn(net::task awaitable_task); 431 | ``` 432 | -------------------------------------------------------------------------------- /compile_flags.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tglane/socketwrapper/b1d915f5a5ee8bf2d7c9930b9335084045fe8989/compile_flags.txt -------------------------------------------------------------------------------- /example/Makefile: -------------------------------------------------------------------------------- 1 | # cc = g++ 2 | cc = clang++ 3 | 4 | CFLAGS = -std=c++20 -fpic -Wall -Werror -Wextra -pedantic -g -fsanitize=undefined -fsanitize=address 5 | # -fsanitize=thread 6 | 7 | LDFLAGS = -lpthread 8 | 9 | %.o: %.cpp 10 | $(cc) -c $< -o $@ $(CFLAGS) 11 | 12 | .PHONY: all 13 | all: tls async_tls tcp async_tcp udp async_udp coroutine_udp span options 14 | 15 | .PHONY: tls 16 | tls: tls_example.cpp 17 | $(cc) -o tls_example $^ $(CFLAGS) $(LDFLAGS) -lssl -lcrypto 18 | 19 | PHONY: async_tls 20 | async_tls: async_tls_example.cpp 21 | $(cc) -o async_tls_example $^ $(CFLAGS) $(LDFLAGS) -lssl -lcrypto 22 | 23 | .PHONY: tcp 24 | tcp: tcp_example.cpp 25 | $(cc) -o tcp_example $^ $(CFLAGS) $(LDFLAGS) 26 | 27 | .PHONY: async_tcp 28 | async_tcp: async_tcp_example.cpp 29 | $(cc) -o async_tcp_example $^ $(CFLAGS) $(LDFLAGS) 30 | 31 | .PHONY: coroutine_tcp 32 | coroutine_tcp: coroutine_tcp_example.cpp 33 | $(cc) -o coroutine_tcp_example $^ $(CFLAGS) $(LDFLAGS) 34 | 35 | .PHONY: udp 36 | udp: udp_example.cpp 37 | $(cc) -o udp_example $^ $(CFLAGS) $(LDFLAGS) 38 | 39 | .PHONY: async_udp 40 | async_udp: async_udp_example.cpp 41 | $(cc) -o async_udp_example $^ $(CFLAGS) $(LDFLAGS) 42 | 43 | .PHONY: coroutine_udp 44 | coroutine_udp: coroutine_udp_example.cpp 45 | $(cc) -o coroutine_udp_example $^ $(CFLAGS) $(LDFLAGS) 46 | 47 | .PHONY: span 48 | span: span_example.cpp 49 | $(cc) -o span_example $^ $(CFLAGS) $(LDFLAGS) 50 | 51 | .PHONY: options 52 | options: options_example.cpp 53 | $(cc) -o options_example $^ $(CFLAGS) $(LDFLAGS) 54 | 55 | .PHONY: clean 56 | clean: 57 | rm tls_example 58 | rm async_tls_example 59 | rm tcp_example 60 | rm async_tcp_example 61 | rm udp_example 62 | rm async_udp_example 63 | rm span_example 64 | rm options_example 65 | -------------------------------------------------------------------------------- /example/async_tcp_example.cpp: -------------------------------------------------------------------------------- 1 | #include "../include/socketwrapper/tcp.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | extern "C" { 8 | int pid_shutdown_sockets(int, int); 9 | } 10 | 11 | int main(int argc, char** argv) 12 | { 13 | if (argc <= 1) 14 | return 0; 15 | 16 | if (strcmp(argv[1], "r") == 0) 17 | { 18 | std::cout << "--- Receiver ---\n"; 19 | auto acceptor = net::tcp_acceptor(net::endpoint_v4("0.0.0.0", 4433)); 20 | auto acceptor_two = net::tcp_acceptor(net::endpoint_v4("0.0.0.0", 4556)); 21 | 22 | std::cout << "Waiting for accept\n"; 23 | 24 | // Make sure to prevent calling the callback on a moved-from (invalid) socket if you use references to async 25 | // sockets 26 | auto conns = std::vector>(); 27 | conns.reserve(2); 28 | 29 | acceptor.async_accept( 30 | [&acceptor, &conns](net::tcp_connection&& conn, std::exception_ptr ex) 31 | { 32 | auto buffer_one = std::array{}; 33 | auto buffer_two = std::array{}; 34 | 35 | std::cout << "Accepted\n"; 36 | if (ex != nullptr) 37 | { 38 | std::cout << "But with error so not really accepted :(\n"; 39 | return; 40 | } 41 | 42 | conns.push_back(std::move(conn)); 43 | auto& sock = conns.back(); 44 | 45 | auto read_fut_one = sock.promised_read(net::span{buffer_one}); 46 | 47 | acceptor.async_accept( 48 | [&conns](net::tcp_connection&& conn, std::exception_ptr) 49 | { 50 | std::cout << "Accepted again\n"; 51 | std::array buffer; 52 | conns.push_back(std::move(conn)); 53 | auto& sock = conns.back(); 54 | 55 | const auto read_result = sock.read(net::span(buffer), std::chrono::milliseconds(2000)); 56 | if (read_result.has_value()) 57 | { 58 | std::cout << "Received from second accept-read: " << *read_result << "bytes -- " 59 | << std::string_view(buffer.data(), *read_result) << '\n'; 60 | } 61 | auto result = sock.promised_read(net::span(buffer)); 62 | auto br = result.get(); 63 | std::cout << "Received from second accept-read: " << br << "bytes -- " 64 | << std::string_view(buffer.data(), br) << '\n'; 65 | 66 | // sock.async_read(net::span{buffer}, 67 | // [&buffer](size_t br, std::exception_ptr) { 68 | // std::cout << "Nested received: " << br << " bytes from inner " 69 | // << std::string_view{buffer.data(), br} << '\n'; 70 | // }); 71 | }); 72 | 73 | // Read data from buffer when read promise is resolved 74 | size_t bytes_read = read_fut_one.get(); 75 | std::cout << "Promised read resolved! Read " << bytes_read << " bytes from future one. -- " 76 | << std::string_view{buffer_one.data(), bytes_read} << '\n'; 77 | 78 | sock.async_read(net::span{buffer_two}, 79 | [&buffer_two](size_t br, std::exception_ptr) { 80 | std::cout << "Received in callback: " << br << " - " << std::string_view{buffer_two.data(), br} 81 | << '\n'; 82 | }); 83 | }); 84 | 85 | std::cout << "Wait for handlers to finish ...\n"; 86 | net::async_run(); 87 | } 88 | else if (strcmp(argv[1], "s") == 0) 89 | { 90 | std::cout << "--- Sender ---\n"; 91 | { 92 | auto sock = net::tcp_connection(net::endpoint_v4("127.0.0.1", 4433)); 93 | std::cout << "Connected\n"; 94 | auto vec = std::vector{'H', 'e', 'l', 'l', 'o'}; 95 | 96 | std::this_thread::sleep_for(std::chrono::milliseconds(2000)); 97 | std::string_view buffer{"Hello String_view-World"}; 98 | sock.send(net::span{buffer.begin(), buffer.end()}); 99 | std::cout << "Sent first connection first message\n"; 100 | 101 | std::this_thread::sleep_for(std::chrono::milliseconds(1000)); 102 | sock.send(net::span{std::string_view{"Test, test, 123"}}); 103 | std::cout << "Sent first connection second message\n"; 104 | } 105 | { 106 | auto sock = net::tcp_connection(net::endpoint_v4("127.0.0.1", 4433)); 107 | std::cout << "Connected again\n"; 108 | std::vector vec{'H', 'e', 'l', 'l', 'o'}; 109 | 110 | auto send_fut = sock.promised_send(net::span{"Promised to say hello"}); 111 | send_fut.wait(); 112 | std::cout << "Sent second connection first message\n"; 113 | 114 | std::this_thread::sleep_for(std::chrono::milliseconds(1000)); 115 | 116 | // std::this_thread::sleep_for(std::chrono::milliseconds(2000)); 117 | std::string_view buffer{"Hello world from the second accepted connection!"}; 118 | // sock.async_send(net::span{buffer}, [](size_t, std::exception_ptr) { std::cout << "Async message sent\n"; 119 | // }); 120 | sock.send(net::span{buffer}); 121 | // sock.promised_send(net::span{buffer}).get(); 122 | std::cout << "Sent second connection second message\n"; 123 | 124 | net::async_run(); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /example/async_tls_example.cpp: -------------------------------------------------------------------------------- 1 | #include "../include/socketwrapper/tls.hpp" 2 | #include "../include/socketwrapper/utility.hpp" 3 | #include 4 | #include 5 | #include 6 | 7 | int main(int argc, char** argv) 8 | { 9 | if (argc <= 1) 10 | return 0; 11 | 12 | if (strcmp(argv[1], "r") == 0) 13 | { 14 | std::cout << "--- Receiver ---\n"; 15 | auto acceptor = net::tls_acceptor_v4("./cert.pem", "./key.pem", net::endpoint_v4("0.0.0.0", 4433)); 16 | 17 | auto sock_fut = acceptor.promised_accept(); 18 | std::cout << "Got future socket\n"; 19 | auto conn = sock_fut.get(); 20 | auto buffer = std::array{}; 21 | conn.async_read(net::span{buffer}, 22 | [&buffer](size_t br, std::exception_ptr) { 23 | std::cout << "Received " << br << " bytes -- " << std::string_view{buffer.data(), br} << '\n'; 24 | }); 25 | 26 | net::async_run(); 27 | } 28 | else if (strcmp(argv[1], "s") == 0) 29 | { 30 | std::cout << "--- Sender ---\n"; 31 | auto tls_sock = 32 | net::tls_connection("./cert.pem", "./key.pem", net::endpoint_v4("127.0.0.1", 4433)); 33 | 34 | tls_sock.promised_send(net::span{"Hello world"}).get(); 35 | std::cout << "TLS encrypted message sent\n"; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /example/async_udp_example.cpp: -------------------------------------------------------------------------------- 1 | #include "../include/socketwrapper/udp.hpp" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | int main(int argc, char** argv) 8 | { 9 | if (argc <= 1) 10 | return 0; 11 | 12 | if (strcmp(argv[1], "r") == 0) 13 | { 14 | std::cout << "--- Receiver ---\n"; 15 | auto sock = net::udp_socket(net::endpoint_v4("0.0.0.0", 4433)); 16 | 17 | auto buffer = std::array{}; 18 | 19 | // Test the read timeout 20 | std::cout << "Started receiving\n"; 21 | const auto result = sock.read(net::span{buffer}, std::chrono::milliseconds(10000)); 22 | if (result.has_value()) 23 | { 24 | const auto& [br, from] = result.value(); 25 | std::cout << "Received within timeframe: " << std::string_view{buffer.data(), br} << '\n'; 26 | } 27 | else 28 | { 29 | std::cout << "Received no data in the specified timeframe\n"; 30 | } 31 | 32 | sock.async_read(net::span{buffer}, 33 | [&sock, &buffer](std::pair result, std::exception_ptr) 34 | { 35 | std::cout << "1. Received " << result.first << " bytes. -- " 36 | << std::string_view{buffer.data(), result.first} << '\n'; 37 | 38 | sock.async_read(net::span{buffer}, 39 | [&sock, &buffer](std::pair result, std::exception_ptr) 40 | { 41 | std::cout << "2. Received " << result.first << " bytes. -- " 42 | << std::string_view{buffer.data(), result.first} << '\n'; 43 | 44 | sock.async_read(net::span{buffer}, 45 | [&sock, &buffer](std::pair result, std::exception_ptr) 46 | { 47 | std::cout << "Inner received " << result.first << " bytes. -- " 48 | << std::string_view{buffer.data(), result.first} << '\n'; 49 | 50 | auto read_fut = sock.promised_read(net::span{buffer}); 51 | auto read_res = read_fut.get(); 52 | std::cout << "Promised inner read: " << std::string_view(buffer.data(), read_res.first) 53 | << '\n'; 54 | 55 | sock.async_read(net::span{buffer}, 56 | [&buffer](std::pair result, std::exception_ptr) 57 | { 58 | std::cout << "Nested inner received " << result.first << " bytes. -- " 59 | << std::string_view{buffer.data(), result.first} << '\n'; 60 | }); 61 | }); 62 | }); 63 | }); 64 | 65 | std::cout << "Waiting ...\n"; 66 | net::async_run(); 67 | std::cout << "All async events handled\n"; 68 | } 69 | else if (strcmp(argv[1], "s") == 0) 70 | { 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 | 76 | std::cout << "--- Sender ---\n"; 77 | auto sock = net::udp_socket(); 78 | 79 | auto str = std::string("Hello async UDP world!"); 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}); 82 | first_send.wait(); 83 | std::cout << "First message send\n"; 84 | 85 | sock.send(net::endpoint_v4("127.0.0.1", port), net::span{"KekW"}); 86 | std::cout << "Second message send\n"; 87 | 88 | std::this_thread::sleep_for(std::chrono::milliseconds(2000)); 89 | 90 | sock.send(net::endpoint_v4("127.0.0.1", port), net::span{"Third message"}); 91 | std::cout << "Last message sent!\n"; 92 | 93 | // net::async_run(); 94 | io_loop.join(); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /example/options_example.cpp: -------------------------------------------------------------------------------- 1 | #include "../include/socketwrapper/tcp.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | int main() 10 | { 11 | std::cout << "--- Options example ---\n"; 12 | 13 | auto acceptor = net::tcp_acceptor_v4(net::endpoint_v4("0.0.0.0", 4433)); 14 | 15 | // Set and get socket option example socket option for receive buffer size 16 | acceptor.set_option(net::option{10000}); 17 | auto recv_buff_size = acceptor.get_option_value>(); 18 | std::cout << "Recvbuff size for accepting socket: " << recv_buff_size << '\n'; 19 | 20 | acceptor.async_accept( 21 | [](auto sock, std::exception_ptr) 22 | { 23 | auto buffer = std::array{}; 24 | auto len = sock.read(net::span(buffer)); 25 | std::cout << "Message read: " << std::string_view(buffer.data(), len) << '\n'; 26 | }); 27 | 28 | auto test_con = net::tcp_connection_v4(net::endpoint_v4("127.0.0.1", 4433, net::socket_type::stream)); 29 | test_con.send(net::span{std::string_view{"Hello world"}}); 30 | 31 | // Get the peer security ctx 32 | // auto peer_ctx_opt = test_con.get_option>(); 33 | // std::cout << peer_ctx_opt.value() << '\n'; 34 | 35 | std::this_thread::sleep_for(std::chrono::milliseconds(1000)); 36 | } 37 | -------------------------------------------------------------------------------- /example/span_example.cpp: -------------------------------------------------------------------------------- 1 | #include "../include/socketwrapper/span.hpp" 2 | #include 3 | #include 4 | #include 5 | 6 | int main() 7 | { 8 | int ix[5] = {3, 5, 3, 77, 11}; 9 | auto s_one = net::span{ix}; 10 | 11 | std::cout << "Size of span from int[5]: " << s_one.size() << '\n'; 12 | 13 | auto empty_vec = std::vector{}; 14 | auto empty_span = net::span{empty_vec}; 15 | std::cout << "empty_span.empty() = " << empty_span.empty() << '\n'; 16 | 17 | auto str = std::string{"Hello World"}; 18 | auto s_two = net::span{str.begin(), str.end()}; 19 | 20 | auto s_three = net::span{str.c_str(), 4}; 21 | 22 | auto vec = std::vector{'H', 'e', 'l', 'l', 'o'}; 23 | auto s_four = net::span{vec}; 24 | std::cout << *std::prev(std::end(s_four)) << '\n' << std::endl; 25 | for (const auto& it : s_four) 26 | { 27 | std::cout << it << '\n'; 28 | } 29 | 30 | auto s_five = net::span{&(ix[0]), &(ix[4])}; 31 | 32 | auto s_six = net::span{str}; 33 | } 34 | -------------------------------------------------------------------------------- /example/tcp_example.cpp: -------------------------------------------------------------------------------- 1 | #include "../include/socketwrapper/tcp.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | int main(int argc, char** argv) 8 | { 9 | if (argc <= 1) 10 | return 0; 11 | 12 | if (strcmp(argv[1], "r") == 0) 13 | { 14 | std::cout << "--- Receiver ---\n"; 15 | 16 | auto buffer = std::array{}; 17 | auto acceptor = net::tcp_acceptor_v4(net::endpoint_v4("0.0.0.0", 4433)); 18 | 19 | std::cout << "Waiting for accept\n"; 20 | auto opt = acceptor.accept(std::chrono::milliseconds(5000)); 21 | if (!opt) 22 | { 23 | std::cout << "No acception\n"; 24 | return 0; 25 | } 26 | const auto& sock = opt.value(); 27 | std::cout << "Accepted\n"; 28 | 29 | std::cout << "Wait for data ...\n"; 30 | size_t bytes_read = sock.read(net::span{buffer}); 31 | std::cout << "Received: " << bytes_read << " - " << std::string_view{buffer.data(), bytes_read} << '\n'; 32 | 33 | const auto read_result = sock.read(net::span{buffer}, std::chrono::milliseconds(4000)); 34 | if (read_result.has_value()) 35 | { 36 | std::cout << "Received: " << *read_result << " - " << std::string_view{buffer.data(), *read_result} << '\n'; 37 | } 38 | } 39 | else if (strcmp(argv[1], "s") == 0) 40 | { 41 | std::cout << "--- Sender ---\n"; 42 | 43 | auto sock = net::tcp_connection(); 44 | std::cout << "Socket created\n"; 45 | 46 | sock.connect(net::endpoint_v4{"127.0.0.1", 4433, net::socket_type::stream}); 47 | std::cout << "Connected\n"; 48 | 49 | std::this_thread::sleep_for(std::chrono::milliseconds(3000)); 50 | 51 | auto vec = std::vector{'H', 'e', 'l', 'l', 'o'}; 52 | // sock.send(net::span{vec}); 53 | // sock.send(net::span{std::string {"Hello World"}}); 54 | sock.send(net::span{vec.begin(), vec.end()}); 55 | std::cout << "Sent\n"; 56 | 57 | std::this_thread::sleep_for(std::chrono::milliseconds(1000)); 58 | 59 | auto buffer_view = std::string_view{"Hello String_view-World"}; 60 | sock.send(net::span{buffer_view.begin(), buffer_view.end()}); 61 | std::cout << "Sent again\n"; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /example/tls_example.cpp: -------------------------------------------------------------------------------- 1 | #include "../include/socketwrapper/tls.hpp" 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std::literals::chrono_literals; 7 | 8 | int main(int argc, char** argv) 9 | { 10 | if (argc <= 1) 11 | return 0; 12 | 13 | if (strcmp(argv[1], "r") == 0) 14 | { 15 | std::cout << "--- Receiver ---\n"; 16 | 17 | auto acceptor = net::tls_acceptor_v4("./cert.pem", "./key.pem", net::endpoint_v4("0.0.0.0", 4433)); 18 | std::cout << "Waiting for accept for 4 seconds...\n"; 19 | auto sock = acceptor.accept(4000ms); 20 | if (!sock) 21 | { 22 | std::cout << "No connection available\n"; 23 | return 0; 24 | } 25 | std::cout << "Accepted\n"; 26 | 27 | auto buffer = std::array{}; 28 | const auto read_result = sock->read(net::span{buffer.begin(), buffer.end()}, 2000ms); 29 | if (read_result.has_value()) 30 | { 31 | std::cout << "Received: " << *read_result << '\n' << std::string_view{buffer.data(), *read_result} << '\n'; 32 | } 33 | } 34 | else if (strcmp(argv[1], "s") == 0) 35 | { 36 | std::cout << "--- Sender ---\n"; 37 | auto sock = net::tls_connection_v4("./cert.pem", "./key.pem", net::endpoint_v4("127.0.0.1", 4433)); 38 | std::cout << "Connected\n"; 39 | sock.send(net::span{std::string{"Hello World"}}); 40 | std::cout << "Sent" << '\n'; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /example/udp_example.cpp: -------------------------------------------------------------------------------- 1 | #include "../include/socketwrapper/udp.hpp" 2 | #include 3 | #include 4 | #include 5 | 6 | int main(int argc, char** argv) 7 | { 8 | if (argc <= 1) 9 | return 0; 10 | 11 | if (strcmp(argv[1], "r") == 0) 12 | { 13 | std::cout << "--- Receiver ---\n"; 14 | 15 | auto sock = net::udp_socket(net::endpoint_v4("0.0.0.0", 4433)); 16 | // auto sock = net::udp_socket(net::endpoint_v4({0, 0, 0, 0}, 4433)); 17 | 18 | auto buffer = std::array{}; 19 | const auto [bytes_read, peer] = sock.read(net::span(buffer)); 20 | std::cout << "Peer addr: " << peer.get_addr_string() << "; Peer port: " << peer.get_port() 21 | << "; Bytes read: " << bytes_read << '\n'; 22 | std::cout << std::string_view(buffer.data(), bytes_read) << '\n'; 23 | 24 | const auto read_result = sock.read(net::span{buffer}, std::chrono::milliseconds(4000)); 25 | if (read_result.has_value()) 26 | { 27 | const auto& [bytes_read, peer_opt] = read_result.value(); 28 | std::cout << "Peer addr: " << peer_opt.get_addr_string() << "; Peer port: " << peer_opt.get_port() 29 | << "; Bytes read: " << bytes_read << '\n'; 30 | std::cout << std::string_view{buffer.data(), bytes_read} << '\n'; 31 | } 32 | else 33 | { 34 | std::cout << "No message received :(\n"; 35 | } 36 | } 37 | else if (strcmp(argv[1], "s") == 0) 38 | { 39 | std::cout << "--- Sender ---\n"; 40 | 41 | auto sock = net::udp_socket(); 42 | 43 | auto buffer = std::string{"Hello world"}; 44 | const auto endpoint = net::endpoint_v4("127.0.0.1", 4433); 45 | sock.send(endpoint, net::span(buffer)); 46 | std::cout << "All messages sent." << std::endl; 47 | 48 | std::this_thread::sleep_for(std::chrono::milliseconds(2000)); 49 | 50 | auto vec = std::vector{'A', 'B', 'C'}; 51 | sock.send(net::endpoint_v4(std::array{127, 0, 0, 1}, 4433), net::span(vec)); 52 | sock.send(net::endpoint_v4(std::array{127, 0, 0, 1}, 4433), net::span("KekWWW")); 53 | std::cout << "All messages sent. Again." << std::endl; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /include/socketwrapper/awaitable.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SOCKETWRAPPER_NET_AWAITABLE_HPP 2 | #define SOCKETWRAPPER_NET_AWAITABLE_HPP 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "detail/event_loop.hpp" 10 | 11 | namespace net { 12 | 13 | /// Awaitable type for all network related I/O operations of this library 14 | template 15 | class op_awaitable 16 | { 17 | operation_type m_operation; 18 | detail::event_type m_event; 19 | int m_fd; 20 | 21 | public: 22 | using operation = operation_type; 23 | 24 | op_awaitable(int fd, operation_type op, detail::event_type event) 25 | : m_operation(std::move(op)) 26 | , m_event{event} 27 | , m_fd(fd) 28 | {} 29 | 30 | ~op_awaitable() 31 | { 32 | // If we have not received an result at this point we need to remove the event/fd combo from the event loop 33 | // This could happen if this awaitable is invoked in the net::select(...) function which will drop all 34 | // awaitables except for the one that finishes first 35 | if (m_fd > 0) 36 | { 37 | auto& exec = detail::event_loop::instance(); 38 | exec.remove(m_fd, m_event); 39 | } 40 | } 41 | 42 | bool await_ready() noexcept 43 | { 44 | // Return false to suspend the coroutine initially 45 | // Returning true would mean that we directly call await_resume without calling await_suspend first to 46 | // start the async io operation 47 | size_t bytes_available = 0; 48 | ::ioctl(m_fd, FIONREAD, &bytes_available); 49 | return bytes_available > 0; 50 | } 51 | 52 | void await_suspend(std::coroutine_handle<> suspended) noexcept 53 | { 54 | // Create a coroutine resumption task that gets executed from the event_loop when the event appears 55 | auto& exec = detail::event_loop::instance(); 56 | exec.add(m_fd, m_event, detail::coroutine_completion_handler(suspended)); 57 | } 58 | 59 | result_type await_resume() 60 | { 61 | auto op_result = m_operation(m_fd); 62 | m_fd = -1; 63 | return std::move(op_result); 64 | } 65 | }; 66 | 67 | } // namespace net 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /include/socketwrapper/detail/base_socket.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SOCKETWRAPPER_NET_INTERNAL_BASE_SOCKET_HPP 2 | #define SOCKETWRAPPER_NET_INTERNAL_BASE_SOCKET_HPP 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "event_loop.hpp" 11 | #include "socket_option.hpp" 12 | #include "utility.hpp" 13 | 14 | namespace net { 15 | 16 | namespace detail { 17 | 18 | /// Socket base class 19 | class base_socket 20 | { 21 | protected: 22 | int m_sockfd; 23 | 24 | ip_version m_family; 25 | 26 | base_socket(int sockfd, ip_version ip_ver) 27 | : m_sockfd{sockfd} 28 | , m_family{ip_ver} 29 | { 30 | if (m_sockfd == -1) 31 | { 32 | throw std::runtime_error{"Failed to create socket."}; 33 | } 34 | 35 | const int reuse = 1; 36 | if (::setsockopt(m_sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) 37 | { 38 | throw std::runtime_error{"Failed to set address reusable."}; 39 | } 40 | 41 | #ifdef SO_REUSEPORT 42 | if (::setsockopt(m_sockfd, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse)) < 0) 43 | { 44 | throw std::runtime_error{"Failed to set port reusable."}; 45 | } 46 | #endif 47 | } 48 | 49 | public: 50 | base_socket(const base_socket&) = delete; 51 | base_socket& operator=(const base_socket&) = delete; 52 | 53 | base_socket(base_socket&& rhs) noexcept 54 | { 55 | *this = std::move(rhs); 56 | } 57 | 58 | base_socket& operator=(base_socket&& rhs) noexcept 59 | { 60 | // Provide custom move assginment operator to prevent the moved socket from closing the underlying file 61 | // descriptor 62 | if (this != &rhs) 63 | { 64 | m_sockfd = rhs.m_sockfd; 65 | m_family = rhs.m_family; 66 | 67 | rhs.m_sockfd = -1; 68 | } 69 | return *this; 70 | } 71 | 72 | base_socket(socket_type type, ip_version ip_ver) 73 | : m_sockfd{::socket(static_cast(ip_ver), static_cast(type), 0)} 74 | , m_family{ip_ver} 75 | { 76 | detail::init_socket_system(); 77 | 78 | if (m_sockfd == -1) 79 | { 80 | throw std::runtime_error{"Failed to create socket."}; 81 | } 82 | 83 | set_option(option{1}); 84 | #ifdef SO_REUSEPORT 85 | set_option(option{1}); 86 | #endif 87 | } 88 | 89 | virtual ~base_socket() 90 | { 91 | if (m_sockfd > 0) 92 | { 93 | auto& exec = event_loop::instance(); 94 | exec.deregister(m_sockfd); 95 | ::close(m_sockfd); 96 | } 97 | } 98 | 99 | template ::value, bool>> 101 | void set_option(OPTION_TYPE&& opt_val) 102 | { 103 | if (::setsockopt(m_sockfd, 104 | opt_val.level_native(), 105 | opt_val.name(), 106 | reinterpret_cast(opt_val.value()), 107 | opt_val.size()) != 0) 108 | { 109 | throw std::runtime_error{"Failed to set socket option."}; 110 | } 111 | } 112 | 113 | template ::value, bool>> 115 | OPTION_TYPE get_option() const 116 | { 117 | OPTION_TYPE opt_val{}; 118 | unsigned int opt_len = opt_val.size(); 119 | if (::getsockopt( 120 | m_sockfd, opt_val.level_native(), opt_val.name(), reinterpret_cast(opt_val.value()), &opt_len) != 121 | 0) 122 | { 123 | throw std::runtime_error{"Failed to get socket option."}; 124 | } 125 | return opt_val; 126 | } 127 | 128 | template ::value, bool>> 130 | typename OPTION_TYPE::value_type get_option_value() const 131 | { 132 | return *get_option().value(); 133 | } 134 | 135 | size_t bytes_available() const 136 | { 137 | size_t bytes = 0; 138 | ::ioctl(m_sockfd, FIONREAD, &bytes); 139 | return bytes; 140 | } 141 | 142 | void cancel() const 143 | { 144 | // Cancel all pending async operations 145 | auto& exec = detail::event_loop::instance(); 146 | exec.deregister(m_sockfd); 147 | } 148 | 149 | int get() const 150 | { 151 | return m_sockfd; 152 | } 153 | 154 | ip_version family() const 155 | { 156 | return m_family; 157 | } 158 | }; 159 | 160 | } // namespace detail 161 | 162 | } // namespace net 163 | 164 | #endif 165 | -------------------------------------------------------------------------------- /include/socketwrapper/detail/callbacks.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SOCKETWRAPPER_NET_DETAIL_CALLBACKS_HPP 2 | #define SOCKETWRAPPER_NET_DETAIL_CALLBACKS_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #if __cplusplus >= 202002L 9 | #include 10 | #endif 11 | 12 | namespace net { 13 | 14 | namespace detail { 15 | 16 | class completion_handler 17 | { 18 | public: 19 | virtual ~completion_handler() 20 | {} 21 | 22 | virtual void invoke(const int) = 0; 23 | }; 24 | 25 | class no_return_completion_handler : public completion_handler 26 | { 27 | std::function m_operation; 28 | 29 | public: 30 | template 31 | no_return_completion_handler(invoke_type&& operation) 32 | : completion_handler() 33 | , m_operation(std::forward(operation)) 34 | {} 35 | 36 | void invoke(const int fd) override 37 | { 38 | try 39 | { 40 | m_operation(fd); 41 | } 42 | catch (...) 43 | { 44 | // Here the exception wont get handled since there is no result handler 45 | } 46 | } 47 | }; 48 | 49 | template 50 | class promise_completion_handler : public completion_handler 51 | { 52 | std::function m_operation; 53 | std::promise m_fullfill; 54 | 55 | public: 56 | template 57 | promise_completion_handler(invoke_type&& operation, std::promise fullfill) 58 | : completion_handler() 59 | , m_operation(std::forward(operation)) 60 | , m_fullfill(std::move(fullfill)) 61 | {} 62 | 63 | void invoke(const int fd) override 64 | { 65 | try 66 | { 67 | auto result = m_operation(fd); 68 | m_fullfill.set_value(std::move(result)); 69 | } 70 | catch (...) 71 | { 72 | m_fullfill.set_exception(std::current_exception()); 73 | } 74 | } 75 | }; 76 | 77 | template 78 | class callback_completion_handler : public completion_handler 79 | { 80 | std::function m_operation; 81 | std::function m_fullfill; 82 | 83 | public: 84 | template 85 | callback_completion_handler(invoke_type&& operation, callback_type&& callback) 86 | : completion_handler() 87 | , m_operation(std::forward(operation)) 88 | , m_fullfill(std::forward(callback)) 89 | {} 90 | 91 | void invoke(const int fd) override 92 | { 93 | try 94 | { 95 | auto result = m_operation(fd); 96 | m_fullfill(std::move(result), nullptr); 97 | } 98 | catch (...) 99 | { 100 | m_fullfill({}, std::current_exception()); 101 | } 102 | }; 103 | }; 104 | 105 | #if __cplusplus >= 202002L 106 | class coroutine_completion_handler : public completion_handler 107 | { 108 | std::coroutine_handle<> m_waiting_coroutine; 109 | 110 | public: 111 | coroutine_completion_handler(std::coroutine_handle<> suspended) 112 | : completion_handler() 113 | , m_waiting_coroutine(suspended) 114 | {} 115 | 116 | void invoke(const int) override 117 | { 118 | // For coroutines we only need the coroutine handle from the op_awaitable to be resumed 119 | // The operation is than executed by the op_awaitable that spawned the async task on executor 120 | m_waiting_coroutine.resume(); 121 | }; 122 | }; 123 | #endif 124 | 125 | } // namespace detail 126 | 127 | } // namespace net 128 | 129 | #endif 130 | -------------------------------------------------------------------------------- /include/socketwrapper/detail/event_loop.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SOCKETWRAPPER_NET_INTERNAL_EXECUTOR_HPP 2 | #define SOCKETWRAPPER_NET_INTERNAL_EXECUTOR_HPP 3 | 4 | #include "callbacks.hpp" 5 | #include "event_notifier.hpp" 6 | #include "threadpool.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace net { 17 | 18 | namespace detail { 19 | 20 | inline bool operator<(const std::pair& lhs, const std::pair& rhs) 21 | { 22 | if (lhs.first == rhs.first) 23 | { 24 | return lhs.second < rhs.second; 25 | } 26 | return lhs.first < rhs.first; 27 | } 28 | 29 | inline bool operator<(const std::pair& lhs, const int rhs) 30 | { 31 | return lhs.first < rhs; 32 | } 33 | 34 | inline bool operator<(const int lhs, const std::pair& rhs) 35 | { 36 | return lhs < rhs.first; 37 | } 38 | 39 | /// Class to manage all asynchronous socket io operations 40 | class event_loop 41 | { 42 | enum class context_control : uint8_t 43 | { 44 | NO_OP = 0, 45 | EXIT_LOOP = 1, 46 | RELOAD_FD_SET = 2 47 | }; 48 | 49 | thread_pool m_pool; 50 | 51 | std::mutex m_waker_mutex{}; 52 | 53 | event_notifier m_waker{}; 54 | 55 | std::future m_background_poll{}; 56 | 57 | std::condition_variable m_stop_condition{}; 58 | 59 | std::atomic_bool m_stop_token{false}; 60 | 61 | std::multimap, std::unique_ptr, std::less<>> m_completion_handlers{}; 62 | 63 | event_loop(size_t num_threads = std::thread::hardware_concurrency()) 64 | : m_pool{num_threads} 65 | , m_background_poll{std::async(std::launch::async, &event_loop::event_handling_loop, this)} 66 | {} 67 | 68 | ~event_loop() 69 | { 70 | // Remove all events from the event notifier and join its background task 71 | for (const auto& it : m_completion_handlers) 72 | { 73 | m_waker.unwatch(it.first.first, it.first.second); 74 | } 75 | m_stop_token.store(true); 76 | m_waker.cancel_next_event(); 77 | m_background_poll.wait(); 78 | } 79 | 80 | void event_handling_loop() 81 | { 82 | while (!m_stop_token.load()) 83 | { 84 | const auto event = m_waker.next_event(); 85 | if (event.has_value()) 86 | { 87 | // Handle event 88 | auto lock = std::lock_guard(m_waker_mutex); 89 | if (auto comp_it = m_completion_handlers.find(event.value()); comp_it != m_completion_handlers.end()) 90 | { 91 | m_pool.add_job( 92 | [this, sock_fd = comp_it->first.first, completion_handler = std::move(comp_it->second)]() 93 | { 94 | completion_handler->invoke(sock_fd); 95 | m_stop_condition.notify_one(); 96 | }); 97 | 98 | m_completion_handlers.erase(comp_it); 99 | } 100 | } 101 | } 102 | } 103 | 104 | public: 105 | static event_loop& instance() 106 | { 107 | static auto handler = event_loop(); 108 | return handler; 109 | } 110 | 111 | event_loop(const event_loop&) = delete; 112 | event_loop& operator=(event_loop&) = delete; 113 | event_loop(event_loop&&) = delete; 114 | event_loop& operator=(event_loop&&) = delete; 115 | 116 | void run() 117 | { 118 | // Wait until all registered events were handled 119 | auto lock = std::unique_lock(m_waker_mutex); 120 | m_stop_condition.wait(lock, 121 | [this]() 122 | { 123 | // Check if there are no waiting async tasks registered or in execution 124 | // Its safe to check the pool for being empty because we are the only ones to insert new jobs into 125 | // its queue so there can not be a job inserted while this function holds the internal mutex 126 | return m_completion_handlers.empty() && m_pool.queue_empty() && !m_pool.busy(); 127 | }); 128 | 129 | // Wait until the threadpool finished executing the completion handlers 130 | m_pool.flush(); 131 | } 132 | 133 | template 134 | bool add(const int sock_fd, const event_type type, callback_type&& callback) 135 | { 136 | auto lock = std::lock_guard(m_waker_mutex); 137 | m_completion_handlers.insert(std::make_pair( 138 | std::make_pair(sock_fd, type), std::make_unique(std::forward(callback)))); 139 | const auto res = m_waker.watch(sock_fd, type); 140 | return res; 141 | } 142 | 143 | bool remove(const int sock_fd, const event_type type) 144 | { 145 | auto result = false; 146 | { 147 | auto lock = std::lock_guard(m_waker_mutex); 148 | if (const auto it = m_completion_handlers.find(std::make_pair(sock_fd, type)); 149 | it != m_completion_handlers.end()) 150 | { 151 | m_completion_handlers.erase(it); 152 | result = m_waker.unwatch(sock_fd, type); 153 | m_stop_condition.notify_one(); 154 | } 155 | } 156 | return result; 157 | } 158 | 159 | void deregister(const int sock_fd) 160 | { 161 | auto lock = std::lock_guard(m_waker_mutex); 162 | const auto fd_range = m_completion_handlers.equal_range(sock_fd); 163 | for (auto it = fd_range.first; it != fd_range.second;) 164 | { 165 | m_waker.unwatch(it->first.first, it->first.second); 166 | it = m_completion_handlers.erase(it); 167 | } 168 | } 169 | 170 | bool is_registered(const int sock_fd, const event_type type) const 171 | { 172 | const auto& context_it = m_completion_handlers.find(std::make_pair(sock_fd, type)); 173 | return context_it != m_completion_handlers.end(); 174 | } 175 | }; 176 | 177 | } // namespace detail 178 | 179 | /// Free function to easily wait until the event_loop runs out of registered events 180 | inline void async_run() 181 | { 182 | detail::event_loop::instance().run(); 183 | } 184 | 185 | } // namespace net 186 | 187 | #endif 188 | -------------------------------------------------------------------------------- /include/socketwrapper/detail/event_notifier.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SOCKETWRAPPER_NET_INTERNAL_MESSAGE_NOTIFIER_HPP 2 | #define SOCKETWRAPPER_NET_INTERNAL_MESSAGE_NOTIFIER_HPP 3 | 4 | #if defined(__FreeBSD__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__NetBSD__) 5 | #include "event_notifier_kqueue.hpp" 6 | #elif defined(__linux__) 7 | #include "event_notifier_epoll.hpp" 8 | #endif 9 | 10 | namespace net { 11 | 12 | namespace detail { 13 | 14 | #if defined(__FreeBSD__) || defined(__APPLE__) || defined(__OpenBSD__) || defined(__NetBSD__) 15 | using event_notifier = event_notifier_kqueue; 16 | #elif defined(__linux__) 17 | using event_notifier = event_notifier_epoll; 18 | #endif 19 | 20 | } // namespace detail 21 | 22 | } // namespace net 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /include/socketwrapper/detail/event_notifier_epoll.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SOCKETWRAPPER_NET_INTERNAL_MESSAGE_NOTIFIER_EPOLL_HPP 2 | #define SOCKETWRAPPER_NET_INTERNAL_MESSAGE_NOTIFIER_EPOLL_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | namespace net { 12 | 13 | namespace detail { 14 | 15 | enum class event_type : int16_t 16 | { 17 | READ = EPOLLIN, 18 | WRITE = EPOLLOUT, 19 | }; 20 | 21 | class event_notifier_epoll 22 | { 23 | using event_t = struct ::epoll_event; 24 | 25 | enum class control : uint8_t 26 | { 27 | NO_OP = 0, 28 | EXIT_LOOP = 1, 29 | RELOAD_EVENT_QUEUE = 2 30 | }; 31 | 32 | int m_epoll_fd; 33 | 34 | std::array m_control_pipes{}; 35 | 36 | std::map m_events{}; 37 | 38 | public: 39 | event_notifier_epoll() 40 | : m_epoll_fd{::epoll_create(1)} 41 | { 42 | if (m_epoll_fd == -1) 43 | { 44 | throw std::runtime_error{"Failed to create epoll instance when instantiating event_notifier_epoll."}; 45 | } 46 | 47 | // Create pipe to stop select and add it to m_fds 48 | if (::pipe(m_control_pipes.data()) < 0) 49 | throw std::runtime_error{"Failed to create pipe when instantiating class event_notifier_epoll."}; 50 | 51 | // Add the pipe to the epoll monitoring set 52 | event_t pipe_event{}; 53 | pipe_event.events = static_cast(event_type::READ); 54 | pipe_event.data.fd = m_control_pipes[0]; 55 | if (::epoll_ctl(m_epoll_fd, EPOLL_CTL_ADD, m_control_pipes[0], &pipe_event) == -1) 56 | { 57 | throw std::runtime_error{"Failed to add stop event for message notifier to epoll queue."}; 58 | } 59 | } 60 | 61 | event_notifier_epoll(const event_notifier_epoll&) = delete; 62 | 63 | event_notifier_epoll& operator=(const event_notifier_epoll&) = delete; 64 | 65 | event_notifier_epoll(event_notifier_epoll&&) = delete; 66 | 67 | event_notifier_epoll& operator=(const event_notifier_epoll&&) = delete; 68 | 69 | ~event_notifier_epoll() 70 | { 71 | // Send stop signal to epoll loop to exit background task 72 | const auto stop_byte = control::EXIT_LOOP; 73 | ::write(m_control_pipes[1], reinterpret_cast(&stop_byte), 1); 74 | 75 | // Unregister all registered events 76 | for (const auto& event_pair : m_events) 77 | { 78 | if (event_pair.second.events & static_cast(event_type::READ)) 79 | { 80 | unwatch(event_pair.first, event_type::READ); 81 | } 82 | if (event_pair.second.events & static_cast(event_type::WRITE)) 83 | { 84 | unwatch(event_pair.first, event_type::WRITE); 85 | } 86 | } 87 | 88 | // Unregister control pipe read event 89 | event_t pipe_event{}; 90 | pipe_event.events = static_cast(event_type::READ); 91 | pipe_event.data.fd = m_control_pipes[0]; 92 | ::epoll_ctl(m_epoll_fd, EPOLL_CTL_DEL, m_control_pipes[0], &pipe_event); 93 | 94 | ::close(m_control_pipes[0]); 95 | ::close(m_control_pipes[1]); 96 | } 97 | 98 | bool watch(int fd, event_type watch_for) 99 | { 100 | if (const auto it = m_events.find(fd); it != m_events.end()) 101 | { 102 | // Modify the epoll event that is already registered for the socket 103 | auto& event_data = it->second; 104 | event_data.events |= static_cast(watch_for); 105 | if (::epoll_ctl(m_epoll_fd, EPOLL_CTL_MOD, fd, &event_data) == -1) 106 | { 107 | return false; 108 | } 109 | } 110 | else 111 | { 112 | event_t event_data{}; 113 | 114 | // Assign new event to epoll queue 115 | event_data.events = static_cast(watch_for) | EPOLLET; 116 | event_data.data.fd = fd; 117 | if (::epoll_ctl(m_epoll_fd, EPOLL_CTL_ADD, fd, &event_data) == -1) 118 | { 119 | return false; 120 | } 121 | 122 | m_events.insert_or_assign(fd, event_data); 123 | } 124 | 125 | // Restart loop with updated fd set 126 | const auto control_byte = control::RELOAD_EVENT_QUEUE; 127 | ::write(m_control_pipes[1], reinterpret_cast(&control_byte), 1); 128 | 129 | return true; 130 | } 131 | 132 | bool unwatch(int fd, event_type watched_for) 133 | { 134 | if (const auto& it = m_events.find(fd); it != m_events.end()) 135 | { 136 | auto& event_data = it->second; 137 | 138 | // Remove the inparam event type from the epoll queue for this socket 139 | event_data.events &= ~static_cast(watched_for); 140 | if (event_data.events == EPOLLET) 141 | { 142 | if (::epoll_ctl(m_epoll_fd, EPOLL_CTL_DEL, fd, nullptr) == -1) 143 | { 144 | return false; 145 | } 146 | m_events.erase(it); 147 | } 148 | else 149 | { 150 | if (::epoll_ctl(m_epoll_fd, EPOLL_CTL_MOD, fd, &event_data) == -1) 151 | { 152 | return false; 153 | } 154 | } 155 | 156 | // Restart loop with updated fd set 157 | const auto control_byte = control::RELOAD_EVENT_QUEUE; 158 | ::write(m_control_pipes[1], reinterpret_cast(&control_byte), 1); 159 | 160 | return true; 161 | } 162 | return false; 163 | } 164 | 165 | std::optional> next_event() 166 | { 167 | auto ready_set = std::array{}; 168 | 169 | while (true) 170 | { 171 | int num_ready = ::epoll_wait(m_epoll_fd, ready_set.data(), 64, -1); 172 | for (int i = 0; i < num_ready; ++i) 173 | { 174 | if (ready_set[i].data.fd == m_control_pipes[0]) 175 | { 176 | // Internal stop for reloading event queue after add/remove 177 | auto control_byte = control::NO_OP; 178 | ::read(ready_set[i].data.fd, reinterpret_cast(&control_byte), 1); 179 | if (control_byte == control::EXIT_LOOP) 180 | { 181 | return std::nullopt; 182 | } 183 | } 184 | else if (ready_set[i].events & static_cast(event_type::READ)) 185 | { 186 | int fd = ready_set[i].data.fd; 187 | unwatch(fd, event_type::READ); 188 | return std::make_pair(fd, event_type::READ); 189 | } 190 | else if (ready_set[i].events & static_cast(event_type::WRITE)) 191 | { 192 | int fd = ready_set[i].data.fd; 193 | unwatch(fd, event_type::WRITE); 194 | return std::make_pair(fd, event_type::WRITE); 195 | } 196 | } 197 | } 198 | } 199 | 200 | void cancel_next_event() const 201 | { 202 | const auto control_byte = control::EXIT_LOOP; 203 | ::write(m_control_pipes[1], reinterpret_cast(&control_byte), 1); 204 | } 205 | }; 206 | 207 | } // namespace detail 208 | 209 | } // namespace net 210 | 211 | #endif 212 | -------------------------------------------------------------------------------- /include/socketwrapper/detail/event_notifier_kqueue.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SOCKETWRAPPER_NET_INTERNAL_MESSAGE_NOTIFIER_KQUEUE_HPP 2 | #define SOCKETWRAPPER_NET_INTERNAL_MESSAGE_NOTIFIER_KQUEUE_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | namespace net { 13 | 14 | namespace detail { 15 | 16 | enum class event_type : int16_t 17 | { 18 | READ = EVFILT_READ, 19 | WRITE = EVFILT_WRITE, 20 | }; 21 | 22 | class event_notifier_kqueue 23 | { 24 | using event_t = struct ::kevent; 25 | 26 | enum class control : uint8_t 27 | { 28 | NO_OP = 0, 29 | EXIT_LOOP = 1, 30 | RELOAD_EVENT_QUEUE = 2 31 | }; 32 | 33 | int m_kernel_queue; 34 | 35 | std::array m_control_pipes{}; 36 | 37 | std::map, event_t> m_events{}; 38 | 39 | public: 40 | event_notifier_kqueue() 41 | : m_kernel_queue{::kqueue()} 42 | { 43 | if (m_kernel_queue == -1) 44 | { 45 | throw std::runtime_error{"Failed to create epoll instance when instantiating event_notifier_kqueue."}; 46 | } 47 | 48 | // Create pipe to stop select and add it to m_fds 49 | if (::pipe(m_control_pipes.data()) < 0) 50 | { 51 | throw std::runtime_error{"Failed to create pipe when instantiating class event_notifier_kqueue."}; 52 | } 53 | event_t pipe_event{}; 54 | EV_SET( 55 | &pipe_event, m_control_pipes[0], static_cast(event_type::READ), EV_ADD | EV_CLEAR, 0, 0, nullptr); 56 | if (::kevent(m_kernel_queue, &pipe_event, 1, nullptr, 0, nullptr) == -1) 57 | { 58 | throw std::runtime_error{"Failed to add stop event for message notifier to kernel queue."}; 59 | } 60 | } 61 | 62 | ~event_notifier_kqueue() 63 | { 64 | // Send stop signal to epoll loop to exit background task 65 | const auto stop_byte = control::EXIT_LOOP; 66 | ::write(m_control_pipes[1], reinterpret_cast(&stop_byte), 1); 67 | 68 | // Unregister all registered events 69 | for (const auto& event_pair : m_events) 70 | { 71 | unwatch(event_pair.first.first, event_pair.first.second); 72 | } 73 | 74 | // Unregister control pipe read event 75 | event_t pipe_event{}; 76 | EV_SET(&pipe_event, m_control_pipes[0], static_cast(event_type::READ), EV_DELETE, 0, 0, nullptr); 77 | ::kevent(m_kernel_queue, &pipe_event, 1, nullptr, 0, nullptr); 78 | 79 | ::close(m_control_pipes[0]); 80 | ::close(m_control_pipes[1]); 81 | ::close(m_kernel_queue); 82 | } 83 | 84 | event_notifier_kqueue(const event_notifier_kqueue&) = delete; 85 | 86 | event_notifier_kqueue& operator=(const event_notifier_kqueue&) = delete; 87 | 88 | event_notifier_kqueue(event_notifier_kqueue&&) = delete; 89 | 90 | event_notifier_kqueue& operator=(const event_notifier_kqueue&&) = delete; 91 | 92 | bool watch(int fd, event_type watch_for) 93 | { 94 | event_t event_data{}; 95 | 96 | // Attach event to kernel queue 97 | EV_SET(&event_data, fd, static_cast(watch_for), EV_ADD | EV_CLEAR, 0, 0, nullptr); 98 | if (::kevent(m_kernel_queue, &event_data, 1, nullptr, 0, nullptr) == -1) 99 | { 100 | return false; 101 | } 102 | 103 | m_events.insert_or_assign(std::make_pair(fd, watch_for), event_data); 104 | 105 | // Restart loop with updated fd set 106 | const auto control_byte = control::RELOAD_EVENT_QUEUE; 107 | ::write(m_control_pipes[1], reinterpret_cast(&control_byte), 1); 108 | 109 | return true; 110 | } 111 | 112 | bool unwatch(int fd, event_type watched_for) 113 | { 114 | if (const auto& event_it = m_events.find(std::tie(fd, watched_for)); event_it != m_events.end()) 115 | { 116 | // Remove read event 117 | EV_SET(&event_it->second, fd, static_cast(watched_for), EV_DELETE, 0, 0, nullptr); 118 | if (::kevent(m_kernel_queue, &event_it->second, 1, nullptr, 0, nullptr) == -1) 119 | { 120 | return false; 121 | } 122 | 123 | m_events.erase(event_it); 124 | 125 | // Restart loop with updated fd set 126 | const auto control_byte = control::RELOAD_EVENT_QUEUE; 127 | ::write(m_control_pipes[1], reinterpret_cast(&control_byte), 1); 128 | 129 | return true; 130 | } 131 | return false; 132 | } 133 | 134 | std::optional> next_event() 135 | { 136 | auto ready_set = std::array{}; 137 | 138 | while (true) 139 | { 140 | const int num_ready = ::kevent(m_kernel_queue, nullptr, 0, ready_set.data(), ready_set.size(), nullptr); 141 | for (int i = 0; i < num_ready; i++) 142 | { 143 | if (ready_set[i].ident == static_cast(m_control_pipes[0])) 144 | { 145 | // Internal stop for reloading event queue after add/remove 146 | auto control_byte = control::NO_OP; 147 | ::read(ready_set[i].ident, reinterpret_cast(&control_byte), 1); 148 | if (control_byte == control::EXIT_LOOP) 149 | { 150 | return std::nullopt; 151 | } 152 | } 153 | else if (ready_set[i].filter == static_cast(event_type::READ) || 154 | ready_set[i].filter == static_cast(event_type::WRITE)) 155 | { 156 | int fd = ready_set[i].ident; 157 | unwatch(fd, static_cast(ready_set[i].filter)); 158 | return std::make_pair(fd, static_cast(ready_set[i].filter)); 159 | } 160 | } 161 | } 162 | 163 | return std::nullopt; 164 | } 165 | 166 | void cancel_next_event() const 167 | { 168 | const auto control_byte = control::EXIT_LOOP; 169 | ::write(m_control_pipes[1], reinterpret_cast(&control_byte), 1); 170 | } 171 | }; 172 | 173 | } // namespace detail 174 | 175 | } // namespace net 176 | 177 | #endif 178 | -------------------------------------------------------------------------------- /include/socketwrapper/detail/socket_option.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SOCKETWRAPPER_NET_SOCKET_OPTION_HPP 2 | #define SOCKETWRAPPER_NET_SOCKET_OPTION_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | namespace net { 15 | 16 | namespace detail { 17 | 18 | template class REF_TYPE> 19 | struct is_template_of : std::false_type 20 | {}; 21 | 22 | template