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-2025 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&&) noexcept = 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&&) noexcept = delete;
41
42 //! @brief Close the socket.
43 void toClose();
44 //! @brief Wait for the task to be done and then exit.
45 void toJoin();
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> asyncTask;
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 Launch the asynchronous 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 void launchAsyncTask(Func&& func, Args&&... args);
99 //! @brief Guard for the spin lock to ensure mutual exclusion.
100 class Guard
101 {
102 public:
103 //! @brief Construct a new Guard object.
104 //! @param socket - target socket
105 explicit Guard(const Socket& socket) : socket{socket} { socket.spinLock(); }
106 //! @brief Destroy the Guard object.
107 virtual ~Guard() { socket.spinUnlock(); }
108 //! @brief Construct a new Guard object.
109 Guard(const Guard&) = delete;
110 //! @brief Construct a new Guard object.
111 Guard(Guard&&) noexcept = delete;
112 //! @brief The operator (=) overloading of Guard class.
113 //! @return reference of the Guard object
114 Guard& operator=(const Guard&) = delete;
115 //! @brief The operator (=) overloading of Guard class.
116 //! @return reference of the Guard object
117 Guard& operator=(Guard&&) noexcept = delete;
118
119 private:
120 //! @brief Socket to be mutually exclusive.
121 const Socket& socket;
122 };
123
124 //! @brief File descriptor.
125 int sock{-1};
126};
127
128//! @brief TCP socket.
129class TCPSocket : public Socket, public std::enable_shared_from_this<TCPSocket>
130{
131public:
132 //! @brief Construct a new TCPSocket object.
133 //! @param sockId - socket id
134 explicit TCPSocket(const int sockId = -1) : Socket(Type::tcp, sockId) {}
135
136 //! @brief Send bytes from the buffer to socket FD.
137 //! @param bytes - bytes buffer
138 //! @param size - length of buffer
139 //! @return sent size
140 ::ssize_t toSend(const char* const bytes, const std::size_t size);
141 //! @brief Send the message string to socket FD.
142 //! @param message - message string
143 //! @return sent size
144 ::ssize_t toSend(const std::string_view message);
145 //! @brief Open a connection on socket FD to peer.
146 //! @param ip - peer ip address
147 //! @param port - peer port number
148 void toConnect(const std::string& ip, const std::uint16_t port);
149 //! @brief Create the thread to receive.
150 //! @param detach - whether to detach
151 void toReceive(const bool detach = false);
152
153 //! @brief Alias for the handling on message received.
154 using MessageCallback = std::function<void(const std::string_view)>;
155 //! @brief Alias for the handling on raw message received.
156 using RawMessageCallback = std::function<void(char* const, const std::size_t)>;
157 //! @brief Bind the callback to handle the received message.
158 //! @param callback - callback on received message
159 void subscribeMessage(MessageCallback callback);
160 //! @brief Bind the callback to handle the received raw message.
161 //! @param callback - callback on received raw message
162 void subscribeRawMessage(RawMessageCallback callback);
163
164private:
165 //! @brief Handling on message received.
166 std::atomic<std::shared_ptr<MessageCallback>> msgCb;
167 //! @brief Handling on raw message received.
168 std::atomic<std::shared_ptr<RawMessageCallback>> rawMsgCb;
169
170 //! @brief Receive bytes from socket FD.
171 //! @param socket - target socket
172 static void toRecv(const std::shared_ptr<TCPSocket> socket);
173
174 //! @brief Emit the received message.
175 //! @param message - received message
176 void onMessage(const std::string_view message) const;
177 //! @brief Emit the received raw message.
178 //! @param bytes - received bytes buffer
179 //! @param size - length of buffer
180 void onRawMessage(char* const bytes, const std::size_t size) const;
181};
182
183//! @brief TCP server.
184class TCPServer : public Socket, public std::enable_shared_from_this<TCPServer>
185{
186public:
187 //! @brief Construct a new TCPServer object.
188 explicit TCPServer();
189
190 //! @brief Bind to transport ip address and port number.
191 //! @param ip - ip address
192 //! @param port - port number
193 void toBind(const std::string& ip, const std::uint16_t port);
194 //! @brief Bind to transport port number with default ip address.
195 //! @param port - port number
196 void toBind(const std::uint16_t port);
197 //! @brief Listen on a port number. Wait for the connection to be established.
198 void toListen();
199 //! @brief Create the thread to accept the connection from the client.
200 //! @param detach - whether to detach
201 void toAccept(const bool detach = false);
202
203 //! @brief Alias for the handling on new connection.
204 using ConnectionCallback = std::function<void(const std::shared_ptr<TCPSocket>)>;
205 //! @brief Bind the callback to handle the new connection.
206 //! @param callback - callback on new connection
207 void subscribeConnection(ConnectionCallback callback);
208
209private:
210 //! @brief Handling on new connection.
211 std::atomic<std::shared_ptr<ConnectionCallback>> connCb;
212
213 //! @brief Accept the connection on socket FD.
214 //! @param server - target server
215 static void toAccept(const std::shared_ptr<TCPServer> server);
216
217 //! @brief Emit the new connection.
218 //! @param client - new connected client
219 void onConnection(const std::shared_ptr<TCPSocket> client) const;
220};
221
222//! @brief UDP socket.
223class UDPSocket : public Socket, public std::enable_shared_from_this<UDPSocket>
224{
225public:
226 //! @brief Construct a new UDPSocket object.
227 //! @param sockId - socket id
228 explicit UDPSocket(const int sockId = -1) : Socket(Type::udp, sockId) {}
229
230 //! @brief Send bytes from the buffer on socket FD to peer.
231 //! @param bytes - bytes buffer
232 //! @param size - length of buffer
233 //! @param ip - peer ip address
234 //! @param port - peer port number
235 //! @return sent size
236 ::ssize_t toSendTo(
237 const char* const bytes, const std::size_t size, const std::string& ip, const std::uint16_t port);
238 //! @brief Send the message string on socket FD to peer.
239 //! @param message - message string
240 //! @param ip - peer ip address
241 //! @param port - peer port number
242 //! @return sent size
243 ::ssize_t toSendTo(const std::string_view message, const std::string& ip, const std::uint16_t port);
244 //! @brief Send bytes from the buffer to socket FD.
245 //! @param bytes - bytes buffer
246 //! @param size - length of buffer
247 //! @return sent size
248 ::ssize_t toSend(const char* const bytes, const std::size_t size);
249 //! @brief Send the message string to socket FD.
250 //! @param message - message string
251 //! @return sent size
252 ::ssize_t toSend(const std::string_view message);
253 //! @brief Open a connection on socket FD to peer.
254 //! @param ip - peer ip address
255 //! @param port - peer port number
256 void toConnect(const std::string& ip, const std::uint16_t port);
257 //! @brief Create the thread to receive.
258 //! @param detach - whether to detach
259 void toReceive(const bool detach = false);
260 //! @brief Create the thread to receive from peer.
261 //! @param detach - whether to detach
262 void toReceiveFrom(const bool detach = false);
263
264 //! @brief Alias for the handling on message received.
265 using MessageCallback = std::function<void(const std::string_view, const std::string&, const std::uint16_t)>;
266 //! @brief Alias for the handling on raw message received.
267 using RawMessageCallback =
268 std::function<void(char* const, const std::size_t, const std::string&, const std::uint16_t)>;
269 //! @brief Bind the callback to handle the received message.
270 //! @param callback - callback on received message
271 void subscribeMessage(MessageCallback callback);
272 //! @brief Bind the callback to handle the received raw message.
273 //! @param callback - callback on received raw message
274 void subscribeRawMessage(RawMessageCallback callback);
275
276private:
277 //! @brief Handling on message received.
278 std::atomic<std::shared_ptr<MessageCallback>> msgCb;
279 //! @brief Handling on raw message received.
280 std::atomic<std::shared_ptr<RawMessageCallback>> rawMsgCb;
281
282 //! @brief Receive bytes from socket FD.
283 //! @param socket - target socket
284 static void toRecv(const std::shared_ptr<UDPSocket> socket);
285 //! @brief Receive bytes through socket FD.
286 //! @param socket - target socket
287 static void toRecvFrom(const std::shared_ptr<UDPSocket> socket);
288
289 //! @brief Emit the received message.
290 //! @param message - received message
291 //! @param ip - source ip address
292 //! @param port - source port number
293 void onMessage(const std::string_view message, const std::string& ip, const std::uint16_t port) const;
294 //! @brief Emit the received raw message.
295 //! @param bytes - received bytes buffer
296 //! @param size - length of buffer
297 //! @param ip - source ip address
298 //! @param port - source port number
299 void onRawMessage(char* const bytes, const std::size_t size, const std::string& ip, const std::uint16_t port) const;
300};
301
302//! @brief UDP server.
303class UDPServer : public UDPSocket
304{
305public:
306 //! @brief Bind to transport ip address and port number.
307 //! @param ip - ip address
308 //! @param port - port number
309 void toBind(const std::string& ip, const std::uint16_t port);
310 //! @brief Bind to transport port number with default ip address.
311 //! @param port - port number
312 void toBind(const std::uint16_t port);
313};
314} // namespace socket
315} // namespace utility
316