Add first tcp server implementation
This commit is contained in:
commit
72121036a6
14
.clang-tidy
Normal file
14
.clang-tidy
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
Checks: "-*,readability-identifier-naming"
|
||||||
|
CheckOptions:
|
||||||
|
readability-identifier-naming.NamespaceCase: lower_case
|
||||||
|
readability-identifier-naming.EnumCase: CamelCase
|
||||||
|
readability-identifier-naming.ClassCase: CamelCase
|
||||||
|
readability-identifier-naming.StructCase: CamelCase
|
||||||
|
|
||||||
|
readability-identifier-naming.PrivateMemberCase: camelBack
|
||||||
|
readability-identifier-naming.PrivateMemberPrefix: 'm_'
|
||||||
|
|
||||||
|
readability-identifier-naming.FunctionCase: lower_case
|
||||||
|
readability-identifier-naming.VariableCase: camelBack
|
||||||
|
readability-identifier-naming.GlobalVariablePrefix: 'g_'
|
||||||
|
|
||||||
4
.clangd
Normal file
4
.clangd
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
CompileFlags:
|
||||||
|
Remove: ['-fno-shrink-wrap', '-fstrict-volatile-bitfields', '-fno-tree-switch-conversion']
|
||||||
|
Add: ['-std=c++23', '-D__cpp_concepts=202002L']
|
||||||
|
|
||||||
78
CMakeLists.txt
Normal file
78
CMakeLists.txt
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Global settings
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
project(playground)
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Dependencies
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
# spdlog
|
||||||
|
|
||||||
|
find_package(spdlog REQUIRED)
|
||||||
|
|
||||||
|
# Google Test
|
||||||
|
|
||||||
|
include(FetchContent)
|
||||||
|
FetchContent_Declare(
|
||||||
|
googletest
|
||||||
|
URL https://github.com/google/googletest/archive/03597a01ee50ed33e9dfd640b249b4be3799d395.zip
|
||||||
|
)
|
||||||
|
# For Windows: Prevent overriding the parent project's compiler/linker settings
|
||||||
|
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
||||||
|
FetchContent_MakeAvailable(googletest)
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Executabless
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
# add_executable(playground src/main.cpp src/tcp.cpp)
|
||||||
|
# target_link_libraries(playground PRIVATE spdlog::spdlog)
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# Tests
|
||||||
|
#
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
|
add_executable(
|
||||||
|
tcp_client_test
|
||||||
|
tests/tcp_client.cpp
|
||||||
|
src/tcp.cpp
|
||||||
|
)
|
||||||
|
target_link_libraries(
|
||||||
|
tcp_client_test
|
||||||
|
spdlog::spdlog
|
||||||
|
GTest::gtest_main
|
||||||
|
)
|
||||||
|
target_include_directories(
|
||||||
|
tcp_client_test
|
||||||
|
PRIVATE
|
||||||
|
src
|
||||||
|
)
|
||||||
|
|
||||||
|
include(GoogleTest)
|
||||||
|
gtest_discover_tests(tcp_client_test)
|
||||||
|
|
||||||
15
scripts/tcp_client.py
Normal file
15
scripts/tcp_client.py
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import socket
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
|
||||||
|
sock.connect(("localhost", 1234))
|
||||||
|
sock.sendall(b"asdf")
|
||||||
|
data = sock.recv(1024)
|
||||||
|
print(data.decode())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
||||||
80
scripts/tcp_server.py
Normal file
80
scripts/tcp_server.py
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import socket
|
||||||
|
import sys
|
||||||
|
import logging
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
|
||||||
|
class TcpServer:
|
||||||
|
def __init__(self, port, client_handler):
|
||||||
|
self._port = port
|
||||||
|
self._client_handler = client_handler
|
||||||
|
|
||||||
|
self._server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
self._server_socket.setsockopt(
|
||||||
|
socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
|
||||||
|
server_address = ('0.0.0.0', port)
|
||||||
|
self._server_socket.bind(server_address)
|
||||||
|
self._server_socket.listen(1)
|
||||||
|
|
||||||
|
def handle_connection(self):
|
||||||
|
try:
|
||||||
|
connection, client_address = self._server_socket.accept()
|
||||||
|
logging.info(f"Client connected: {client_address}")
|
||||||
|
self._client_handler(connection)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
|
||||||
|
def handle_client(connection):
|
||||||
|
connection.settimeout(3.0)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
connection.sendall(
|
||||||
|
b"""{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": "1",
|
||||||
|
"method": "SetLedPattern",
|
||||||
|
"params": {
|
||||||
|
"led": 19,
|
||||||
|
"pattern": 3.1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
|
||||||
|
try:
|
||||||
|
res = connection.recv(1024)
|
||||||
|
if not res:
|
||||||
|
logging.info("Client disconnected")
|
||||||
|
connection.close()
|
||||||
|
break
|
||||||
|
|
||||||
|
logging.info(f"SetLedPattern response: {res.decode()}")
|
||||||
|
except socket.timeout:
|
||||||
|
logging.error("Connection timed out")
|
||||||
|
break
|
||||||
|
|
||||||
|
sleep(1)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
logging.basicConfig(format='%(asctime)s %(levelname)s %(message)s',
|
||||||
|
datefmt='%H:%M:%S',
|
||||||
|
level=logging.DEBUG,
|
||||||
|
stream=sys.stdout)
|
||||||
|
|
||||||
|
port = 65432
|
||||||
|
|
||||||
|
server = TcpServer(port=port, client_handler=handle_client)
|
||||||
|
logging.info(f"Started TCP server on port {port}")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
server.handle_connection()
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(
|
||||||
|
f"An error occurred while handling the client connection: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
101
src/main.cpp
Normal file
101
src/main.cpp
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
#include "tcp.hpp"
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
spdlog::set_level(spdlog::level::debug);
|
||||||
|
|
||||||
|
tcp::NonBlockingClient client;
|
||||||
|
|
||||||
|
tcp::HostString host = {"192.168.0.12"};
|
||||||
|
uint16_t port = 123;
|
||||||
|
|
||||||
|
spdlog::info("Connecting to {}:{}", host.data(), port);
|
||||||
|
|
||||||
|
if (!client.connect(host, port)) std::terminate();
|
||||||
|
|
||||||
|
tcp::ConnectionStatus status = tcp::ConnectionStatus::AttemptingConnection;
|
||||||
|
while (status == tcp::ConnectionStatus::AttemptingConnection) {
|
||||||
|
auto statusRes = client.get_last_connection_status();
|
||||||
|
if (!statusRes) std::terminate();
|
||||||
|
status = statusRes.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case tcp::ConnectionStatus::Successful:
|
||||||
|
spdlog::info("Connected successfully");
|
||||||
|
break;
|
||||||
|
case tcp::ConnectionStatus::Refused:
|
||||||
|
spdlog::error("Connection refused");
|
||||||
|
std::terminate();
|
||||||
|
case tcp::ConnectionStatus::NoRouteToHost:
|
||||||
|
spdlog::error("No route to host");
|
||||||
|
std::terminate();
|
||||||
|
case tcp::ConnectionStatus::TimedOut:
|
||||||
|
spdlog::error("Connection timed out");
|
||||||
|
std::terminate();
|
||||||
|
case tcp::ConnectionStatus::HostUnreachable:
|
||||||
|
spdlog::error("Host unreachable");
|
||||||
|
std::terminate();
|
||||||
|
case tcp::ConnectionStatus::OtherError:
|
||||||
|
spdlog::error("Other error");
|
||||||
|
std::terminate();
|
||||||
|
case tcp::ConnectionStatus::AttemptingConnection:
|
||||||
|
spdlog::error("Attempting connection");
|
||||||
|
std::terminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
char buffer[1024];
|
||||||
|
|
||||||
|
while (!client.data_available())
|
||||||
|
;
|
||||||
|
|
||||||
|
auto recvRes = client.recv(std::span<char>(buffer, sizeof(buffer)));
|
||||||
|
|
||||||
|
if (!recvRes) {
|
||||||
|
switch (recvRes.error()) {
|
||||||
|
case tcp::Error::FailedToCreateSocket:
|
||||||
|
spdlog::error("Failed to create socket");
|
||||||
|
case tcp::Error::FailedToStartConnecting:
|
||||||
|
spdlog::error("Failed to start connecting");
|
||||||
|
case tcp::Error::FailedToReadSocketError:
|
||||||
|
spdlog::error("Failed to read socket error");
|
||||||
|
case tcp::Error::FailedToResolveHost:
|
||||||
|
spdlog::error("Failed to resolve host");
|
||||||
|
case tcp::Error::OtherOperationInProgress:
|
||||||
|
spdlog::error("Other operation in progress");
|
||||||
|
case tcp::Error::SocketNotConnected:
|
||||||
|
spdlog::error("Socket not connected");
|
||||||
|
case tcp::Error::BufferFull:
|
||||||
|
spdlog::error("Buffer full");
|
||||||
|
case tcp::Error::NoDataAvailable:
|
||||||
|
spdlog::error("No data available");
|
||||||
|
case tcp::Error::SendFailed:
|
||||||
|
spdlog::error("Send failed");
|
||||||
|
case tcp::Error::RecvFailed:
|
||||||
|
spdlog::error("Recv failed");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::terminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
spdlog::info("Received {} bytes", recvRes.value());
|
||||||
|
if (recvRes.value() > 0) {
|
||||||
|
spdlog::info("Received message: {}",
|
||||||
|
std::string(buffer, recvRes.value()));
|
||||||
|
}
|
||||||
|
|
||||||
|
EAGAIN;
|
||||||
|
|
||||||
|
const char* msg = "Hello, world!";
|
||||||
|
auto sendRes = client.send(std::span<const char>(msg, strlen(msg)));
|
||||||
|
if (!sendRes) {
|
||||||
|
spdlog::error("Failed to send message");
|
||||||
|
std::terminate();
|
||||||
|
}
|
||||||
|
spdlog::info("Sent {} bytes", sendRes.value());
|
||||||
|
client.disconnect();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
259
src/tcp.cpp
Normal file
259
src/tcp.cpp
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
#include "tcp.hpp"
|
||||||
|
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <array>
|
||||||
|
#include <cerrno>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <expected>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <netinet/tcp.h>
|
||||||
|
#include <poll.h>
|
||||||
|
#include <span>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
|
||||||
|
std::expected<sockaddr_in, tcp::Error> get_server_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{tcp::Error::FailedToResolveHost};
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
|
||||||
|
namespace tcp {
|
||||||
|
|
||||||
|
|
||||||
|
[[nodiscard]] std::expected<void, Error>
|
||||||
|
NonBlockingClient::connect(HostString host, uint16_t port) {
|
||||||
|
/// Resolve host
|
||||||
|
|
||||||
|
auto addrRes = get_server_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) {
|
||||||
|
if (errno == EALREADY) {
|
||||||
|
spdlog::error(
|
||||||
|
"tcp::NonBlockingClient: connect() failed with errno={}",
|
||||||
|
errno);
|
||||||
|
return std::unexpected{Error::OtherOperationInProgress};
|
||||||
|
} else if (errno != EINPROGRESS) {
|
||||||
|
spdlog::error(
|
||||||
|
"tcp::NonBlockingClient: connect() failed with errno={}",
|
||||||
|
errno);
|
||||||
|
return std::unexpected{Error::FailedToStartConnecting};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_startedNewConAttemt = true;
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::expected<ConnectionStatus, Error>
|
||||||
|
NonBlockingClient::get_last_connection_status() {
|
||||||
|
if (!m_startedNewConAttemt) 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 ConnectionStatus::AttemptingConnection;
|
||||||
|
|
||||||
|
m_startedNewConAttemt = 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{Error::FailedToReadSocketError};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err == 0) {
|
||||||
|
m_lastKnownConStatus = ConnectionStatus::Successful;
|
||||||
|
} else {
|
||||||
|
switch (err) {
|
||||||
|
case ECONNREFUSED:
|
||||||
|
m_lastKnownConStatus = ConnectionStatus::Refused;
|
||||||
|
break;
|
||||||
|
case ENETUNREACH:
|
||||||
|
m_lastKnownConStatus = ConnectionStatus::NoRouteToHost;
|
||||||
|
break;
|
||||||
|
case ETIMEDOUT:
|
||||||
|
m_lastKnownConStatus = ConnectionStatus::TimedOut;
|
||||||
|
break;
|
||||||
|
case EHOSTUNREACH:
|
||||||
|
m_lastKnownConStatus = ConnectionStatus::HostUnreachable;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
m_lastKnownConStatus = ConnectionStatus::OtherError;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_lastKnownConStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NonBlockingClient::disconnect() {
|
||||||
|
close_socket();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::expected<int, Error>
|
||||||
|
NonBlockingClient::send(std::span<const char> data) {
|
||||||
|
auto conRes = get_last_connection_status();
|
||||||
|
if (!conRes) return std::unexpected{conRes.error()};
|
||||||
|
if (conRes != ConnectionStatus::Successful) {
|
||||||
|
spdlog::error("tcp::NonBlockingClient: Socket not connected");
|
||||||
|
return std::unexpected{Error::SocketNotConnected};
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
switch (errno) {
|
||||||
|
case EWOULDBLOCK:
|
||||||
|
return std::unexpected{Error::BufferFull};
|
||||||
|
case EALREADY:
|
||||||
|
return std::unexpected{Error::OtherOperationInProgress};
|
||||||
|
default:
|
||||||
|
return std::unexpected{Error::SendFailed};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytesWritten;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NonBlockingClient::data_available() {
|
||||||
|
auto conRes = get_last_connection_status();
|
||||||
|
if (!conRes) return false;
|
||||||
|
if (conRes != ConnectionStatus::Successful) 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, Error>
|
||||||
|
NonBlockingClient::recv(std::span<char> 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);
|
||||||
|
|
||||||
|
switch (errno) {
|
||||||
|
case EWOULDBLOCK:
|
||||||
|
return std::unexpected{Error::NoDataAvailable};
|
||||||
|
default:
|
||||||
|
return std::unexpected{Error::RecvFailed};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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, Error> 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{Error::FailedToCreateSocket};
|
||||||
|
}
|
||||||
|
|
||||||
|
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{Error::FailedToCreateSocket};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fcntl(m_socket, F_SETFL, (currentFlags) | O_NONBLOCK) == -1) {
|
||||||
|
spdlog::error("tcp::NonBlockingClient: fcntl() failed with errno={}",
|
||||||
|
errno);
|
||||||
|
close_socket();
|
||||||
|
return std::unexpected{Error::FailedToCreateSocket};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::expected<bool, Error>
|
||||||
|
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{Error::FailedToReadSocketError};
|
||||||
|
}
|
||||||
|
|
||||||
|
return (ret == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace tcp
|
||||||
94
src/tcp.hpp
Normal file
94
src/tcp.hpp
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
#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 {
|
||||||
|
|
||||||
|
|
||||||
|
enum class Error {
|
||||||
|
FailedToCreateSocket,
|
||||||
|
FailedToStartConnecting,
|
||||||
|
FailedToReadSocketError,
|
||||||
|
FailedToResolveHost,
|
||||||
|
OtherOperationInProgress,
|
||||||
|
SocketNotConnected,
|
||||||
|
BufferFull,
|
||||||
|
NoDataAvailable,
|
||||||
|
SendFailed,
|
||||||
|
RecvFailed,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
enum class ConnectionStatus {
|
||||||
|
AttemptingConnection,
|
||||||
|
Successful,
|
||||||
|
Refused,
|
||||||
|
NoRouteToHost,
|
||||||
|
TimedOut,
|
||||||
|
HostUnreachable,
|
||||||
|
OtherError,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
using HostString = std::array<char, 64>;
|
||||||
|
|
||||||
|
// struct ServerSettings {
|
||||||
|
// uint16_t port = 0;
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// class NonBlockingServer {
|
||||||
|
// public:
|
||||||
|
// NonBlockingServer(ServerSettings settings);
|
||||||
|
// };
|
||||||
|
|
||||||
|
class NonBlockingClient {
|
||||||
|
public:
|
||||||
|
// clang-format off
|
||||||
|
[[nodiscard]] std::expected<void, Error>
|
||||||
|
connect(HostString host, uint16_t port);
|
||||||
|
// clang-format off
|
||||||
|
|
||||||
|
void disconnect();
|
||||||
|
|
||||||
|
[[nodiscard]] std::expected<ConnectionStatus, Error>
|
||||||
|
get_last_connection_status();
|
||||||
|
|
||||||
|
bool data_available();
|
||||||
|
|
||||||
|
[[nodiscard]] std::expected<int, Error> recv(std::span<char> buffer);
|
||||||
|
[[nodiscard]] std::expected<int, Error> send(std::span<const char> data);
|
||||||
|
|
||||||
|
private:
|
||||||
|
int m_socket = -1;
|
||||||
|
pollfd m_pfdOut = {.fd = -1, .events = POLLOUT};
|
||||||
|
pollfd m_pfdIn = {.fd = -1, .events = POLLIN};
|
||||||
|
|
||||||
|
bool m_startedNewConAttemt = false;
|
||||||
|
ConnectionStatus m_lastKnownConStatus = ConnectionStatus::OtherError;
|
||||||
|
|
||||||
|
void close_socket();
|
||||||
|
|
||||||
|
[[nodiscard]] std::expected<void, Error> create_socket();
|
||||||
|
[[nodiscard]] std::expected<bool, Error> new_error_codes_available();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace tcp
|
||||||
8
tests/tcp_client.cpp
Normal file
8
tests/tcp_client.cpp
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#include "tcp.hpp"
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
TEST(TcpClient, Connect) {
|
||||||
|
tcp::NonBlockingClient client;
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user