Compare commits
12 Commits
1847a0ea0c
...
v1.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 6c693d5aa0 | |||
| 2d8cfb5123 | |||
| da6159d706 | |||
| 4766d4755a | |||
| 440837cdbc | |||
| b22150de94 | |||
| 4ffd828655 | |||
| 1c91b7a1d7 | |||
| 9582dbdc88 | |||
| 47a2135093 | |||
| 9aee820625 | |||
| da346c256e |
@@ -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)
|
target_link_libraries(playground PRIVATE spdlog::spdlog)
|
||||||
|
|
||||||
|
|
||||||
@@ -54,25 +54,28 @@ target_link_libraries(playground PRIVATE spdlog::spdlog)
|
|||||||
#
|
#
|
||||||
|
|
||||||
|
|
||||||
|
include(GoogleTest)
|
||||||
|
|
||||||
|
|
||||||
enable_testing()
|
enable_testing()
|
||||||
|
|
||||||
add_executable(
|
add_executable(tcp_blocking_test
|
||||||
tcp_test
|
tests/tcp_blocking.cpp
|
||||||
tests/tcp.cpp
|
src/tcp_server.cpp
|
||||||
src/tcp.cpp
|
src/tcp_client.cpp
|
||||||
)
|
|
||||||
target_link_libraries(
|
|
||||||
tcp_test
|
|
||||||
spdlog::spdlog
|
|
||||||
GTest::gtest_main
|
|
||||||
)
|
|
||||||
target_include_directories(
|
|
||||||
tcp_test
|
|
||||||
PRIVATE
|
|
||||||
src
|
|
||||||
)
|
)
|
||||||
|
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_blocking_test)
|
||||||
gtest_discover_tests(tcp_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)
|
||||||
|
|
||||||
|
|||||||
48
src/common.hpp
Normal file
48
src/common.hpp
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
|
// STL includes
|
||||||
|
#include <expected>
|
||||||
|
|
||||||
|
// C stdlib includes
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
|
||||||
|
// Library includes
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
|
||||||
|
namespace tcp {
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
using HostString = std::array<char, 64>;
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace tcp
|
||||||
13
src/main.cpp
13
src/main.cpp
@@ -1,12 +1,15 @@
|
|||||||
#include "tcp.hpp"
|
// Library includes
|
||||||
#include <cstddef>
|
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
// Project includes
|
||||||
|
#include "tcp_server.hpp"
|
||||||
|
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
spdlog::set_level(spdlog::level::debug);
|
spdlog::set_level(spdlog::level::debug);
|
||||||
|
|
||||||
tcp::NonBlockingServer server{{.port = 65432}};
|
tcp::NonBlockingServer server{};
|
||||||
if (auto res = server.start_listening(); !res) {
|
if (auto res = server.start_listening(65432); !res) {
|
||||||
spdlog::error("Failed to start server: {}", res.error());
|
spdlog::error("Failed to start server: {}", res.error());
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -47,6 +50,6 @@ int main() {
|
|||||||
spdlog::info("Sent {} bytes", sendRes.value());
|
spdlog::info("Sent {} bytes", sendRes.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
server.disconnect();
|
server.disconnect_from_client();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
120
src/tcp.hpp
120
src/tcp.hpp
@@ -1,120 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <cerrno>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <expected>
|
|
||||||
#include <poll.h>
|
|
||||||
#include <span>
|
|
||||||
#include <spdlog/spdlog.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
|
|
||||||
// C stdlib includes
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
#include <expected>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#include <netdb.h>
|
|
||||||
#include <netinet/in.h>
|
|
||||||
#include <netinet/tcp.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
|
|
||||||
|
|
||||||
namespace tcp {
|
|
||||||
|
|
||||||
|
|
||||||
using HostString = std::array<char, 64>;
|
|
||||||
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Non blocking server
|
|
||||||
//
|
|
||||||
//
|
|
||||||
|
|
||||||
struct ServerSettings {
|
|
||||||
uint16_t port = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class NonBlockingServer {
|
|
||||||
public:
|
|
||||||
NonBlockingServer(ServerSettings settings);
|
|
||||||
~NonBlockingServer();
|
|
||||||
|
|
||||||
[[nodiscard]] std::expected<void, int> start_listening();
|
|
||||||
|
|
||||||
bool next_client_available();
|
|
||||||
[[nodiscard]] std::expected<void, int> accept_next_client();
|
|
||||||
void disconnect();
|
|
||||||
|
|
||||||
[[nodiscard]] std::expected<int, int>
|
|
||||||
send(std::span<const std::byte> data) const;
|
|
||||||
|
|
||||||
[[nodiscard]] std::expected<int, int>
|
|
||||||
recv(std::span<std::byte> buffer) const;
|
|
||||||
|
|
||||||
bool data_available();
|
|
||||||
|
|
||||||
[[nodiscard]] std::expected<std::array<char, 16>, int>
|
|
||||||
get_client_ip_as_string() const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
ServerSettings m_settings;
|
|
||||||
const struct sockaddr_in m_serverAddress;
|
|
||||||
int m_serverSocket = -1;
|
|
||||||
|
|
||||||
pollfd m_serverPfdIn = {.fd = -1, .events = POLLIN};
|
|
||||||
pollfd m_clientPfdIn = {.fd = -1, .events = POLLIN};
|
|
||||||
|
|
||||||
int m_clientSocket = -1;
|
|
||||||
struct sockaddr m_clientAddress;
|
|
||||||
socklen_t m_clientAddrLen = sizeof(m_clientAddress);
|
|
||||||
|
|
||||||
|
|
||||||
[[nodiscard]] std::expected<void, int> create_socket();
|
|
||||||
void close_server_socket();
|
|
||||||
void close_client_socket();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Non blocking client
|
|
||||||
//
|
|
||||||
//
|
|
||||||
|
|
||||||
|
|
||||||
class NonBlockingClient {
|
|
||||||
public:
|
|
||||||
~NonBlockingClient();
|
|
||||||
|
|
||||||
// clang-format off
|
|
||||||
[[nodiscard]] std::expected<void, int>
|
|
||||||
connect(HostString host, uint16_t port);
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
void disconnect();
|
|
||||||
|
|
||||||
[[nodiscard]] std::expected<int, int> get_last_connection_status();
|
|
||||||
|
|
||||||
bool data_available();
|
|
||||||
|
|
||||||
[[nodiscard]] std::expected<int, int> recv(std::span<std::byte> buffer);
|
|
||||||
[[nodiscard]] std::expected<int, int> send(std::span<const std::byte> data);
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
void close_socket();
|
|
||||||
|
|
||||||
[[nodiscard]] std::expected<void, int> create_socket();
|
|
||||||
[[nodiscard]] std::expected<bool, int> new_error_codes_available();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace tcp
|
|
||||||
300
src/tcp_client.cpp
Normal file
300
src/tcp_client.cpp
Normal 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 = 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 = 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
95
src/tcp_client.hpp
Normal 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
|
||||||
@@ -1,57 +1,184 @@
|
|||||||
#include "tcp.hpp"
|
#include "tcp_server.hpp"
|
||||||
|
|
||||||
|
// C stdlib includes
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
#include <cerrno>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <expected>
|
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <netdb.h>
|
#include <netdb.h>
|
||||||
#include <netinet/in.h>
|
|
||||||
#include <netinet/tcp.h>
|
// Library includes
|
||||||
#include <poll.h>
|
|
||||||
#include <span>
|
|
||||||
#include <spdlog/spdlog.h>
|
#include <spdlog/spdlog.h>
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/types.h>
|
|
||||||
|
|
||||||
|
// Project includes
|
||||||
namespace {
|
#include "common.hpp"
|
||||||
|
|
||||||
|
|
||||||
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 auto get_local_address(uint16_t port) -> sockaddr_in {
|
|
||||||
sockaddr_in address{};
|
|
||||||
address.sin_addr.s_addr = htonl(INADDR_ANY);
|
|
||||||
address.sin_family = AF_INET;
|
|
||||||
address.sin_port = htons(port);
|
|
||||||
return address;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
|
|
||||||
namespace tcp {
|
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 = 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
|
// Non blocking server
|
||||||
@@ -59,43 +186,49 @@ namespace tcp {
|
|||||||
//
|
//
|
||||||
|
|
||||||
|
|
||||||
NonBlockingServer::NonBlockingServer(ServerSettings settings)
|
|
||||||
: m_settings{settings}, m_serverAddress(get_local_address(settings.port)) {
|
|
||||||
}
|
|
||||||
|
|
||||||
NonBlockingServer::~NonBlockingServer() {
|
NonBlockingServer::~NonBlockingServer() {
|
||||||
close_client_socket();
|
close_client_socket();
|
||||||
close_server_socket();
|
close_server_socket();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::expected<void, int> NonBlockingServer::start_listening() {
|
///
|
||||||
|
/// @details Calling this repeatedly closes the previous socket and creates a
|
||||||
|
/// new one.
|
||||||
|
///
|
||||||
|
std::expected<void, int> NonBlockingServer::start_listening(uint16_t port) {
|
||||||
/// Create socket
|
/// Create socket
|
||||||
|
|
||||||
close_server_socket();
|
close_server_socket();
|
||||||
if (auto res = create_socket(); !res) {
|
if (auto res = create_server_socket(); !res) {
|
||||||
return std::unexpected{res.error()};
|
return std::unexpected{res.error()};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Bin socket and start listening
|
/// Bind socket and start listening
|
||||||
|
|
||||||
if (::bind(m_serverSocket, (struct sockaddr*)&m_serverAddress,
|
const struct sockaddr_in serverAddress = get_local_address(port);
|
||||||
sizeof(m_serverAddress)) != 0) {
|
|
||||||
|
if (::bind(m_serverSocket, (struct sockaddr*)&serverAddress,
|
||||||
|
sizeof(serverAddress)) != 0) {
|
||||||
spdlog::error("tcp::NonBlockingServer: bind() failed with errno={}",
|
spdlog::error("tcp::NonBlockingServer: bind() failed with errno={}",
|
||||||
errno);
|
errno);
|
||||||
close(m_serverSocket);
|
close_server_socket();
|
||||||
return std::unexpected{errno};
|
return std::unexpected{errno};
|
||||||
};
|
};
|
||||||
|
|
||||||
if (::listen(m_serverSocket, 1) != 0) {
|
if (::listen(m_serverSocket, 1) != 0) {
|
||||||
spdlog::error("tcp::NonBlockingServer: listen() failed with errno={}",
|
spdlog::error("tcp::NonBlockingServer: listen() failed with errno={}",
|
||||||
errno);
|
errno);
|
||||||
close(m_serverSocket);
|
close_server_socket();
|
||||||
return std::unexpected{errno};
|
return std::unexpected{errno};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NonBlockingServer::stop_listening() {
|
||||||
|
close_server_socket();
|
||||||
|
}
|
||||||
|
|
||||||
bool NonBlockingServer::next_client_available() {
|
bool NonBlockingServer::next_client_available() {
|
||||||
int ret = poll(&m_serverPfdIn, 1, 0);
|
int ret = poll(&m_serverPfdIn, 1, 0);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
@@ -108,10 +241,12 @@ bool NonBlockingServer::next_client_available() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] std::expected<void, int> NonBlockingServer::accept_next_client() {
|
[[nodiscard]] std::expected<void, int> NonBlockingServer::accept_next_client() {
|
||||||
disconnect();
|
disconnect_from_client();
|
||||||
|
|
||||||
|
socklen_t clientAddrLen = sizeof(m_clientAddress);
|
||||||
|
|
||||||
int result = ::accept(m_serverSocket, (struct sockaddr*)&m_clientAddress,
|
int result = ::accept(m_serverSocket, (struct sockaddr*)&m_clientAddress,
|
||||||
&m_clientAddrLen);
|
&clientAddrLen);
|
||||||
if (result < 0) return std::unexpected{errno};
|
if (result < 0) return std::unexpected{errno};
|
||||||
|
|
||||||
m_clientSocket = result;
|
m_clientSocket = result;
|
||||||
@@ -119,16 +254,13 @@ bool NonBlockingServer::next_client_available() {
|
|||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
void NonBlockingServer::disconnect() {
|
|
||||||
|
void NonBlockingServer::disconnect_from_client() {
|
||||||
close_client_socket();
|
close_client_socket();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Do we need this?
|
|
||||||
// bool NonBlockingServer::socket_closed() const {
|
|
||||||
// }
|
|
||||||
|
|
||||||
[[nodiscard]] std::expected<std::array<char, 16>, int>
|
[[nodiscard]] std::expected<std::array<char, 16>, int>
|
||||||
NonBlockingServer::get_client_ip_as_string() const {
|
NonBlockingServer::get_client_ip_as_string() {
|
||||||
std::array<char, 16> addrStr;
|
std::array<char, 16> addrStr;
|
||||||
|
|
||||||
if (inet_ntop(AF_INET, &((struct sockaddr_in*)&m_clientAddress)->sin_addr,
|
if (inet_ntop(AF_INET, &((struct sockaddr_in*)&m_clientAddress)->sin_addr,
|
||||||
@@ -143,34 +275,7 @@ NonBlockingServer::get_client_ip_as_string() const {
|
|||||||
// return std::unexpected{errno};
|
// return std::unexpected{errno};
|
||||||
// }
|
// }
|
||||||
|
|
||||||
return {addrStr};
|
return addrStr;
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] std::expected<int, int>
|
|
||||||
NonBlockingServer::send(std::span<const std::byte> data) const {
|
|
||||||
const int bytesWritten =
|
|
||||||
::send(m_clientSocket, (const char*)data.data(), data.size(), 0);
|
|
||||||
|
|
||||||
if (bytesWritten <= 0) {
|
|
||||||
spdlog::error("tcp::NonBlockingServer: send() failed with errno={}",
|
|
||||||
errno);
|
|
||||||
return std::unexpected{errno};
|
|
||||||
}
|
|
||||||
|
|
||||||
return bytesWritten;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] std::expected<int, int>
|
|
||||||
NonBlockingServer::recv(std::span<std::byte> buffer) const {
|
|
||||||
const int bytesReceived =
|
|
||||||
::recv(m_clientSocket, (char*)buffer.data(), buffer.size(), 0);
|
|
||||||
|
|
||||||
if (bytesReceived < 0) {
|
|
||||||
spdlog::error("tcp::NonBlockingServer: recv() failed with errno={}",
|
|
||||||
errno);
|
|
||||||
return std::unexpected{errno};
|
|
||||||
}
|
|
||||||
return {bytesReceived};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NonBlockingServer::data_available() {
|
bool NonBlockingServer::data_available() {
|
||||||
@@ -184,11 +289,40 @@ bool NonBlockingServer::data_available() {
|
|||||||
return (m_clientPfdIn.revents & POLLIN);
|
return (m_clientPfdIn.revents & POLLIN);
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] std::expected<void, int> NonBlockingServer::create_socket() {
|
[[nodiscard]] std::expected<int, int>
|
||||||
|
NonBlockingServer::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::NonBlockingServer: send() failed with errno={}",
|
||||||
|
errno);
|
||||||
|
return std::unexpected{errno};
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytesWritten;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::expected<int, int>
|
||||||
|
NonBlockingServer::recv(std::span<std::byte> buffer) {
|
||||||
|
const int bytesReceived =
|
||||||
|
::recv(m_clientSocket, (char*)buffer.data(), buffer.size(), 0);
|
||||||
|
|
||||||
|
if (bytesReceived < 0) {
|
||||||
|
spdlog::error("tcp::NonBlockingServer: recv() failed with errno={}",
|
||||||
|
errno);
|
||||||
|
return std::unexpected{errno};
|
||||||
|
}
|
||||||
|
return {bytesReceived};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::expected<void, int>
|
||||||
|
NonBlockingServer::create_server_socket() {
|
||||||
m_serverSocket = socket(AF_INET, SOCK_STREAM, 0);
|
m_serverSocket = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
if (m_serverSocket < 1) {
|
if (m_serverSocket < 1) {
|
||||||
spdlog::error("tcp::NonBlockingServer: socket() failed with errno={}",
|
spdlog::error("tcp::NonBlockingServer: socket() failed with errno={}",
|
||||||
errno);
|
errno);
|
||||||
|
close_server_socket();
|
||||||
return std::unexpected{errno};
|
return std::unexpected{errno};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,191 +386,4 @@ void NonBlockingServer::close_client_socket() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Non blocking client
|
|
||||||
//
|
|
||||||
//
|
|
||||||
|
|
||||||
|
|
||||||
NonBlockingClient::~NonBlockingClient() {
|
|
||||||
close_socket();
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] std::expected<void, int>
|
|
||||||
NonBlockingClient::connect(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 {};
|
|
||||||
}
|
|
||||||
|
|
||||||
[[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 = new_error_codes_available();
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NonBlockingClient::disconnect() {
|
|
||||||
m_lastKnownConStatus = ENOTCONN;
|
|
||||||
close_socket();
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] std::expected<int, int>
|
|
||||||
NonBlockingClient::send(std::span<const std::byte> data) {
|
|
||||||
// TODO: Do we need this?
|
|
||||||
auto conRes = get_last_connection_status();
|
|
||||||
if (!conRes) return std::unexpected{conRes.error()};
|
|
||||||
if (conRes.value() != EISCONN) {
|
|
||||||
spdlog::error("tcp::NonBlockingClient: Socket not connected");
|
|
||||||
return std::unexpected{ENOTCONN};
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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::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};
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
[[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);
|
|
||||||
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 {};
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] std::expected<bool, int>
|
|
||||||
NonBlockingClient::new_error_codes_available() {
|
|
||||||
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
|
} // namespace tcp
|
||||||
104
src/tcp_server.hpp
Normal file
104
src/tcp_server.hpp
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
|
// STL includes
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <expected>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
// C stdlib includes
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <sys/poll.h>
|
||||||
|
|
||||||
|
|
||||||
|
namespace tcp {
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// 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();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// Non blocking server
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
|
||||||
|
class NonBlockingServer {
|
||||||
|
public:
|
||||||
|
NonBlockingServer() = default;
|
||||||
|
NonBlockingServer(const NonBlockingServer&) = delete;
|
||||||
|
NonBlockingServer(NonBlockingServer&&) = delete;
|
||||||
|
NonBlockingServer& operator=(const NonBlockingServer&) = delete;
|
||||||
|
NonBlockingServer& operator=(NonBlockingServer&&) = delete;
|
||||||
|
~NonBlockingServer();
|
||||||
|
|
||||||
|
[[nodiscard]] std::expected<void, int> start_listening(uint16_t port);
|
||||||
|
void stop_listening();
|
||||||
|
|
||||||
|
bool next_client_available();
|
||||||
|
[[nodiscard]] std::expected<void, int> accept_next_client();
|
||||||
|
void disconnect_from_client();
|
||||||
|
|
||||||
|
[[nodiscard]] std::expected<std::array<char, 16>, int>
|
||||||
|
get_client_ip_as_string();
|
||||||
|
|
||||||
|
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_serverSocket = -1;
|
||||||
|
|
||||||
|
pollfd m_serverPfdIn = {.fd = -1, .events = POLLIN};
|
||||||
|
pollfd m_clientPfdIn = {.fd = -1, .events = POLLIN};
|
||||||
|
|
||||||
|
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();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace tcp
|
||||||
202
tests/tcp_blocking.cpp
Normal file
202
tests/tcp_blocking.cpp
Normal 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);
|
||||||
|
}
|
||||||
@@ -1,13 +1,18 @@
|
|||||||
#include "tcp.hpp"
|
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
// STL includes
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
TEST(TcpServer, Accept) {
|
// Project includes
|
||||||
tcp::NonBlockingServer server{{.port = 1234}};
|
#include "tcp_client.hpp"
|
||||||
|
#include "tcp_server.hpp"
|
||||||
|
|
||||||
|
|
||||||
|
TEST(NonBlockingTcpServer, Accept) {
|
||||||
|
tcp::NonBlockingServer server;
|
||||||
tcp::NonBlockingClient client;
|
tcp::NonBlockingClient client;
|
||||||
|
|
||||||
auto lisRes = server.start_listening();
|
auto lisRes = server.start_listening(1234);
|
||||||
|
|
||||||
EXPECT_FALSE(server.next_client_available());
|
EXPECT_FALSE(server.next_client_available());
|
||||||
|
|
||||||
@@ -20,8 +25,86 @@ TEST(TcpServer, Accept) {
|
|||||||
EXPECT_FALSE(server.next_client_available());
|
EXPECT_FALSE(server.next_client_available());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(TcpClient, Connect) {
|
TEST(NonBlockingTcpServer, RestartListening) {
|
||||||
tcp::NonBlockingServer server{{.port = 1234}};
|
tcp::NonBlockingServer server;
|
||||||
|
tcp::NonBlockingClient client;
|
||||||
|
|
||||||
|
auto lisRes = server.start_listening(1234);
|
||||||
|
|
||||||
|
auto conRes = client.connect({"localhost"}, 1234);
|
||||||
|
|
||||||
|
EXPECT_TRUE(server.next_client_available());
|
||||||
|
|
||||||
|
auto lisRes2 = server.start_listening(2345);
|
||||||
|
|
||||||
|
EXPECT_FALSE(server.next_client_available());
|
||||||
|
|
||||||
|
auto conRes2 = client.connect({"localhost"}, 2345);
|
||||||
|
|
||||||
|
EXPECT_TRUE(server.next_client_available());
|
||||||
|
|
||||||
|
auto statRes2 = client.get_last_connection_status();
|
||||||
|
if (!statRes2) return;
|
||||||
|
EXPECT_EQ(statRes2.value(), EISCONN);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(NonBlockingTcpServer, StopListening) {
|
||||||
|
tcp::NonBlockingServer server;
|
||||||
|
tcp::NonBlockingClient client;
|
||||||
|
|
||||||
|
auto lisRes = server.start_listening(1234);
|
||||||
|
|
||||||
|
auto conRes = client.connect({"localhost"}, 1234);
|
||||||
|
|
||||||
|
EXPECT_TRUE(server.next_client_available());
|
||||||
|
|
||||||
|
server.stop_listening();
|
||||||
|
|
||||||
|
EXPECT_FALSE(server.next_client_available());
|
||||||
|
|
||||||
|
auto conRes2 = client.connect({"localhost"}, 1234);
|
||||||
|
|
||||||
|
EXPECT_EQ(client.get_last_connection_status().value(), ECONNREFUSED);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(NonBlockingTcpServer, DataAvailable) {
|
||||||
|
tcp::NonBlockingServer server;
|
||||||
|
tcp::NonBlockingClient client;
|
||||||
|
|
||||||
|
EXPECT_FALSE(server.data_available());
|
||||||
|
|
||||||
|
auto lisRes = server.start_listening(1234);
|
||||||
|
auto conRes = client.connect({"localhost"}, 1234);
|
||||||
|
auto accRes = server.accept_next_client();
|
||||||
|
|
||||||
|
EXPECT_FALSE(server.data_available());
|
||||||
|
|
||||||
|
std::array<char, 14> txMsg = {"Hello, World!"};
|
||||||
|
auto sendRes = client.send(std::as_bytes(std::span(txMsg)));
|
||||||
|
EXPECT_EQ(sendRes.value(), txMsg.size());
|
||||||
|
EXPECT_TRUE(server.data_available());
|
||||||
|
|
||||||
|
std::array<char, 1024> rxBuffer;
|
||||||
|
auto recvRes = server.recv(std::as_writable_bytes(std::span(rxBuffer)));
|
||||||
|
EXPECT_EQ(recvRes.value(), txMsg.size());
|
||||||
|
EXPECT_FALSE(server.data_available());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(NonBlockingTcpServer, GetClientIPAsString) {
|
||||||
|
tcp::NonBlockingServer server;
|
||||||
|
tcp::NonBlockingClient client;
|
||||||
|
|
||||||
|
auto lisRes = server.start_listening(1234);
|
||||||
|
auto conRes = client.connect({"localhost"}, 1234);
|
||||||
|
auto accRes = server.accept_next_client();
|
||||||
|
auto ipRes = server.get_client_ip_as_string();
|
||||||
|
if (!ipRes) return;
|
||||||
|
|
||||||
|
EXPECT_STREQ(ipRes.value().data(), "127.0.0.1");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(NonBlockingTcpClient, Connect) {
|
||||||
|
tcp::NonBlockingServer server;
|
||||||
tcp::NonBlockingClient client;
|
tcp::NonBlockingClient client;
|
||||||
|
|
||||||
/// Default connection status, i.e., before attempting connection
|
/// Default connection status, i.e., before attempting connection
|
||||||
@@ -40,7 +123,7 @@ TEST(TcpClient, Connect) {
|
|||||||
|
|
||||||
/// Connection status when connection is acknowledged
|
/// Connection status when connection is acknowledged
|
||||||
|
|
||||||
auto lisRes = server.start_listening();
|
auto lisRes = server.start_listening(1234);
|
||||||
|
|
||||||
auto conRes2 = client.connect({"localhost"}, 1234);
|
auto conRes2 = client.connect({"localhost"}, 1234);
|
||||||
|
|
||||||
@@ -57,13 +140,13 @@ TEST(TcpClient, Connect) {
|
|||||||
EXPECT_EQ(statRes4.value(), ECONNREFUSED);
|
EXPECT_EQ(statRes4.value(), ECONNREFUSED);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(TcpClient, Reconnect) {
|
TEST(NonBlockingTcpClient, Reconnect) {
|
||||||
tcp::NonBlockingServer server1{{.port = 1234}};
|
tcp::NonBlockingServer server1;
|
||||||
tcp::NonBlockingServer server2{{.port = 2345}};
|
tcp::NonBlockingServer server2;
|
||||||
tcp::NonBlockingClient client;
|
tcp::NonBlockingClient client;
|
||||||
|
|
||||||
auto lisRes1 = server1.start_listening();
|
auto lisRes1 = server1.start_listening(1234);
|
||||||
auto lisRes2 = server2.start_listening();
|
auto lisRes2 = server2.start_listening(2345);
|
||||||
|
|
||||||
auto conRes1 = client.connect({"localhost"}, 1234);
|
auto conRes1 = client.connect({"localhost"}, 1234);
|
||||||
|
|
||||||
@@ -78,13 +161,13 @@ TEST(TcpClient, Reconnect) {
|
|||||||
EXPECT_EQ(statRes2.value(), EISCONN);
|
EXPECT_EQ(statRes2.value(), EISCONN);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(TcpClient, DataAvailable) {
|
TEST(NonBlockingTcpClient, DataAvailable) {
|
||||||
tcp::NonBlockingServer server{{.port = 1234}};
|
tcp::NonBlockingServer server;
|
||||||
tcp::NonBlockingClient client;
|
tcp::NonBlockingClient client;
|
||||||
|
|
||||||
EXPECT_FALSE(client.data_available());
|
EXPECT_FALSE(client.data_available());
|
||||||
|
|
||||||
auto lisRes = server.start_listening();
|
auto lisRes = server.start_listening(1234);
|
||||||
auto conRes = client.connect({"localhost"}, 1234);
|
auto conRes = client.connect({"localhost"}, 1234);
|
||||||
auto accRes = server.accept_next_client();
|
auto accRes = server.accept_next_client();
|
||||||
|
|
||||||
@@ -104,34 +187,11 @@ TEST(TcpClient, DataAvailable) {
|
|||||||
EXPECT_FALSE(client.data_available());
|
EXPECT_FALSE(client.data_available());
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(TcpServer, DataAvailable) {
|
TEST(NonBlockingTcpClientServer, ClientSendServerReceive) {
|
||||||
tcp::NonBlockingServer server{{.port = 1234}};
|
tcp::NonBlockingServer server;
|
||||||
tcp::NonBlockingClient client;
|
tcp::NonBlockingClient client;
|
||||||
|
|
||||||
EXPECT_FALSE(server.data_available());
|
auto lisRes = server.start_listening(1234);
|
||||||
|
|
||||||
auto lisRes = server.start_listening();
|
|
||||||
auto conRes = client.connect({"localhost"}, 1234);
|
|
||||||
auto accRes = server.accept_next_client();
|
|
||||||
|
|
||||||
EXPECT_FALSE(server.data_available());
|
|
||||||
|
|
||||||
std::array<char, 14> txMsg = {"Hello, World!"};
|
|
||||||
auto sendRes = client.send(std::as_bytes(std::span(txMsg)));
|
|
||||||
EXPECT_EQ(sendRes.value(), txMsg.size());
|
|
||||||
EXPECT_TRUE(server.data_available());
|
|
||||||
|
|
||||||
std::array<char, 1024> rxBuffer;
|
|
||||||
auto recvRes = server.recv(std::as_writable_bytes(std::span(rxBuffer)));
|
|
||||||
EXPECT_EQ(recvRes.value(), txMsg.size());
|
|
||||||
EXPECT_FALSE(server.data_available());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(TcpClientServer, ClientSendServerReceive) {
|
|
||||||
tcp::NonBlockingServer server{{.port = 1234}};
|
|
||||||
tcp::NonBlockingClient client;
|
|
||||||
|
|
||||||
auto lisRes = server.start_listening();
|
|
||||||
auto conRes = client.connect({"localhost"}, 1234);
|
auto conRes = client.connect({"localhost"}, 1234);
|
||||||
auto accRes = server.accept_next_client();
|
auto accRes = server.accept_next_client();
|
||||||
|
|
||||||
@@ -147,11 +207,11 @@ TEST(TcpClientServer, ClientSendServerReceive) {
|
|||||||
EXPECT_EQ(std::strcmp(txMsg.data(), rxBuffer.data()), 0);
|
EXPECT_EQ(std::strcmp(txMsg.data(), rxBuffer.data()), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(TcpClientServer, ClientReceiveServerSend) {
|
TEST(NonBlockingTcpClientServer, ClientReceiveServerSend) {
|
||||||
tcp::NonBlockingServer server{{.port = 1234}};
|
tcp::NonBlockingServer server;
|
||||||
tcp::NonBlockingClient client;
|
tcp::NonBlockingClient client;
|
||||||
|
|
||||||
auto lisRes = server.start_listening();
|
auto lisRes = server.start_listening(1234);
|
||||||
auto conRes = client.connect({"localhost"}, 1234);
|
auto conRes = client.connect({"localhost"}, 1234);
|
||||||
auto accRes = server.accept_next_client();
|
auto accRes = server.accept_next_client();
|
||||||
|
|
||||||
Reference in New Issue
Block a user