Compare commits

...

3 Commits
v1.0 ... main

9 changed files with 892 additions and 303 deletions

View File

@ -43,7 +43,7 @@ FetchContent_MakeAvailable(googletest)
#
add_executable(playground src/main.cpp src/tcp.cpp)
add_executable(playground src/main.cpp src/tcp_server.cpp src/tcp_client.cpp)
target_link_libraries(playground PRIVATE spdlog::spdlog)
@ -54,24 +54,28 @@ target_link_libraries(playground PRIVATE spdlog::spdlog)
#
include(GoogleTest)
enable_testing()
add_executable(
tcp_test
tests/tcp.cpp
src/tcp.cpp
)
target_link_libraries(
tcp_test
spdlog::spdlog
GTest::gtest_main
)
target_include_directories(
tcp_test
PRIVATE
src
add_executable(tcp_blocking_test
tests/tcp_blocking.cpp
src/tcp_server.cpp
src/tcp_client.cpp
)
target_link_libraries(tcp_blocking_test spdlog::spdlog GTest::gtest_main)
target_include_directories(tcp_blocking_test PRIVATE src)
include(GoogleTest)
gtest_discover_tests(tcp_test)
gtest_discover_tests(tcp_blocking_test)
add_executable(tcp_non_blocking_test
tests/tcp_non_blocking.cpp
src/tcp_server.cpp
src/tcp_client.cpp
)
target_link_libraries(tcp_non_blocking_test spdlog::spdlog GTest::gtest_main)
target_include_directories(tcp_non_blocking_test PRIVATE src)
gtest_discover_tests(tcp_non_blocking_test)

54
src/common.hpp Normal file
View File

@ -0,0 +1,54 @@
#pragma once
// STL includes
#include <expected>
// C stdlib includes
#include <netdb.h>
#include <netinet/in.h>
// Library includes
#include <spdlog/spdlog.h>
namespace tcp {
namespace detail {
inline std::expected<sockaddr_in, int> resolve_remote_address(const char* host,
uint16_t port) {
hostent* server = gethostbyname(host);
if (server == nullptr) {
spdlog::error("tcp: get_host_by_name() failed with h_errno={}",
h_errno);
return std::unexpected{h_errno};
}
sockaddr_in address{};
address.sin_family = AF_INET;
address.sin_port = htons(port);
bcopy((char*)server->h_addr, (char*)&address.sin_addr.s_addr,
server->h_length);
return address;
}
inline sockaddr_in get_local_address(uint16_t port) {
sockaddr_in address{};
address.sin_addr.s_addr = htonl(INADDR_ANY);
address.sin_family = AF_INET;
address.sin_port = htons(port);
return address;
}
} // namespace detail
using HostString = std::array<char, 64>;
} // namespace tcp

View File

@ -2,7 +2,7 @@
#include <spdlog/spdlog.h>
// Project includes
#include "tcp.hpp"
#include "tcp_server.hpp"
int main() {

300
src/tcp_client.cpp Normal file
View File

@ -0,0 +1,300 @@
#include "tcp_client.hpp"
// C stdlib includes
#include <arpa/inet.h>
#include <fcntl.h>
#include <netdb.h>
// Library includes
#include <spdlog/spdlog.h>
namespace tcp {
//
//
// Blocking client
//
//
BlockingClient::~BlockingClient() {
close_socket();
}
[[nodiscard]] std::expected<void, int>
BlockingClient::connect(const HostString& host, uint16_t port) {
/// Resolve host
auto addrRes = detail::resolve_remote_address(host.data(), port);
if (!addrRes) return std::unexpected{addrRes.error()};
sockaddr_in serverAddress = addrRes.value();
/// Create socket
close_socket();
m_socket = socket(AF_INET, SOCK_STREAM, 0);
if (m_socket < 1) {
spdlog::error("tcp::BlockingClient: socket() failed with errno={}",
errno);
close_socket();
return std::unexpected{errno};
}
/// Connect
int connectRes = ::connect(m_socket, (struct sockaddr*)&serverAddress,
sizeof(serverAddress));
if (connectRes < 0) {
spdlog::error("tcp::BlockingClient: connect() failed with errno={}",
errno);
return std::unexpected{errno};
}
return {};
}
void BlockingClient::disconnect() {
close_socket();
}
[[nodiscard]] std::expected<int, int>
BlockingClient::send(std::span<const std::byte> data) {
const int bytesWritten =
::send(m_socket, (const char*)data.data(), data.size(), 0);
if (bytesWritten <= 0) {
spdlog::error("tcp::BlockingClient: send() failed with errno={}",
errno);
return std::unexpected{errno};
}
return bytesWritten;
}
[[nodiscard]] std::expected<int, int>
BlockingClient::recv(std::span<std::byte> buffer) {
const int bytesReceived =
::recv(m_socket, (char*)buffer.data(), buffer.size(), 0);
if (bytesReceived < 0) {
spdlog::error("tcp::BlockingClient: recv() failed with errno={}",
errno);
return std::unexpected{errno};
}
return {bytesReceived};
}
void BlockingClient::close_socket() {
if (shutdown(m_socket, 0) < 0) {
spdlog::debug("tcp::BlockingClient: shutdown() failed with errno={}",
errno);
}
if (close(m_socket) < 0) {
spdlog::debug("tcp::BlockingClient: close() failed with errno={}",
errno);
}
m_socket = -1;
}
//
//
// Non blocking client
//
//
NonBlockingClient::~NonBlockingClient() {
close_socket();
}
[[nodiscard]] std::expected<void, int>
NonBlockingClient::connect(const HostString& host, uint16_t port) {
/// Resolve host
auto addrRes = detail::resolve_remote_address(host.data(), port);
if (!addrRes) return std::unexpected{addrRes.error()};
sockaddr_in serverAddress = addrRes.value();
/// Create socket
close_socket();
if (auto res = create_socket(); !res) {
return std::unexpected{res.error()};
}
/// Connect
int connectRes = ::connect(m_socket, (struct sockaddr*)&serverAddress,
sizeof(serverAddress));
if (connectRes < 0 && errno != EINPROGRESS) {
spdlog::error("tcp::NonBlockingClient: connect() failed with errno={}",
errno);
return std::unexpected{errno};
}
m_startedNewConAttempt = true;
return {};
}
void NonBlockingClient::disconnect() {
m_lastKnownConStatus = ENOTCONN;
close_socket();
}
///
/// @brief Get last known connection status
/// @return - expected(EINPROGRESS) if connection is still being attempted
/// - expected(EISCONN) if already connected
/// - expected(error code) if an error occurred during connection (e.g.,
/// ECONNREFUSED)
/// - unexpected(errno) if an error occurred while checking connection
/// status
///
[[nodiscard]] std::expected<int, int>
NonBlockingClient::get_last_connection_status() {
if (!m_startedNewConAttempt) return m_lastKnownConStatus;
/// Check if connect operation has been completed
auto pendingOpRes = socket_has_new_output_event();
if (!pendingOpRes) return std::unexpected{pendingOpRes.error()};
if (!pendingOpRes.value()) return EINPROGRESS;
m_startedNewConAttempt = false;
/// Check for connection errors
int err = 0;
socklen_t len = sizeof(err);
if (getsockopt(m_socket, SOL_SOCKET, SO_ERROR, &err, &len) < 0) {
spdlog::error(
"tcp::NonBlockingClient: getsockopt() failed with errno={}", errno);
return std::unexpected{errno};
}
if (err == 0) {
m_lastKnownConStatus = EISCONN;
} else {
m_lastKnownConStatus = err;
}
return m_lastKnownConStatus;
}
bool NonBlockingClient::data_available() {
auto conRes = get_last_connection_status();
if (!conRes) return false;
if (conRes.value() != EISCONN) return false;
int ret = poll(&m_pfdIn, 1, 0);
if (ret < 0) {
spdlog::error("tcp::NonBlockingClient: poll() failed with errno={}",
errno);
return false;
}
return (m_pfdIn.revents & POLLIN);
}
[[nodiscard]] std::expected<int, int>
NonBlockingClient::send(std::span<const std::byte> data) {
const int bytesWritten =
::send(m_socket, (const char*)data.data(), data.size(), 0);
if (bytesWritten <= 0) {
spdlog::error("tcp::NonBlockingClient: send() failed with errno={}",
errno);
return std::unexpected{errno};
}
return bytesWritten;
}
[[nodiscard]] std::expected<int, int>
NonBlockingClient::recv(std::span<std::byte> buffer) {
const int bytesReceived =
::recv(m_socket, (char*)buffer.data(), buffer.size(), 0);
if (bytesReceived < 0) {
spdlog::error("tcp::NonBlockingClient: recv() failed with errno={}",
errno);
return std::unexpected{errno};
}
return {bytesReceived};
}
[[nodiscard]] std::expected<void, int> NonBlockingClient::create_socket() {
m_socket = socket(AF_INET, SOCK_STREAM, 0);
if (m_socket < 1) {
spdlog::error("tcp::NonBlockingClient: socket() failed with errno={}",
errno);
close_socket();
return std::unexpected{errno};
}
m_pfdOut.fd = m_socket;
m_pfdIn.fd = m_socket;
auto currentFlags = fcntl(m_socket, F_GETFL, 0);
if (currentFlags == -1) {
spdlog::error("tcp::NonBlockingClient: fcntl() failed with errno={}",
errno);
close_socket();
return std::unexpected{errno};
}
if (fcntl(m_socket, F_SETFL, (currentFlags) | O_NONBLOCK) == -1) {
spdlog::error("tcp::NonBlockingClient: fcntl() failed with errno={}",
errno);
close_socket();
return std::unexpected{errno};
}
return {};
}
void NonBlockingClient::close_socket() {
if (shutdown(m_socket, 0) < 0) {
spdlog::debug("tcp::NonBlockingClient: shutdown() failed with errno={}",
errno);
}
if (close(m_socket) < 0) {
spdlog::debug("tcp::NonBlockingClient: close() failed with errno={}",
errno);
}
m_socket = -1;
m_pfdOut.fd = -1;
m_pfdIn.fd = -1;
}
///
/// @return true if there is new output event, i.e., the connect() operation has
/// completed (successfully or not)
///
[[nodiscard]] std::expected<bool, int>
NonBlockingClient::socket_has_new_output_event() {
int ret = poll(&m_pfdOut, 1, 0);
if (ret < 0) {
spdlog::error("tcp::NonBlockingClient: poll() failed with errno={}",
errno);
return std::unexpected{errno};
}
// ret == 0 means the operation timed out, i.e., no new events
return (ret != 0);
}
} // namespace tcp

95
src/tcp_client.hpp Normal file
View File

@ -0,0 +1,95 @@
#pragma once
// STL includes
#include <cerrno>
#include <cstdint>
#include <expected>
#include <span>
// C stdlib includes
#include <netinet/in.h>
#include <sys/poll.h>
// Project includes
#include "common.hpp"
namespace tcp {
//
//
// Blocking client
//
//
class BlockingClient {
public:
BlockingClient() = default;
BlockingClient(const BlockingClient&) = delete;
BlockingClient(BlockingClient&&) = delete;
BlockingClient& operator=(const BlockingClient&) = delete;
BlockingClient& operator=(BlockingClient&&) = delete;
~BlockingClient();
[[nodiscard]] std::expected<void, int> connect(const HostString& host,
uint16_t port);
void disconnect();
[[nodiscard]] std::expected<int, int> send(std::span<const std::byte> data);
[[nodiscard]] std::expected<int, int> recv(std::span<std::byte> buffer);
private:
int m_socket = -1;
void close_socket();
};
//
//
// Non blocking client
//
//
class NonBlockingClient {
public:
NonBlockingClient() = default;
NonBlockingClient(const NonBlockingClient&) = delete;
NonBlockingClient(NonBlockingClient&&) = delete;
NonBlockingClient& operator=(const NonBlockingClient&) = delete;
NonBlockingClient& operator=(NonBlockingClient&&) = delete;
~NonBlockingClient();
[[nodiscard]] std::expected<void, int> connect(const HostString& host,
uint16_t port);
void disconnect();
[[nodiscard]] std::expected<int, int> get_last_connection_status();
bool data_available();
[[nodiscard]] std::expected<int, int> send(std::span<const std::byte> data);
[[nodiscard]] std::expected<int, int> recv(std::span<std::byte> buffer);
private:
int m_socket = -1;
pollfd m_pfdOut = {.fd = -1, .events = POLLOUT};
pollfd m_pfdIn = {.fd = -1, .events = POLLIN};
bool m_startedNewConAttempt = false;
int m_lastKnownConStatus = ENOTCONN;
[[nodiscard]] std::expected<void, int> create_socket();
void close_socket();
[[nodiscard]] std::expected<bool, int> socket_has_new_output_event();
};
} // namespace tcp

View File

@ -1,4 +1,4 @@
#include "tcp.hpp"
#include "tcp_server.hpp"
// C stdlib includes
#include <arpa/inet.h>
@ -8,43 +8,177 @@
// Library includes
#include <spdlog/spdlog.h>
namespace {
std::expected<sockaddr_in, int> resolve_remote_address(const char* host,
uint16_t port) {
hostent* server = gethostbyname(host);
if (server == nullptr) {
spdlog::error("tcp: get_host_by_name() failed with h_errno={}",
h_errno);
return std::unexpected{h_errno};
}
sockaddr_in address{};
address.sin_family = AF_INET;
address.sin_port = htons(port);
bcopy((char*)server->h_addr, (char*)&address.sin_addr.s_addr,
server->h_length);
return address;
}
inline sockaddr_in get_local_address(uint16_t port) {
sockaddr_in address{};
address.sin_addr.s_addr = htonl(INADDR_ANY);
address.sin_family = AF_INET;
address.sin_port = htons(port);
return address;
}
} // namespace
// Project includes
#include "common.hpp"
namespace tcp {
//
//
// Blocking server
//
//
BlockingServer::~BlockingServer() {
close_client_socket();
close_server_socket();
}
///
/// @details Calling this repeatedly closes the previous socket and creates a
/// new one.
///
std::expected<void, int> BlockingServer::start_listening(uint16_t port) {
/// Create socket
close_server_socket();
if (auto res = create_server_socket(); !res) {
return std::unexpected{res.error()};
}
/// Bind socket and start listening
const struct sockaddr_in serverAddress = detail::get_local_address(port);
if (::bind(m_serverSocket, (struct sockaddr*)&serverAddress,
sizeof(serverAddress)) != 0) {
spdlog::error("tcp::BlockingServer: bind() failed with errno={}",
errno);
close_server_socket();
return std::unexpected{errno};
};
if (::listen(m_serverSocket, 1) != 0) {
spdlog::error("tcp::BlockingServer: listen() failed with errno={}",
errno);
close_server_socket();
return std::unexpected{errno};
}
return {};
}
void BlockingServer::stop_listening() {
close_server_socket();
}
[[nodiscard]] std::expected<void, int> BlockingServer::accept() {
disconnect_from_client();
socklen_t clientAddrLen = sizeof(m_clientAddress);
int result = ::accept(m_serverSocket, (struct sockaddr*)&m_clientAddress,
&clientAddrLen);
if (result < 0) return std::unexpected{errno};
m_clientSocket = result;
return {};
}
void BlockingServer::disconnect_from_client() {
close_client_socket();
}
[[nodiscard]] std::expected<std::array<char, 16>, int>
BlockingServer::get_client_ip_as_string() {
std::array<char, 16> addrStr;
if (inet_ntop(AF_INET, &((struct sockaddr_in*)&m_clientAddress)->sin_addr,
addrStr.data(), addrStr.size()) == nullptr) {
spdlog::error("tcp::BlockingServer: inet_ntop() failed with errno={}",
errno);
return std::unexpected{errno};
}
// if (inet_ntoa_r(((struct sockaddr_in*)&m_clientAddress)->sin_addr,
// addrStr.data(), addrStr.size()) == nullptr) {
// return std::unexpected{errno};
// }
return addrStr;
}
[[nodiscard]] std::expected<int, int>
BlockingServer::send(std::span<const std::byte> data) {
const int bytesWritten =
::send(m_clientSocket, (const char*)data.data(), data.size(), 0);
if (bytesWritten <= 0) {
spdlog::error("tcp::BlockingServer: send() failed with errno={}",
errno);
return std::unexpected{errno};
}
return bytesWritten;
}
[[nodiscard]] std::expected<int, int>
BlockingServer::recv(std::span<std::byte> buffer) {
const int bytesReceived =
::recv(m_clientSocket, (char*)buffer.data(), buffer.size(), 0);
if (bytesReceived < 0) {
spdlog::error("tcp::BlockingServer: recv() failed with errno={}",
errno);
return std::unexpected{errno};
}
return {bytesReceived};
}
[[nodiscard]] std::expected<void, int> BlockingServer::create_server_socket() {
m_serverSocket = socket(AF_INET, SOCK_STREAM, 0);
if (m_serverSocket < 1) {
spdlog::error("tcp::BlockingServer: socket() failed with errno={}",
errno);
close_server_socket();
return std::unexpected{errno};
}
int flag = 1;
if (::setsockopt(m_serverSocket, SOL_SOCKET, SO_REUSEADDR, &flag,
sizeof(flag)) < 0) {
spdlog::error("tcp::BlockingServer: setsockopt() failed with errno={}",
errno);
close_server_socket();
return std::unexpected{errno};
}
return {};
}
void BlockingServer::close_server_socket() {
if (shutdown(m_serverSocket, 0) < 0) {
spdlog::debug("tcp::BlockingServer: shutdown() failed with errno={}",
errno);
}
if (close(m_serverSocket) < 0) {
spdlog::debug("tcp::BlockingServer: close() failed with errno={}",
errno);
}
m_serverSocket = -1;
}
void BlockingServer::close_client_socket() {
if (shutdown(m_clientSocket, 0) < 0) {
spdlog::debug("tcp::BlockingServer: shutdown() failed with errno={}",
errno);
}
if (close(m_clientSocket) < 0) {
spdlog::debug("tcp::BlockingServer: close() failed with errno={}",
errno);
}
m_clientSocket = -1;
}
//
//
// Non blocking server
@ -69,9 +203,9 @@ std::expected<void, int> NonBlockingServer::start_listening(uint16_t port) {
return std::unexpected{res.error()};
}
/// Bin socket and start listening
/// Bind socket and start listening
const struct sockaddr_in serverAddress = get_local_address(port);
const struct sockaddr_in serverAddress = detail::get_local_address(port);
if (::bind(m_serverSocket, (struct sockaddr*)&serverAddress,
sizeof(serverAddress)) != 0) {
@ -252,198 +386,4 @@ void NonBlockingServer::close_client_socket() {
}
//
//
// Non blocking client
//
//
NonBlockingClient::~NonBlockingClient() {
close_socket();
}
[[nodiscard]] std::expected<void, int>
NonBlockingClient::connect(const HostString& host, uint16_t port) {
/// Resolve host
auto addrRes = resolve_remote_address(host.data(), port);
if (!addrRes) return std::unexpected{addrRes.error()};
sockaddr_in serverAddress = addrRes.value();
/// Create socket
close_socket();
if (auto res = create_socket(); !res) {
return std::unexpected{res.error()};
}
/// Connect
int connectRes = ::connect(m_socket, (struct sockaddr*)&serverAddress,
sizeof(serverAddress));
if (connectRes < 0 && errno != EINPROGRESS) {
spdlog::error("tcp::NonBlockingClient: connect() failed with errno={}",
errno);
return std::unexpected{errno};
}
m_startedNewConAttempt = true;
return {};
}
void NonBlockingClient::disconnect() {
m_lastKnownConStatus = ENOTCONN;
close_socket();
}
///
/// @brief Get last known connection status
/// @return - expected(EINPROGRESS) if connection is still being attempted
/// - expected(EISCONN) if already connected
/// - expected(error code) if an error occurred during connection (e.g.,
/// ECONNREFUSED)
/// - unexpected(errno) if an error occurred while checking connection
/// status
///
[[nodiscard]] std::expected<int, int>
NonBlockingClient::get_last_connection_status() {
if (!m_startedNewConAttempt) return m_lastKnownConStatus;
/// Check if connect operation has been completed
auto pendingOpRes = socket_has_new_output_event();
if (!pendingOpRes) return std::unexpected{pendingOpRes.error()};
if (!pendingOpRes.value()) return EINPROGRESS;
m_startedNewConAttempt = false;
/// Check for connection errors
int err = 0;
socklen_t len = sizeof(err);
if (getsockopt(m_socket, SOL_SOCKET, SO_ERROR, &err, &len) < 0) {
spdlog::error(
"tcp::NonBlockingClient: getsockopt() failed with errno={}", errno);
return std::unexpected{errno};
}
if (err == 0) {
m_lastKnownConStatus = EISCONN;
} else {
m_lastKnownConStatus = err;
}
return m_lastKnownConStatus;
}
bool NonBlockingClient::data_available() {
auto conRes = get_last_connection_status();
if (!conRes) return false;
if (conRes.value() != EISCONN) return false;
int ret = poll(&m_pfdIn, 1, 0);
if (ret < 0) {
spdlog::error("tcp::NonBlockingClient: poll() failed with errno={}",
errno);
return false;
}
return (m_pfdIn.revents & POLLIN);
}
[[nodiscard]] std::expected<int, int>
NonBlockingClient::send(std::span<const std::byte> data) {
const int bytesWritten =
::send(m_socket, (const char*)data.data(), data.size(), 0);
if (bytesWritten <= 0) {
spdlog::error("tcp::NonBlockingClient: send() failed with errno={}",
errno);
return std::unexpected{errno};
}
return bytesWritten;
}
[[nodiscard]] std::expected<int, int>
NonBlockingClient::recv(std::span<std::byte> buffer) {
const int bytesReceived =
::recv(m_socket, (char*)buffer.data(), buffer.size(), 0);
if (bytesReceived < 0) {
spdlog::error("tcp::NonBlockingClient: recv() failed with errno={}",
errno);
return std::unexpected{errno};
}
return {bytesReceived};
}
[[nodiscard]] std::expected<void, int> NonBlockingClient::create_socket() {
m_socket = socket(AF_INET, SOCK_STREAM, 0);
if (m_socket < 1) {
spdlog::error("tcp::NonBlockingClient: socket() failed with errno={}",
errno);
close_socket();
return std::unexpected{errno};
}
m_pfdOut.fd = m_socket;
m_pfdIn.fd = m_socket;
auto currentFlags = fcntl(m_socket, F_GETFL, 0);
if (currentFlags == -1) {
spdlog::error("tcp::NonBlockingClient: fcntl() failed with errno={}",
errno);
close_socket();
return std::unexpected{errno};
}
if (fcntl(m_socket, F_SETFL, (currentFlags) | O_NONBLOCK) == -1) {
spdlog::error("tcp::NonBlockingClient: fcntl() failed with errno={}",
errno);
close_socket();
return std::unexpected{errno};
}
return {};
}
void NonBlockingClient::close_socket() {
if (shutdown(m_socket, 0) < 0) {
spdlog::debug("tcp::NonBlockingClient: shutdown() failed with errno={}",
errno);
}
if (close(m_socket) < 0) {
spdlog::debug("tcp::NonBlockingClient: close() failed with errno={}",
errno);
}
m_socket = -1;
m_pfdOut.fd = -1;
m_pfdIn.fd = -1;
}
///
/// @return true if there is new output event, i.e., the connect() operation has
/// completed (successfully or not)
///
[[nodiscard]] std::expected<bool, int>
NonBlockingClient::socket_has_new_output_event() {
int ret = poll(&m_pfdOut, 1, 0);
if (ret < 0) {
spdlog::error("tcp::NonBlockingClient: poll() failed with errno={}",
errno);
return std::unexpected{errno};
}
// ret == 0 means the operation timed out, i.e., no new events
return (ret != 0);
}
} // namespace tcp

View File

@ -2,7 +2,6 @@
// STL includes
#include <array>
#include <cerrno>
#include <cstdint>
#include <expected>
@ -16,7 +15,44 @@
namespace tcp {
using HostString = std::array<char, 64>;
//
//
// Blocking server
//
//
class BlockingServer {
public:
BlockingServer() = default;
BlockingServer(const BlockingServer&) = delete;
BlockingServer(BlockingServer&&) = delete;
BlockingServer& operator=(const BlockingServer&) = delete;
BlockingServer& operator=(BlockingServer&&) = delete;
~BlockingServer();
[[nodiscard]] std::expected<void, int> start_listening(uint16_t port);
void stop_listening();
[[nodiscard]] std::expected<void, int> accept();
void disconnect_from_client();
[[nodiscard]] std::expected<std::array<char, 16>, int>
get_client_ip_as_string();
[[nodiscard]] std::expected<int, int> send(std::span<const std::byte> data);
[[nodiscard]] std::expected<int, int> recv(std::span<std::byte> buffer);
private:
int m_serverSocket = -1;
int m_clientSocket = -1;
struct sockaddr m_clientAddress = {0};
[[nodiscard]] std::expected<void, int> create_server_socket();
void close_server_socket();
void close_client_socket();
};
//
@ -65,47 +101,4 @@ private:
};
//
//
// Non blocking client
//
//
class NonBlockingClient {
public:
NonBlockingClient() = default;
NonBlockingClient(const NonBlockingClient&) = delete;
NonBlockingClient(NonBlockingClient&&) = delete;
NonBlockingClient& operator=(const NonBlockingClient&) = delete;
NonBlockingClient& operator=(NonBlockingClient&&) = delete;
~NonBlockingClient();
[[nodiscard]] std::expected<void, int> connect(const HostString& host,
uint16_t port);
void disconnect();
[[nodiscard]] std::expected<int, int> get_last_connection_status();
bool data_available();
[[nodiscard]] std::expected<int, int> send(std::span<const std::byte> data);
[[nodiscard]] std::expected<int, int> recv(std::span<std::byte> buffer);
private:
int m_socket = -1;
pollfd m_pfdOut = {.fd = -1, .events = POLLOUT};
pollfd m_pfdIn = {.fd = -1, .events = POLLIN};
bool m_startedNewConAttempt = false;
int m_lastKnownConStatus = ENOTCONN;
[[nodiscard]] std::expected<void, int> create_socket();
void close_socket();
[[nodiscard]] std::expected<bool, int> socket_has_new_output_event();
};
} // namespace tcp

202
tests/tcp_blocking.cpp Normal file
View File

@ -0,0 +1,202 @@
#include <gtest/gtest.h>
// STL includes
#include <cstring>
#include <thread>
// Project includes
#include "tcp_client.hpp"
#include "tcp_server.hpp"
TEST(BlockingTcpServer, Accept) {
tcp::BlockingServer server;
tcp::BlockingClient client;
auto lisRes = server.start_listening(1234);
std::thread clientThread([&client]() {
auto conRes = client.connect({"localhost"}, 1234);
EXPECT_TRUE(conRes.has_value());
});
std::thread serverThread([&server]() {
auto accRes = server.accept();
EXPECT_TRUE(accRes.has_value());
});
serverThread.join();
clientThread.join();
}
TEST(BlockingTcpServer, RestartListening) {
tcp::BlockingServer server;
tcp::BlockingClient client;
auto lisRes = server.start_listening(1234);
std::thread clientThread([&client]() {
auto conRes = client.connect({"localhost"}, 1234);
EXPECT_TRUE(conRes.has_value());
});
clientThread.join();
auto lisRes2 = server.start_listening(2345);
std::thread clientThread2([&client]() {
auto conRes = client.connect({"localhost"}, 1234);
EXPECT_FALSE(conRes.has_value());
});
clientThread2.join();
std::thread clientThread3([&client]() {
auto conRes2 = client.connect({"localhost"}, 2345);
EXPECT_TRUE(conRes2.has_value());
});
clientThread3.join();
}
TEST(BlockingTcpServer, StopListening) {
tcp::BlockingServer server;
tcp::BlockingClient client;
auto lisRes = server.start_listening(1234);
std::thread clientThread([&client]() {
auto conRes = client.connect({"localhost"}, 1234);
EXPECT_TRUE(conRes.has_value());
});
clientThread.join();
server.stop_listening();
std::thread clientThread2([&client]() {
auto conRes2 = client.connect({"localhost"}, 1234);
EXPECT_FALSE(conRes2.has_value());
});
clientThread2.join();
}
TEST(BlockingTcpServer, MultipleAccept) {
tcp::BlockingServer server;
tcp::BlockingClient client1;
tcp::BlockingClient client2;
auto lisRes1 = server.start_listening(1234);
std::thread clientThread1([&client1]() {
auto conRes1 = client1.connect({"localhost"}, 1234);
EXPECT_TRUE(conRes1.has_value());
});
std::thread clientThread2([&client2]() {
auto conRes2 = client2.connect({"localhost"}, 1234);
EXPECT_TRUE(conRes2.has_value());
});
std::thread serverThread([&server]() {
auto accRes1 = server.accept();
EXPECT_TRUE(accRes1.has_value());
auto accRes2 = server.accept();
EXPECT_TRUE(accRes2.has_value());
});
clientThread1.join();
clientThread2.join();
serverThread.join();
}
TEST(BlockingTcpServer, GetClientIPAsString) {
tcp::BlockingServer server;
tcp::BlockingClient client;
auto lisRes = server.start_listening(1234);
auto conRes = client.connect({"localhost"}, 1234);
auto accRes = server.accept();
auto ipRes = server.get_client_ip_as_string();
if (!ipRes) return;
EXPECT_STREQ(ipRes.value().data(), "127.0.0.1");
}
TEST(BlockingTcpClient, Connect) {
tcp::BlockingServer server;
tcp::BlockingClient client;
auto lisRes = server.start_listening(1234);
std::thread clientThread([&client]() {
auto conRes = client.connect({"localhost"}, 1234);
EXPECT_TRUE(conRes.has_value());
});
clientThread.join();
}
TEST(BlockingClient, Reconnect) {
tcp::BlockingServer server1;
tcp::BlockingServer server2;
tcp::BlockingClient client;
auto lisRes1 = server1.start_listening(1234);
auto lisRes2 = server2.start_listening(2345);
std::thread clientThread([&client]() {
auto conRes = client.connect({"localhost"}, 1234);
EXPECT_TRUE(conRes.has_value());
});
clientThread.join();
std::thread clientThread2([&client]() {
auto conRes2 = client.connect({"localhost"}, 2345);
EXPECT_TRUE(conRes2.has_value());
});
clientThread2.join();
}
TEST(BlockingTcpClientServer, ClientSendServerReceive) {
tcp::BlockingServer server;
tcp::BlockingClient client;
auto lisRes = server.start_listening(1234);
auto conRes = client.connect({"localhost"}, 1234);
auto accRes = server.accept();
std::array<char, 14> txMsg = {"Hello, World!"};
auto sendRes = client.send(std::as_bytes(std::span(txMsg)));
EXPECT_EQ(sendRes.value(), txMsg.size());
std::array<char, 1024> rxBuffer;
auto recvRes = server.recv(std::as_writable_bytes(std::span(rxBuffer)));
EXPECT_EQ(recvRes.value(), txMsg.size());
EXPECT_EQ(std::strcmp(txMsg.data(), rxBuffer.data()), 0);
}
TEST(BlockingTcpClientServer, ClientReceiveServerSend) {
tcp::BlockingServer server;
tcp::BlockingClient client;
auto lisRes = server.start_listening(1234);
auto conRes = client.connect({"localhost"}, 1234);
auto accRes = server.accept();
std::array<char, 14> txMsg = {"Hello, World!"};
auto sendRes = server.send(std::as_bytes(std::span(txMsg)));
EXPECT_EQ(sendRes.value(), txMsg.size());
std::array<char, 1024> rxBuffer;
auto recvRes = client.recv(std::as_writable_bytes(std::span(rxBuffer)));
EXPECT_EQ(recvRes.value(), txMsg.size());
EXPECT_EQ(std::strcmp(txMsg.data(), rxBuffer.data()), 0);
}

View File

@ -4,10 +4,11 @@
#include <cstring>
// Project includes
#include "tcp.hpp"
#include "tcp_client.hpp"
#include "tcp_server.hpp"
TEST(TcpServer, Accept) {
TEST(NonBlockingTcpServer, Accept) {
tcp::NonBlockingServer server;
tcp::NonBlockingClient client;
@ -24,7 +25,7 @@ TEST(TcpServer, Accept) {
EXPECT_FALSE(server.next_client_available());
}
TEST(TcpServer, RestartListening) {
TEST(NonBlockingTcpServer, RestartListening) {
tcp::NonBlockingServer server;
tcp::NonBlockingClient client;
@ -47,7 +48,7 @@ TEST(TcpServer, RestartListening) {
EXPECT_EQ(statRes2.value(), EISCONN);
}
TEST(TcpServer, StopListening) {
TEST(NonBlockingTcpServer, StopListening) {
tcp::NonBlockingServer server;
tcp::NonBlockingClient client;
@ -66,7 +67,7 @@ TEST(TcpServer, StopListening) {
EXPECT_EQ(client.get_last_connection_status().value(), ECONNREFUSED);
}
TEST(TcpServer, DataAvailable) {
TEST(NonBlockingTcpServer, DataAvailable) {
tcp::NonBlockingServer server;
tcp::NonBlockingClient client;
@ -89,7 +90,7 @@ TEST(TcpServer, DataAvailable) {
EXPECT_FALSE(server.data_available());
}
TEST(TcpServer, GetClientIPAsString) {
TEST(NonBlockingTcpServer, GetClientIPAsString) {
tcp::NonBlockingServer server;
tcp::NonBlockingClient client;
@ -102,7 +103,7 @@ TEST(TcpServer, GetClientIPAsString) {
EXPECT_STREQ(ipRes.value().data(), "127.0.0.1");
}
TEST(TcpClient, Connect) {
TEST(NonBlockingTcpClient, Connect) {
tcp::NonBlockingServer server;
tcp::NonBlockingClient client;
@ -139,7 +140,7 @@ TEST(TcpClient, Connect) {
EXPECT_EQ(statRes4.value(), ECONNREFUSED);
}
TEST(TcpClient, Reconnect) {
TEST(NonBlockingTcpClient, Reconnect) {
tcp::NonBlockingServer server1;
tcp::NonBlockingServer server2;
tcp::NonBlockingClient client;
@ -160,7 +161,7 @@ TEST(TcpClient, Reconnect) {
EXPECT_EQ(statRes2.value(), EISCONN);
}
TEST(TcpClient, DataAvailable) {
TEST(NonBlockingTcpClient, DataAvailable) {
tcp::NonBlockingServer server;
tcp::NonBlockingClient client;
@ -186,7 +187,7 @@ TEST(TcpClient, DataAvailable) {
EXPECT_FALSE(client.data_available());
}
TEST(TcpClientServer, ClientSendServerReceive) {
TEST(NonBlockingTcpClientServer, ClientSendServerReceive) {
tcp::NonBlockingServer server;
tcp::NonBlockingClient client;
@ -206,7 +207,7 @@ TEST(TcpClientServer, ClientSendServerReceive) {
EXPECT_EQ(std::strcmp(txMsg.data(), rxBuffer.data()), 0);
}
TEST(TcpClientServer, ClientReceiveServerSend) {
TEST(NonBlockingTcpClientServer, ClientReceiveServerSend) {
tcp::NonBlockingServer server;
tcp::NonBlockingClient client;