Skip to content

Commit

Permalink
feat(core): impl network TcpSocket class
Browse files Browse the repository at this point in the history
  • Loading branch information
roby2014 committed Jul 2, 2024
1 parent c5d7fa0 commit 76c04c7
Show file tree
Hide file tree
Showing 3 changed files with 241 additions and 0 deletions.
1 change: 1 addition & 0 deletions core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ set(CUBOS_CORE_SOURCE
"src/geom/intersections.cpp"

"src/net/address.cpp"
"src/net/tcp_socket.cpp"
)

# Create core library
Expand Down
76 changes: 76 additions & 0 deletions core/include/cubos/core/net/tcp_socket.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/// @file
/// @brief Class @ref cubos::core::net::TcpSocket.
/// @ingroup core-net

#pragma once

#include <cstdint>

#include <cubos/core/api.hpp>
#include <cubos/core/net/address.hpp>

#ifdef _WIN32
#include <WinSock2.h>
#include <vcruntime.h>
#else
// Socket type. Differs from Windows (SOCKET) and POSIX (int).
#define SOCKET int
// Reprensets an invalid socket.
#define INVALID_SOCKET (-1)
#endif

namespace cubos::core::net
{
/// @brief Represents a TCP socket.
/// @ingroup core-net
class CUBOS_CORE_API TcpSocket final
{
public:
/// @brief Constructs a empty TCP socket.
TcpSocket();

/// @brief Deconstructs by desconnecting the inner socket.
~TcpSocket();

/// @brief Forbid copy construction.
TcpSocket(const TcpSocket&) = delete;

/// @brief Forbid copy assignment.
TcpSocket& operator=(const TcpSocket&) = delete;

/// @brief Connects via TCP to a remote `address` and `port`.
/// @param address Destination address.
/// @param port Destination port.
/// @param timeoutMs Connection timeout in milliseconds.
/// @return Whether connecting was successful.
bool connect(const Address& address, uint16_t port, int timeoutMs = 0);

/// @brief Disconnects inner socket.
void disconnect();

/// @brief Sends `data` to the connected socket.
/// @param data Message.
/// @param size Message size.
/// @return Whether sending was successful.
bool send(const void* data, std::size_t size);

/// @brief Receives a single datagram message on the socket.
/// @param data Buffer to store the datagram message.
/// @param size Maximum buffer size.
/// @param[out] received Number of bytes read.
/// @return Whether receiving was successful.
bool receive(void* data, std::size_t size, std::size_t& received);

/// @brief Control blocking mode of inner socket.
/// @note A blocking socket does not return control until it has sent (or received) some or all data
/// specified for the operation.
/// @note A non-blocking socket returns whatever is in the receive buffer and immediately continues.
/// @param blocking Whether to block or not.
/// @return Whether setting the block flag was sucessfull.
bool setBlocking(bool blocking);

private:
SOCKET mSock{INVALID_SOCKET};
bool mBlocking{false};
};
} // namespace cubos::core::net
164 changes: 164 additions & 0 deletions core/src/net/tcp_socket.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "Ws2_32.lib")
#else
#include <arpa/inet.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#endif

#include <cubos/core/log.hpp>
#include <cubos/core/net/tcp_socket.hpp>
#include <cubos/core/reflection/external/cstring.hpp>
#include <cubos/core/reflection/external/primitives.hpp>
#include <cubos/core/reflection/external/string.hpp>

using cubos::core::net::Address;
using cubos::core::net::TcpSocket;

/// @brief Cross-platform utility to get last error message.
/// @return Message.
/// @todo Move this to a higher module for general purposes?
static std::string error()
{
#ifdef _WIN32
static char message[256] = {0};
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, WSAGetLastError(), 0, message, 256, 0);
char* nl = strrchr(message, '\n');
if (nl)
*nl = 0;
return {message};
#else
return {strerror(errno)};
#endif
}

TcpSocket::TcpSocket()
{
#ifdef _WIN32
WSADATA wsa;
CUBOS_ASSERT(WSAStartup(MAKEWORD(2, 2), &wsa) == 0, " WSAStartup failed: {}", WSAGetLastError());
#endif
}

TcpSocket::~TcpSocket()
{
disconnect();
}

bool TcpSocket::connect(const Address& address, uint16_t port, int timeoutMs)
{
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(address.toString().c_str());
addr.sin_port = htons(port);

if (::connect(mSock, (struct sockaddr*)&addr, sizeof(addr)) < 0)
{
CUBOS_ERROR("Failed to connect to address {} at port '{}': {}", address.toString(), port, error());
return false;
}

(void)timeoutMs;
// TODO implement timeout

CUBOS_INFO("New TCP connection at address {} and port '{}'", address.toString(), port);
return true;
}

void TcpSocket::disconnect()
{
if (mSock != -1)
{
CUBOS_WARN("Closing TCP socket: '{}'", mSock);
#ifdef _WIN32
closesocket(mSock);
WSACleanup();
#else
close(mSock);
#endif
}
}

bool TcpSocket::send(const void* data, std::size_t size)
{
if (mSock == -1)
{
CUBOS_ERROR("Invalid socket");
return false;
}

#ifdef _WIN32
auto res = ::send(mSock, static_cast<const char*>(data), size, 0);
#else
auto res = ::send(mSock, data, size, 0);
#endif
if (res < 0)
{
CUBOS_ERROR("Failed to send TCP data: {}", error());
return false;
}

CUBOS_INFO("Sent TCP message");
return true;
}

bool TcpSocket::receive(void* data, std::size_t size, std::size_t& received)
{
if (mSock == -1)
{
CUBOS_ERROR("Invalid socket");
return false;
}

#ifdef _WIN32
received = ::recv(mSock, static_cast<char*>(data), size, 0);
#else
received = ::recv(mSock, data, size, 0);
#endif
if (received < 0)
{
CUBOS_ERROR("Failed to receive TCP data: {}", error());
return false;
}

CUBOS_INFO("Received TCP message");
return true;
}

bool TcpSocket::setBlocking(bool blocking)
{
bool success = false;

#ifdef _WIN32
u_long flags = blocking ? 0 : 1;
success = NO_ERROR == ioctlsocket(mSock, FIONBIO, &flags);
#else
mBlocking = blocking;

int flags = fcntl(mSock, F_GETFL, 0);
if (blocking)
{
flags &= ~O_NONBLOCK;
}
else
{
flags |= O_NONBLOCK;
}
success = fcntl(mSock, F_SETFL, flags) == 0;
#endif
if (!success)
{
CUBOS_ERROR("Failed to change block flag of socket '{}'", mSock);
}
else
{
CUBOS_INFO("UDP socket {} is now '{}'", mSock, blocking ? "blocking" : "non-blocking");
}

return success;
}

0 comments on commit 76c04c7

Please sign in to comment.