-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(core): impl network TcpListener class
- Loading branch information
Showing
2 changed files
with
206 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |