Skip to content

Commit

Permalink
feat(core): impl network TcpListener class
Browse files Browse the repository at this point in the history
  • Loading branch information
roby2014 committed Jul 28, 2024
1 parent 74e1fc6 commit 9338f3b
Show file tree
Hide file tree
Showing 2 changed files with 206 additions and 0 deletions.
72 changes: 72 additions & 0 deletions core/include/cubos/core/net/tcp_listener.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/// @file
/// @brief Class @ref cubos::core::net::TcpListener.
/// @ingroup core-net

#pragma once

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

namespace cubos::core::net
{
/// @brief A TCP socket server, listening for connections.
/// @ingroup core-net
class CUBOS_CORE_API TcpListener final
{
public:
/// @brief Create an empty TCP listener, ready for connecting.
TcpListener();

/// @brief Deconstructs by calling @ref close.
~TcpListener();

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

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

/// @brief Start listening for connection at specified `address` and `port`.
/// @param address Destination address.
/// @param port Destination port.
/// @param maxQueueLength Maximum connection requests.
/// @return Whether start listening was successful.
bool listen(const Address& address, uint16_t port, int maxQueueLength = 4096);

/// @brief Accepts a new connection.
/// @param[out] stream Buffer to store the connection stream.
/// @return Whether accepting the connection and creating the stream was successful.
/// @note This function will block until a connection is made.
bool accept(TcpStream& stream) const;

/// @brief Close the listener socket.
void close();

/// @brief Check if connection is valid.
/// @return Whether inner sock is connected.
inline bool valid() const
{
return mSock != InnerInvalidSocket;
}

/// @brief Get inner socket handle.
/// @return Socket.
inline InnerSocket inner() const
{
return mSock;
}

/// @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.
/// @param blocking Whether to block or not.
/// @return Whether setting the block flag was sucessful.
bool setBlocking(bool blocking);

private:
InnerSocket mSock{InnerInvalidSocket};
bool mBlocking{true};
};
} // namespace cubos::core::net
134 changes: 134 additions & 0 deletions core/src/net/tcp_listener.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#ifdef _WIN32
#include <winsock2.h>
#else
#include <arpa/inet.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#endif

#include <cstring>
#include <memory>

#include <cubos/core/log.hpp>
#include <cubos/core/net/tcp_listener.hpp>
#include <cubos/core/net/tcp_stream.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::TcpListener;
using cubos::core::net::TcpStream;

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

TcpListener::~TcpListener()
{
close();
}

bool TcpListener::listen(const Address& address, uint16_t port, int maxQueueLength)
{
close();

mSock = socket(AF_INET, SOCK_STREAM, 0);
if (mSock == InnerInvalidSocket)
{
CUBOS_ERROR("Failed to allocate TCP socket: {}", logSystemError());
return false;
}

sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(address.toString().c_str());

if (::bind(mSock, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)) == -1)
{
CUBOS_ERROR("Failed to bind TCP socket: {}", logSystemError());
return false;
}

if (::listen(mSock, maxQueueLength) < 0)
{
CUBOS_ERROR("Failed to listen to TCP socket: {}", logSystemError());
return false;
}

CUBOS_DEBUG("TCP listener socket '{}' bound to address {} with port '{}'", mSock, address.toString(), port);
return true;
}

bool TcpListener::accept(TcpStream& stream) const
{
if (mSock == InnerInvalidSocket)
{
CUBOS_ERROR("Listener hasn't been initialized correctly");
return false;
}

auto clientSocket = ::accept(mSock, nullptr, nullptr);
if (clientSocket == InnerInvalidSocket)
{
CUBOS_ERROR("Failed to accept TCP connection: {}", logSystemError());
return false;
}

stream.inner(clientSocket);
return true;
}

void TcpListener::close()
{
if (mSock != InnerInvalidSocket)
{
#ifdef _WIN32
closesocket(mSock);
WSACleanup();
#else
::close(mSock);
#endif
mSock = InnerInvalidSocket;
}
}

bool TcpListener::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);
}

return success;
}

0 comments on commit 9338f3b

Please sign in to comment.