1//! @file socket.hpp
2//! @author ryftchen
3//! @brief The declarations (socket) in the utility module.
4//! @version 0.1.0
5//! @copyright Copyright (c) 2022-2026 ryftchen. All rights reserved.
6
7#pragma once
8
9#include <arpa/inet.h>
10#include <future>
11#include <memory>
12
13//! @brief The utility module.
14namespace utility // NOLINT(modernize-concat-nested-namespaces)
15{
16//! @brief Network-socket-related functions in the utility module.
17namespace socket
18{
19//! @brief Brief function description.
20//! @return function description (module_function)
21inline static const char* description() noexcept
22{
23 return "UTIL_SOCKET";
24}
25extern const char* version() noexcept;
26
27//! @brief Network socket.
28class Socket
29{
30public:
31 //! @brief Construct a new Socket object.
32 Socket(const Socket&) = delete;
33 //! @brief Construct a new Socket object.
34 Socket(Socket&&) = delete;
35 //! @brief The operator (=) overloading of Socket class.
36 //! @return reference of the Socket object
37 Socket& operator=(const Socket&) = delete;
38 //! @brief The operator (=) overloading of Socket class.
39 //! @return reference of the Socket object
40 Socket& operator=(Socket&&) = delete;
41
42 //! @brief Close the socket.
43 void close();
44 //! @brief Wait for the task to be done and then exit.
45 void join();
46 //! @brief Set the flag to indicate that a stop has been requested.
47 void requestStop();
48 //! @brief Check whether a stop has been requested.
49 //! @return has been requested or not
50 bool stopRequested() const;
51 //! @brief Get the transport ip address.
52 //! @return transport ip address
53 std::string transportAddress() const;
54 //! @brief Get the transport port number.
55 //! @return transport port number
56 std::uint16_t transportPort() const;
57
58 //! @brief Transport information.
59 ::sockaddr_in sockAddr{};
60 //! @brief Bytes buffer size.
61 static constexpr std::uint16_t bufferSize{0xFFFFU};
62 //! @brief Enumerate specific socket types.
63 enum class Type : std::uint8_t
64 {
65 //! @brief TCP.
66 tcp = ::SOCK_STREAM,
67 //! @brief UDP.
68 udp = ::SOCK_DGRAM
69 };
70
71private:
72 //! @brief Flag for request of stop.
73 std::atomic_bool exitReady{false};
74 //! @brief Result of asynchronous operations for the non-detached thread.
75 std::future<void> ownedTask;
76 //! @brief Spin lock to synchronize access to the socket.
77 mutable ::pthread_spinlock_t sockLock{};
78
79 //! @brief Acquire the spin lock to ensure mutual exclusion.
80 void spinLock() const;
81 //! @brief Release the spin lock to allow other threads to acquire it.
82 void spinUnlock() const;
83
84protected:
85 //! @brief Construct a new Socket object.
86 //! @param sockType - socket type
87 //! @param sockId - socket id
88 explicit Socket(const Type sockType = Type::tcp, const int sockId = -1);
89 //! @brief Destroy the Socket object.
90 ~Socket();
91
92 //! @brief Start the detached operations.
93 //! @tparam Func - type of callable function
94 //! @tparam Args - type of function arguments
95 //! @param func - callable function
96 //! @param args - function arguments
97 template <typename Func, typename... Args>
98 static void spawnDetached(Func&& func, Args&&... args);
99 //! @brief Start the joinable operations.
100 //! @tparam Func - type of callable function
101 //! @tparam Args - type of function arguments
102 //! @param func - callable function
103 //! @param args - function arguments
104 template <typename Func, typename... Args>
105 void spawnJoinable(Func&& func, Args&&... args);
106 //! @brief Guard for the spin lock to ensure mutual exclusion.
107 class Guard
108 {
109 public:
110 //! @brief Construct a new Guard object.
111 //! @param socket - target socket
112 explicit Guard(const Socket& socket) : socket{socket} { socket.spinLock(); }
113 //! @brief Destroy the Guard object.
114 virtual ~Guard() { socket.spinUnlock(); }
115 //! @brief Construct a new Guard object.
116 Guard(const Guard&) = default;
117 //! @brief Construct a new Guard object.
118 Guard(Guard&&) noexcept = default;
119 //! @brief The operator (=) overloading of Guard class.
120 //! @return reference of the Guard object
121 Guard& operator=(const Guard&) = delete;
122 //! @brief The operator (=) overloading of Guard class.
123 //! @return reference of the Guard object
124 Guard& operator=(Guard&&) = delete;
125
126 private:
127 //! @brief Socket to be mutually exclusive.
128 const Socket& socket;
129 };
130
131 //! @brief File descriptor.
132 int sock{-1};
133};
134
135//! @brief TCP socket.
136class TCPSocket : public Socket, public std::enable_shared_from_this<TCPSocket>
137{
138public:
139 //! @brief Construct a new TCPSocket object.
140 //! @param sockId - socket id
141 explicit TCPSocket(const int sockId = -1) : Socket(Type::tcp, sockId) {}
142
143 //! @brief Send bytes from the buffer to socket FD.
144 //! @param bytes - bytes buffer
145 //! @param size - length of buffer
146 //! @return sent size
147 ::ssize_t send(const char* const bytes, const std::size_t size);
148 //! @brief Send the message string to socket FD.
149 //! @param message - message string
150 //! @return sent size
151 ::ssize_t send(const std::string_view message);
152 //! @brief Open a connection on socket FD to peer.
153 //! @param ip - peer ip address
154 //! @param port - peer port number
155 void connect(const std::string& ip, const std::uint16_t port);
156 //! @brief Create the thread to receive.
157 //! @param detached - whether to detach
158 void receive(const bool detached = false);
159
160 //! @brief Alias for the handling on message received.
161 using MessageCallback = std::function<void(const std::string_view)>;
162 //! @brief Alias for the handling on raw message received.
163 using RawMessageCallback = std::function<void(char* const, const std::size_t)>;
164 //! @brief Bind the callback to handle the received message.
165 //! @param callback - callback on received message
166 void subscribeMessage(MessageCallback callback);
167 //! @brief Bind the callback to handle the received raw message.
168 //! @param callback - callback on received raw message
169 void subscribeRawMessage(RawMessageCallback callback);
170
171private:
172 //! @brief Handling on message received.
173 std::atomic<std::shared_ptr<MessageCallback>> msgCb;
174 //! @brief Handling on raw message received.
175 std::atomic<std::shared_ptr<RawMessageCallback>> rawMsgCb;
176
177 //! @brief Receive bytes from socket FD.
178 //! @param socket - target socket
179 static void doReceive(const std::shared_ptr<TCPSocket> socket);
180
181 //! @brief Emit the received message.
182 //! @param message - received message
183 void onMessage(const std::string_view message) const;
184 //! @brief Emit the received raw message.
185 //! @param bytes - received bytes buffer
186 //! @param size - length of buffer
187 void onRawMessage(char* const bytes, const std::size_t size) const;
188};
189
190//! @brief TCP server.
191class TCPServer : public Socket, public std::enable_shared_from_this<TCPServer>
192{
193public:
194 //! @brief Construct a new TCPServer object.
195 explicit TCPServer();
196
197 //! @brief Bind to transport ip address and port number.
198 //! @param ip - ip address
199 //! @param port - port number
200 void bind(const std::string& ip, const std::uint16_t port);
201 //! @brief Bind to transport port number with default ip address.
202 //! @param port - port number
203 void bind(const std::uint16_t port);
204 //! @brief Listen on a port number. Wait for the connection to be established.
205 void listen();
206 //! @brief Create the thread to accept the connection from the client.
207 //! @param detached - whether to detach
208 void accept(const bool detached = false);
209
210 //! @brief Alias for the handling on new connection.
211 using ConnectionCallback = std::function<void(const std::shared_ptr<TCPSocket>)>;
212 //! @brief Bind the callback to handle the new connection.
213 //! @param callback - callback on new connection
214 void subscribeConnection(ConnectionCallback callback);
215
216private:
217 //! @brief Handling on new connection.
218 std::atomic<std::shared_ptr<ConnectionCallback>> connCb;
219
220 //! @brief Accept the connection on socket FD.
221 //! @param server - target server
222 static void accept(const std::shared_ptr<TCPServer> server);
223
224 //! @brief Emit the new connection.
225 //! @param client - new connected client
226 void onConnection(const std::shared_ptr<TCPSocket> client) const;
227};
228
229//! @brief UDP socket.
230class UDPSocket : public Socket, public std::enable_shared_from_this<UDPSocket>
231{
232public:
233 //! @brief Construct a new UDPSocket object.
234 //! @param sockId - socket id
235 explicit UDPSocket(const int sockId = -1) : Socket(Type::udp, sockId) {}
236
237 //! @brief Send bytes from the buffer on socket FD to peer.
238 //! @param bytes - bytes buffer
239 //! @param size - length of buffer
240 //! @param ip - peer ip address
241 //! @param port - peer port number
242 //! @return sent size
243 ::ssize_t sendTo(const char* const bytes, const std::size_t size, const std::string& ip, const std::uint16_t port);
244 //! @brief Send the message string on socket FD to peer.
245 //! @param message - message string
246 //! @param ip - peer ip address
247 //! @param port - peer port number
248 //! @return sent size
249 ::ssize_t sendTo(const std::string_view message, const std::string& ip, const std::uint16_t port);
250 //! @brief Send bytes from the buffer to socket FD.
251 //! @param bytes - bytes buffer
252 //! @param size - length of buffer
253 //! @return sent size
254 ::ssize_t send(const char* const bytes, const std::size_t size);
255 //! @brief Send the message string to socket FD.
256 //! @param message - message string
257 //! @return sent size
258 ::ssize_t send(const std::string_view message);
259 //! @brief Open a connection on socket FD to peer.
260 //! @param ip - peer ip address
261 //! @param port - peer port number
262 void connect(const std::string& ip, const std::uint16_t port);
263 //! @brief Create the thread to receive.
264 //! @param detached - whether to detach
265 void receive(const bool detached = false);
266 //! @brief Create the thread to receive from peer.
267 //! @param detached - whether to detach
268 void receiveFrom(const bool detached = false);
269
270 //! @brief Alias for the handling on message received.
271 using MessageCallback = std::function<void(const std::string_view, const std::string&, const std::uint16_t)>;
272 //! @brief Alias for the handling on raw message received.
273 using RawMessageCallback =
274 std::function<void(char* const, const std::size_t, const std::string&, const std::uint16_t)>;
275 //! @brief Bind the callback to handle the received message.
276 //! @param callback - callback on received message
277 void subscribeMessage(MessageCallback callback);
278 //! @brief Bind the callback to handle the received raw message.
279 //! @param callback - callback on received raw message
280 void subscribeRawMessage(RawMessageCallback callback);
281
282private:
283 //! @brief Handling on message received.
284 std::atomic<std::shared_ptr<MessageCallback>> msgCb;
285 //! @brief Handling on raw message received.
286 std::atomic<std::shared_ptr<RawMessageCallback>> rawMsgCb;
287
288 //! @brief Receive bytes from socket FD.
289 //! @param socket - target socket
290 static void doReceive(const std::shared_ptr<UDPSocket> socket);
291 //! @brief Receive bytes through socket FD.
292 //! @param socket - target socket
293 static void doReceiveFrom(const std::shared_ptr<UDPSocket> socket);
294
295 //! @brief Emit the received message.
296 //! @param message - received message
297 //! @param ip - source ip address
298 //! @param port - source port number
299 void onMessage(const std::string_view message, const std::string& ip, const std::uint16_t port) const;
300 //! @brief Emit the received raw message.
301 //! @param bytes - received bytes buffer
302 //! @param size - length of buffer
303 //! @param ip - source ip address
304 //! @param port - source port number
305 void onRawMessage(char* const bytes, const std::size_t size, const std::string& ip, const std::uint16_t port) const;
306};
307
308//! @brief UDP server.
309class UDPServer : public UDPSocket
310{
311public:
312 //! @brief Bind to transport ip address and port number.
313 //! @param ip - ip address
314 //! @param port - port number
315 void bind(const std::string& ip, const std::uint16_t port);
316 //! @brief Bind to transport port number with default ip address.
317 //! @param port - port number
318 void bind(const std::uint16_t port);
319};
320} // namespace socket
321} // namespace utility
322