Split tcp.hpp and tcp.cpp into separate files for server and client
This commit is contained in:
parent
da6159d706
commit
2d8cfb5123
@ -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)
|
||||
|
||||
|
||||
@ -56,21 +56,13 @@ target_link_libraries(playground PRIVATE spdlog::spdlog)
|
||||
|
||||
enable_testing()
|
||||
|
||||
add_executable(
|
||||
tcp_test
|
||||
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
|
||||
src/tcp_server.cpp
|
||||
src/tcp_client.cpp
|
||||
)
|
||||
target_link_libraries(tcp_test spdlog::spdlog GTest::gtest_main)
|
||||
target_include_directories(tcp_test PRIVATE src)
|
||||
|
||||
include(GoogleTest)
|
||||
gtest_discover_tests(tcp_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
|
||||
@ -2,7 +2,7 @@
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
// Project includes
|
||||
#include "tcp.hpp"
|
||||
#include "tcp_server.hpp"
|
||||
|
||||
|
||||
int main() {
|
||||
|
||||
449
src/tcp.cpp
449
src/tcp.cpp
@ -1,449 +0,0 @@
|
||||
#include "tcp.hpp"
|
||||
|
||||
// C stdlib includes
|
||||
#include <arpa/inet.h>
|
||||
#include <fcntl.h>
|
||||
#include <netdb.h>
|
||||
|
||||
// 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
|
||||
|
||||
|
||||
namespace tcp {
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
// Non blocking server
|
||||
//
|
||||
//
|
||||
|
||||
|
||||
NonBlockingServer::~NonBlockingServer() {
|
||||
close_client_socket();
|
||||
close_server_socket();
|
||||
}
|
||||
|
||||
///
|
||||
/// @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
|
||||
|
||||
close_server_socket();
|
||||
if (auto res = create_server_socket(); !res) {
|
||||
return std::unexpected{res.error()};
|
||||
}
|
||||
|
||||
/// Bin 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::NonBlockingServer: bind() failed with errno={}",
|
||||
errno);
|
||||
close_server_socket();
|
||||
return std::unexpected{errno};
|
||||
};
|
||||
|
||||
if (::listen(m_serverSocket, 1) != 0) {
|
||||
spdlog::error("tcp::NonBlockingServer: listen() failed with errno={}",
|
||||
errno);
|
||||
close_server_socket();
|
||||
return std::unexpected{errno};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void NonBlockingServer::stop_listening() {
|
||||
close_server_socket();
|
||||
}
|
||||
|
||||
bool NonBlockingServer::next_client_available() {
|
||||
int ret = poll(&m_serverPfdIn, 1, 0);
|
||||
if (ret < 0) {
|
||||
spdlog::error("tcp::NonBlockingServer: poll() failed with errno={}",
|
||||
errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
return (m_serverPfdIn.revents & POLLIN);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::expected<void, int> NonBlockingServer::accept_next_client() {
|
||||
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;
|
||||
m_clientPfdIn.fd = m_clientSocket;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void NonBlockingServer::disconnect_from_client() {
|
||||
close_client_socket();
|
||||
}
|
||||
|
||||
[[nodiscard]] std::expected<std::array<char, 16>, int>
|
||||
NonBlockingServer::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::NonBlockingServer: 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;
|
||||
}
|
||||
|
||||
bool NonBlockingServer::data_available() {
|
||||
int ret = poll(&m_clientPfdIn, 1, 0);
|
||||
if (ret < 0) {
|
||||
spdlog::error("tcp::NonBlockingServer: poll() failed with errno={}",
|
||||
errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
return (m_clientPfdIn.revents & POLLIN);
|
||||
}
|
||||
|
||||
[[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);
|
||||
if (m_serverSocket < 1) {
|
||||
spdlog::error("tcp::NonBlockingServer: socket() failed with errno={}",
|
||||
errno);
|
||||
close_server_socket();
|
||||
return std::unexpected{errno};
|
||||
}
|
||||
|
||||
m_serverPfdIn.fd = m_serverSocket;
|
||||
|
||||
auto currentFlags = fcntl(m_serverSocket, F_GETFL, 0);
|
||||
if (currentFlags == -1) {
|
||||
spdlog::error("tcp::NonBlockingServer: fcntl() failed with errno={}",
|
||||
errno);
|
||||
close_server_socket();
|
||||
return std::unexpected{errno};
|
||||
}
|
||||
|
||||
if (fcntl(m_serverSocket, F_SETFL, (currentFlags) | O_NONBLOCK) == -1) {
|
||||
spdlog::error("tcp::NonBlockingServer: fcntl() 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::NonBlockingServer: setsockopt() failed with errno={}", errno);
|
||||
close_server_socket();
|
||||
return std::unexpected{errno};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void NonBlockingServer::close_server_socket() {
|
||||
if (shutdown(m_serverSocket, 0) < 0) {
|
||||
spdlog::debug("tcp::NonBlockingServer: shutdown() failed with errno={}",
|
||||
errno);
|
||||
}
|
||||
|
||||
if (close(m_serverSocket) < 0) {
|
||||
spdlog::debug("tcp::NonBlockingServer: close() failed with errno={}",
|
||||
errno);
|
||||
}
|
||||
|
||||
m_serverSocket = -1;
|
||||
m_serverPfdIn.fd = -1;
|
||||
}
|
||||
|
||||
void NonBlockingServer::close_client_socket() {
|
||||
if (shutdown(m_clientSocket, 0) < 0) {
|
||||
spdlog::debug("tcp::NonBlockingServer: shutdown() failed with errno={}",
|
||||
errno);
|
||||
}
|
||||
|
||||
if (close(m_clientSocket) < 0) {
|
||||
spdlog::debug("tcp::NonBlockingServer: close() failed with errno={}",
|
||||
errno);
|
||||
}
|
||||
|
||||
m_clientSocket = -1;
|
||||
m_clientPfdIn.fd = -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
|
||||
209
src/tcp_client.cpp
Normal file
209
src/tcp_client.cpp
Normal file
@ -0,0 +1,209 @@
|
||||
#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 {
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
// 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
|
||||
64
src/tcp_client.hpp
Normal file
64
src/tcp_client.hpp
Normal file
@ -0,0 +1,64 @@
|
||||
#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 {
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
// 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
|
||||
222
src/tcp_server.cpp
Normal file
222
src/tcp_server.cpp
Normal file
@ -0,0 +1,222 @@
|
||||
#include "tcp_server.hpp"
|
||||
|
||||
// C stdlib includes
|
||||
#include <arpa/inet.h>
|
||||
#include <fcntl.h>
|
||||
#include <netdb.h>
|
||||
|
||||
// Library includes
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
|
||||
namespace tcp {
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
// Non blocking server
|
||||
//
|
||||
//
|
||||
|
||||
|
||||
NonBlockingServer::~NonBlockingServer() {
|
||||
close_client_socket();
|
||||
close_server_socket();
|
||||
}
|
||||
|
||||
///
|
||||
/// @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
|
||||
|
||||
close_server_socket();
|
||||
if (auto res = create_server_socket(); !res) {
|
||||
return std::unexpected{res.error()};
|
||||
}
|
||||
|
||||
/// Bin 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::NonBlockingServer: bind() failed with errno={}",
|
||||
errno);
|
||||
close_server_socket();
|
||||
return std::unexpected{errno};
|
||||
};
|
||||
|
||||
if (::listen(m_serverSocket, 1) != 0) {
|
||||
spdlog::error("tcp::NonBlockingServer: listen() failed with errno={}",
|
||||
errno);
|
||||
close_server_socket();
|
||||
return std::unexpected{errno};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void NonBlockingServer::stop_listening() {
|
||||
close_server_socket();
|
||||
}
|
||||
|
||||
bool NonBlockingServer::next_client_available() {
|
||||
int ret = poll(&m_serverPfdIn, 1, 0);
|
||||
if (ret < 0) {
|
||||
spdlog::error("tcp::NonBlockingServer: poll() failed with errno={}",
|
||||
errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
return (m_serverPfdIn.revents & POLLIN);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::expected<void, int> NonBlockingServer::accept_next_client() {
|
||||
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;
|
||||
m_clientPfdIn.fd = m_clientSocket;
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void NonBlockingServer::disconnect_from_client() {
|
||||
close_client_socket();
|
||||
}
|
||||
|
||||
[[nodiscard]] std::expected<std::array<char, 16>, int>
|
||||
NonBlockingServer::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::NonBlockingServer: 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;
|
||||
}
|
||||
|
||||
bool NonBlockingServer::data_available() {
|
||||
int ret = poll(&m_clientPfdIn, 1, 0);
|
||||
if (ret < 0) {
|
||||
spdlog::error("tcp::NonBlockingServer: poll() failed with errno={}",
|
||||
errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
return (m_clientPfdIn.revents & POLLIN);
|
||||
}
|
||||
|
||||
[[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);
|
||||
if (m_serverSocket < 1) {
|
||||
spdlog::error("tcp::NonBlockingServer: socket() failed with errno={}",
|
||||
errno);
|
||||
close_server_socket();
|
||||
return std::unexpected{errno};
|
||||
}
|
||||
|
||||
m_serverPfdIn.fd = m_serverSocket;
|
||||
|
||||
auto currentFlags = fcntl(m_serverSocket, F_GETFL, 0);
|
||||
if (currentFlags == -1) {
|
||||
spdlog::error("tcp::NonBlockingServer: fcntl() failed with errno={}",
|
||||
errno);
|
||||
close_server_socket();
|
||||
return std::unexpected{errno};
|
||||
}
|
||||
|
||||
if (fcntl(m_serverSocket, F_SETFL, (currentFlags) | O_NONBLOCK) == -1) {
|
||||
spdlog::error("tcp::NonBlockingServer: fcntl() 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::NonBlockingServer: setsockopt() failed with errno={}", errno);
|
||||
close_server_socket();
|
||||
return std::unexpected{errno};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void NonBlockingServer::close_server_socket() {
|
||||
if (shutdown(m_serverSocket, 0) < 0) {
|
||||
spdlog::debug("tcp::NonBlockingServer: shutdown() failed with errno={}",
|
||||
errno);
|
||||
}
|
||||
|
||||
if (close(m_serverSocket) < 0) {
|
||||
spdlog::debug("tcp::NonBlockingServer: close() failed with errno={}",
|
||||
errno);
|
||||
}
|
||||
|
||||
m_serverSocket = -1;
|
||||
m_serverPfdIn.fd = -1;
|
||||
}
|
||||
|
||||
void NonBlockingServer::close_client_socket() {
|
||||
if (shutdown(m_clientSocket, 0) < 0) {
|
||||
spdlog::debug("tcp::NonBlockingServer: shutdown() failed with errno={}",
|
||||
errno);
|
||||
}
|
||||
|
||||
if (close(m_clientSocket) < 0) {
|
||||
spdlog::debug("tcp::NonBlockingServer: close() failed with errno={}",
|
||||
errno);
|
||||
}
|
||||
|
||||
m_clientSocket = -1;
|
||||
m_clientPfdIn.fd = -1;
|
||||
}
|
||||
|
||||
|
||||
} // namespace tcp
|
||||
@ -2,7 +2,6 @@
|
||||
|
||||
|
||||
// STL includes
|
||||
#include <array>
|
||||
#include <cerrno>
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
@ -12,13 +11,13 @@
|
||||
#include <netinet/in.h>
|
||||
#include <sys/poll.h>
|
||||
|
||||
// Project includes
|
||||
#include "common.hpp"
|
||||
|
||||
|
||||
namespace tcp {
|
||||
|
||||
|
||||
using HostString = std::array<char, 64>;
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
// Non blocking server
|
||||
@ -65,47 +64,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
|
||||
@ -4,7 +4,8 @@
|
||||
#include <cstring>
|
||||
|
||||
// Project includes
|
||||
#include "tcp.hpp"
|
||||
#include "tcp_server.hpp"
|
||||
#include "tcp_client.hpp"
|
||||
|
||||
|
||||
TEST(TcpServer, Accept) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user