File udpsocket.hpp
File List > illixr > network > udpsocket.hpp
Go to the documentation of this file
#pragma once
#if defined(_WIN32) || defined(_WIN64)
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# endif
# ifndef _WINSOCKAPI_
# define _WINSOCKAPI_
# endif
// clang-format off
# include
# include
# include
# pragma comment(lib, "Ws2_32.lib")
# define BYTE_TYPE int
# define SOCKET_TYPE SOCKET
// clang-format on
#else
# include
# include
# include
# include
# include
# define BYTE_TYPE ssize_t
# define SOCKET_TYPE int
#endif
#include "illixr/export.hpp"
#include
#include
#include
#include
#include
namespace ILLIXR::network {
class MY_EXPORT_API UDPSocket {
public:
UDPSocket() {
#if defined(_WIN32) || defined(_WIN64)
static const bool initialized = [] {
WSAData wsa_data;
if (WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0)
throw std::runtime_error("WSAStartup failed.");
return true;
}();
(void) initialized;
fd_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (fd_ == INVALID_SOCKET)
throw std::runtime_error("UDP socket creation failed");
#else
fd_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (fd_ < 0)
throw std::runtime_error("UDP socket creation failed");
#endif
}
~UDPSocket() {
#if defined(_WIN32) || defined(_WIN64)
closesocket(fd_);
#else
close(fd_);
#endif
}
// Bind to a local address and port (required on the server side, optional on the client)
void socket_bind(const std::string& ip, int port) const {
sockaddr_in local_addr{};
local_addr.sin_family = AF_INET;
local_addr.sin_port = htons(static_cast<uint16_t>(port));
local_addr.sin_addr.s_addr = ip.empty() ? INADDR_ANY : inet_addr(ip.c_str());
if (bind(fd_, reinterpret_cast<sockaddr*>(&local_addr), sizeof(local_addr)) < 0)
throw std::runtime_error("UDP bind failed");
}
// Bind to all interfaces on a given port (server convenience overload)
void socket_bind(int port) const {
socket_bind("", port);
}
// Allow reuse of local addresses.
// On Linux/Android, SO_REUSEPORT also permits rebinding during a restart.
void socket_set_reuseaddr() const {
#if defined(_WIN32) || defined(_WIN64)
int enable = 1;
// Use SO_EXCLUSIVEADDRUSE on Windows - SO_REUSEADDR has unsafe semantics there
if (setsockopt(fd_, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, reinterpret_cast<const char*>(&enable), sizeof(enable)) < 0)
throw std::runtime_error("SO_EXCLUSIVEADDRUSE failed");
#else
const int enable = 1;
setsockopt(fd_, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
setsockopt(fd_, SOL_SOCKET, SO_REUSEPORT, &enable, sizeof(int));
#endif
}
void socket_set_receive_timeout(int milliseconds) const {
#if defined(_WIN32) || defined(_WIN64)
const DWORD timeout = static_cast<DWORD>(milliseconds);
if (setsockopt(fd_, SOL_SOCKET, SO_RCVTIMEO, reinterpret_cast<const char*>(&timeout), sizeof(timeout)) < 0)
throw std::runtime_error("SO_RCVTIMEO failed");
#else
const timeval timeout{milliseconds / 1000, (milliseconds % 1000) * 1000};
if (setsockopt(fd_, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0)
throw std::runtime_error("SO_RCVTIMEO failed");
#endif
}
void socket_shutdown() const noexcept {
#if defined(_WIN32) || defined(_WIN64)
shutdown(fd_, SD_BOTH);
#else
shutdown(fd_, SHUT_RDWR);
#endif
}
// Store the peer address for use by write_data().
// On the client this is called once with the server's address.
// On the server this is called each time a datagram is received from a new peer.
void set_peer(const std::string& ip, int port) {
std::lock_guard<std::mutex> lock(peer_mutex_);
std::memset(&peer_addr_, 0, sizeof(peer_addr_));
peer_addr_.sin_family = AF_INET;
peer_addr_.sin_port = htons(static_cast<uint16_t>(port));
inet_pton(AF_INET, ip.c_str(), &peer_addr_.sin_addr);
peer_set_ = true;
}
// Store the peer address directly from a sockaddr_in, e.g. the src_addr filled in
// by read_data(). This lets the server learn the client's address from an incoming
// datagram, since UDP is connectionless and the server has no peer until it hears
// from one.
void set_peer(const sockaddr_in& addr) {
std::lock_guard<std::mutex> lock(peer_mutex_);
peer_addr_ = addr;
peer_set_ = true;
}
// Send a datagram to the previously set peer address.
// Returns false if no peer has been set or the send fails.
bool write_data(const std::string& buffer) const {
std::lock_guard<std::mutex> lock(peer_mutex_);
if (!peer_set_)
return false;
BYTE_TYPE sent = sendto(fd_,
#if defined(_WIN32) || defined(_WIN64)
buffer.data(), static_cast<int>(buffer.size()),
#else
buffer.data(), buffer.size(),
#endif
0, reinterpret_cast<const sockaddr*>(&peer_addr_), sizeof(peer_addr_));
return sent == static_cast<BYTE_TYPE>(buffer.size());
}
// Send a datagram to an explicit destination (used by the server to reply to a specific client).
bool write_data_to(const std::string& buffer, const sockaddr_in& dest) const {
BYTE_TYPE sent = sendto(fd_,
#if defined(_WIN32) || defined(_WIN64)
buffer.data(), static_cast<int>(buffer.size()),
#else
buffer.data(), buffer.size(),
#endif
0, reinterpret_cast<const sockaddr*>(&dest), sizeof(dest));
return sent == static_cast<BYTE_TYPE>(buffer.size());
}
// Receive a single datagram. Blocks until data arrives.
// Optionally fills src_addr with the sender's address.
[[nodiscard]] std::string read_data(sockaddr_in* src_addr = nullptr) const {
char buffer[BUFFER_SIZE];
sockaddr_in from{};
#if defined(_WIN32) || defined(_WIN64)
int from_len = sizeof(from);
#else
socklen_t from_len = sizeof(from);
#endif
BYTE_TYPE bytes = recvfrom(fd_, buffer,
#if defined(_WIN32) || defined(_WIN64)
static_cast<int>(BUFFER_SIZE),
#else
BUFFER_SIZE,
#endif
0, reinterpret_cast<sockaddr*>(&from), &from_len);
if (bytes <= 0)
return {};
if (src_addr != nullptr)
*src_addr = from;
return std::string(buffer, static_cast<size_t>(bytes));
}
/* accessors */
[[nodiscard]] std::string local_address() const {
sockaddr_in local{};
#if defined(_WIN32) || defined(_WIN64)
int size = sizeof(local);
#else
socklen_t size = sizeof(local);
#endif
getsockname(fd_, reinterpret_cast<sockaddr*>(&local), &size);
#if defined(_WIN32) || defined(_WIN64)
char ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &local.sin_addr, ip, sizeof(ip));
#else
char* ip = inet_ntoa(local.sin_addr);
#endif
return std::string(ip) + ":" + std::to_string(ntohs(local.sin_port));
}
[[nodiscard]] bool has_peer() const {
std::lock_guard<std::mutex> lock(peer_mutex_);
return peer_set_;
}
private:
SOCKET_TYPE fd_;
mutable std::mutex peer_mutex_;
sockaddr_in peer_addr_{};
bool peer_set_{false};
static constexpr size_t BUFFER_SIZE = 1024 * 64; // 64 KB max datagram
};
} // namespace ILLIXR::network