From b78059881b75560638e6132795ba5a47f43cee06 Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sun, 9 Nov 2025 08:42:49 +0000 Subject: [PATCH 01/30] Add client server example with raw sockets --- examples/CMakeLists.txt | 3 +- examples/core/CMakeLists.txt | 1 + examples/core/network/CMakeLists.txt | 1 + examples/core/network/tcp/CMakeLists.txt | 6 +++ examples/core/network/tcp/client.cpp | 39 +++++++++++++++++ examples/core/network/tcp/server.cpp | 56 ++++++++++++++++++++++++ 6 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 examples/core/CMakeLists.txt create mode 100644 examples/core/network/CMakeLists.txt create mode 100644 examples/core/network/tcp/CMakeLists.txt create mode 100644 examples/core/network/tcp/client.cpp create mode 100644 examples/core/network/tcp/server.cpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index a48f24a75..8d2987185 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1 +1,2 @@ -add_subdirectory(gfx) +add_subdirectory(core) +add_subdirectory(gfx) \ No newline at end of file diff --git a/examples/core/CMakeLists.txt b/examples/core/CMakeLists.txt new file mode 100644 index 000000000..7a1e984b2 --- /dev/null +++ b/examples/core/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(network) diff --git a/examples/core/network/CMakeLists.txt b/examples/core/network/CMakeLists.txt new file mode 100644 index 000000000..c037cff48 --- /dev/null +++ b/examples/core/network/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(tcp) diff --git a/examples/core/network/tcp/CMakeLists.txt b/examples/core/network/tcp/CMakeLists.txt new file mode 100644 index 000000000..ba46572f9 --- /dev/null +++ b/examples/core/network/tcp/CMakeLists.txt @@ -0,0 +1,6 @@ +morpheus_add_executable(NAME client) +target_sources(client PRIVATE client.cpp) +target_link_libraries(client PRIVATE morpheus::core) + +morpheus_add_executable(NAME server) +target_sources(server PRIVATE server.cpp) \ No newline at end of file diff --git a/examples/core/network/tcp/client.cpp b/examples/core/network/tcp/client.cpp new file mode 100644 index 000000000..b776234c1 --- /dev/null +++ b/examples/core/network/tcp/client.cpp @@ -0,0 +1,39 @@ +#include +#include +#include +#include + +int main() { + int sock = 0; + struct sockaddr_in serv_addr; + + if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + perror("Socket creation error"); + return 1; + } + + serv_addr.sin_family = AF_INET; + serv_addr.sin_port = htons(8080); + + // Convert IPv4 address from text to binary form + if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) { + perror("Invalid address / Address not supported"); + return 1; + } + + // Connect to server + if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { + perror("Connection failed"); + return 1; + } + + const char *msg = "Hello from client!"; + send(sock, msg, strlen(msg), 0); + + char buffer[1024] = {0}; + read(sock, buffer, sizeof(buffer)); + std::cout << "Server says: " << buffer << std::endl; + + close(sock); + return 0; +} diff --git a/examples/core/network/tcp/server.cpp b/examples/core/network/tcp/server.cpp new file mode 100644 index 000000000..89f78fec5 --- /dev/null +++ b/examples/core/network/tcp/server.cpp @@ -0,0 +1,56 @@ +#include +#include +#include // for close() +#include // for sockaddr_in, inet_addr() + +int main() { + int server_fd, client_fd; + struct sockaddr_in address; + int opt = 1; + int addrlen = sizeof(address); + + // Create socket (IPv4, TCP) + if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { + perror("socket failed"); + return 1; + } + + // Allow reuse of the address/port + setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)); + + address.sin_family = AF_INET; + address.sin_addr.s_addr = INADDR_ANY; // Bind to all interfaces + address.sin_port = htons(8080); + + // Bind socket to port 8080 + if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) { + perror("bind failed"); + return 1; + } + + // Listen for connections (max backlog 3) + if (listen(server_fd, 3) < 0) { + perror("listen"); + return 1; + } + + std::cout << "Server listening on port 8080...\n"; + + // Accept a client connection + if ((client_fd = accept(server_fd, (struct sockaddr *)&address, (socklen_t *)&addrlen)) < 0) { + perror("accept"); + return 1; + } + + char buffer[1024] = {0}; + read(client_fd, buffer, sizeof(buffer)); + std::cout << "Received: " << buffer << std::endl; + + const char *reply = "Hello from server!"; + send(client_fd, reply, strlen(reply), 0); + + close(client_fd); + close(server_fd); + + return 0; +} \ No newline at end of file From 6136a68bac0b0b829d5b8073b9f8f1402c92685a Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sun, 9 Nov 2025 08:45:05 +0000 Subject: [PATCH 02/30] Mordernise client --- examples/core/network/tcp/client.cpp | 80 ++++++++++++++++++++-------- 1 file changed, 59 insertions(+), 21 deletions(-) diff --git a/examples/core/network/tcp/client.cpp b/examples/core/network/tcp/client.cpp index b776234c1..d20aff441 100644 --- a/examples/core/network/tcp/client.cpp +++ b/examples/core/network/tcp/client.cpp @@ -1,39 +1,77 @@ +#include +#include + #include #include #include #include -int main() { - int sock = 0; - struct sockaddr_in serv_addr; +#include +#include - if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) { - perror("Socket creation error"); - return 1; +using namespace morpheus::conf; + +struct SocketCloser { + using pointer = int; /// Unique ptr should store a file descriptor handle, not a pointer + void operator()(pointer fd) const noexcept { + if (fd >= 0){ + ::close(fd); + } } +}; - serv_addr.sin_family = AF_INET; - serv_addr.sin_port = htons(8080); +using Socket = std::unique_ptr; +auto createSocket() -> exp::expected { + int s = socket(AF_INET, SOCK_STREAM, 0); + if (s < 0) { + return exp::unexpected(s); + } + return Socket(s); +} + +auto createSockAddr(std::string_view address, std::uint16_t port) -> exp::expected { + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(port); // Convert IPv4 address from text to binary form - if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) { - perror("Invalid address / Address not supported"); - return 1; + if (auto const result = inet_pton(AF_INET, address.data(), &addr.sin_addr); result <= 0) { + return exp::unexpected(result); } + return addr; +} + +auto createConnection(std::pair sockInfo) -> exp::expected, int> { + auto [sock, serv_addr] = std::move(sockInfo); + if (auto const result = connect(sock.get(), (struct sockaddr *)&serv_addr, sizeof(serv_addr)); result < 0) { + return exp::unexpected(result); + } + return std::pair{std::move(sock), std::move(serv_addr)}; +} - // Connect to server - if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) { - perror("Connection failed"); +int main() +{ + auto const combineSockAndAddr = [](Socket sock) { + return createSockAddr("127.0.0.1", 8080).and_then([sock = std::move(sock)](struct sockaddr_in addr) mutable -> exp::expected, int> { + return std::pair{std::move(sock), std::move(addr)}; + }); + }; + + auto sockAndAdds = createSocket() + .and_then(combineSockAndAddr) + .and_then(createConnection); + + if (!sockAndAdds) { return 1; } + auto [sock, serv_addr] = std::move(*sockAndAdds); - const char *msg = "Hello from client!"; - send(sock, msg, strlen(msg), 0); + std::string_view const msg = "Hello from client!"; + send(sock.get(), msg.data(), msg.size(), 0); - char buffer[1024] = {0}; - read(sock, buffer, sizeof(buffer)); - std::cout << "Server says: " << buffer << std::endl; + std::array buffer = {0}; + read(sock.get(), buffer.data(), buffer.size()); - close(sock); + print::print("Server says: {}\n", std::string_view(buffer.data(), buffer.size())); return 0; -} +} \ No newline at end of file From a61092637170381a80d5439a9a583e6a56bd13a5 Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sun, 9 Nov 2025 08:51:35 +0000 Subject: [PATCH 03/30] Remove unnecessary move --- examples/core/network/tcp/client.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/core/network/tcp/client.cpp b/examples/core/network/tcp/client.cpp index d20aff441..4c91182b2 100644 --- a/examples/core/network/tcp/client.cpp +++ b/examples/core/network/tcp/client.cpp @@ -64,7 +64,7 @@ int main() if (!sockAndAdds) { return 1; } - auto [sock, serv_addr] = std::move(*sockAndAdds); + auto const& [sock, serv_addr] = *sockAndAdds; std::string_view const msg = "Hello from client!"; send(sock.get(), msg.data(), msg.size(), 0); From 96c59dca8818a9974293d5fa0c178c1c56026dde Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Mon, 10 Nov 2025 08:00:37 +0000 Subject: [PATCH 04/30] Client/Server now using modern C++ --- examples/core/network/tcp/CMakeLists.txt | 5 +- examples/core/network/tcp/client.cpp | 43 +-------- examples/core/network/tcp/common.hpp | 53 +++++++++++ examples/core/network/tcp/server.cpp | 113 +++++++++++++++-------- 4 files changed, 135 insertions(+), 79 deletions(-) create mode 100644 examples/core/network/tcp/common.hpp diff --git a/examples/core/network/tcp/CMakeLists.txt b/examples/core/network/tcp/CMakeLists.txt index ba46572f9..30e497305 100644 --- a/examples/core/network/tcp/CMakeLists.txt +++ b/examples/core/network/tcp/CMakeLists.txt @@ -1,6 +1,9 @@ morpheus_add_executable(NAME client) +target_include_directories(client PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_sources(client PRIVATE client.cpp) target_link_libraries(client PRIVATE morpheus::core) morpheus_add_executable(NAME server) -target_sources(server PRIVATE server.cpp) \ No newline at end of file +target_include_directories(server PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) +target_sources(server PRIVATE server.cpp) +target_link_libraries(server PRIVATE morpheus::core) \ No newline at end of file diff --git a/examples/core/network/tcp/client.cpp b/examples/core/network/tcp/client.cpp index 4c91182b2..04fe87365 100644 --- a/examples/core/network/tcp/client.cpp +++ b/examples/core/network/tcp/client.cpp @@ -1,54 +1,15 @@ #include #include +#include -#include -#include #include #include #include -#include +using namespace morpheus; using namespace morpheus::conf; -struct SocketCloser { - using pointer = int; /// Unique ptr should store a file descriptor handle, not a pointer - void operator()(pointer fd) const noexcept { - if (fd >= 0){ - ::close(fd); - } - } -}; - -using Socket = std::unique_ptr; - -auto createSocket() -> exp::expected { - int s = socket(AF_INET, SOCK_STREAM, 0); - if (s < 0) { - return exp::unexpected(s); - } - return Socket(s); -} - -auto createSockAddr(std::string_view address, std::uint16_t port) -> exp::expected { - struct sockaddr_in addr; - addr.sin_family = AF_INET; - addr.sin_port = htons(port); - // Convert IPv4 address from text to binary form - if (auto const result = inet_pton(AF_INET, address.data(), &addr.sin_addr); result <= 0) { - return exp::unexpected(result); - } - return addr; -} - -auto createConnection(std::pair sockInfo) -> exp::expected, int> { - auto [sock, serv_addr] = std::move(sockInfo); - if (auto const result = connect(sock.get(), (struct sockaddr *)&serv_addr, sizeof(serv_addr)); result < 0) { - return exp::unexpected(result); - } - return std::pair{std::move(sock), std::move(serv_addr)}; -} - int main() { auto const combineSockAndAddr = [](Socket sock) { diff --git a/examples/core/network/tcp/common.hpp b/examples/core/network/tcp/common.hpp new file mode 100644 index 000000000..398c2c22a --- /dev/null +++ b/examples/core/network/tcp/common.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include +#include + +#include +#include + +#include +#include + +namespace morpheus { + +struct SocketCloser { + using pointer = int; /// Unique ptr should store a file descriptor handle, not a pointer + void operator()(pointer fd) const noexcept { + if (fd >= 0){ + ::close(fd); + } + } +}; + +using Socket = std::unique_ptr; +using SocketAndAddr = std::pair; + +auto createSocket() -> conf::exp::expected { + int s = socket(AF_INET, SOCK_STREAM, 0); + if (s < 0) { + return conf::exp::unexpected(s); + } + return Socket(s); +} + +auto createSockAddr(std::string_view address, std::uint16_t port) -> conf::exp::expected { + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + // Convert IPv4 address from text to binary form + if (auto const result = inet_pton(AF_INET, address.data(), &addr.sin_addr); result <= 0) { + return conf::exp::unexpected(result); + } + return addr; +} + +auto createConnection(SocketAndAddr sockInfo) -> conf::exp::expected { + auto [sock, serv_addr] = std::move(sockInfo); + if (auto const result = connect(sock.get(), (struct sockaddr *)&serv_addr, sizeof(serv_addr)); result < 0) { + return conf::exp::unexpected(result); + } + return std::pair{std::move(sock), std::move(serv_addr)}; +} + +} // namespace morpheus \ No newline at end of file diff --git a/examples/core/network/tcp/server.cpp b/examples/core/network/tcp/server.cpp index 89f78fec5..ceda989aa 100644 --- a/examples/core/network/tcp/server.cpp +++ b/examples/core/network/tcp/server.cpp @@ -1,56 +1,95 @@ -#include +#include +#include + #include #include // for close() #include // for sockaddr_in, inet_addr() -int main() { - int server_fd, client_fd; - struct sockaddr_in address; - int opt = 1; - int addrlen = sizeof(address); - - // Create socket (IPv4, TCP) - if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { - perror("socket failed"); - return 1; +using namespace morpheus; +using namespace morpheus::conf; + +auto createSockAddr(std::uint16_t port) -> conf::exp::expected { + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; // Bind to all interfaces + addr.sin_port = htons(port); + return addr; +} + +auto acceptConnection(SocketAndAddr const& socketInfo) -> exp::expected { + int addrlen = sizeof(socketInfo.second); + // Accept a client connection + if (int client_fd = accept(socketInfo.first.get(), (struct sockaddr *)&socketInfo.second, (socklen_t *)&addrlen); client_fd < 0) { + return conf::exp::unexpected(client_fd); } + else{ + return Socket(client_fd); + } +} + +int main() { - // Allow reuse of the address/port - setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)); + std::uint16_t const PORT = 8080; - address.sin_family = AF_INET; - address.sin_addr.s_addr = INADDR_ANY; // Bind to all interfaces - address.sin_port = htons(8080); + auto const setSockOptions = [](Socket sock) -> exp::expected { + // Allow reuse of the address/port + int opt = 1; + setsockopt(sock.get(), SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)); + return sock; + }; - // Bind socket to port 8080 - if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) { - perror("bind failed"); - return 1; - } + auto const combineSockAndAddr = [&](Socket sock) -> exp::expected { + return createSockAddr(PORT).and_then([sock = std::move(sock)](struct sockaddr_in addr) mutable -> exp::expected { + return std::pair{std::move(sock), std::move(addr)}; + }); + }; - // Listen for connections (max backlog 3) - if (listen(server_fd, 3) < 0) { - perror("listen"); - return 1; - } + auto const bindAddress = [](SocketAndAddr sockInfo) -> exp::expected { + auto [sock, addr] = std::move(sockInfo); + // Bind socket to the address + if (auto const result = bind(sock.get(), (struct sockaddr *)&addr, sizeof(addr)); result < 0) { + return conf::exp::unexpected(result); + } + return std::pair{std::move(sock), std::move(addr)}; + }; - std::cout << "Server listening on port 8080...\n"; + auto const listenOnSocket = [](SocketAndAddr socketInfo) -> exp::expected { + // Listen for connections (max backlog 3) + if (auto const result = listen(socketInfo.first.get(), 3); result < 0) { + return conf::exp::unexpected(result); + } + return socketInfo; + }; - // Accept a client connection - if ((client_fd = accept(server_fd, (struct sockaddr *)&address, (socklen_t *)&addrlen)) < 0) { - perror("accept"); + auto sockAddrClient = createSocket() + .and_then(setSockOptions) + .and_then(combineSockAndAddr) + .and_then(bindAddress) + .and_then(listenOnSocket) + .and_then([&PORT](SocketAndAddr socketInfo) -> exp::expected, int> { + print::print("Server listening on port {}...\n", PORT); + if (auto client = acceptConnection(socketInfo); !client) { + return exp::unexpected(client.error()); + } + else { + return std::tuple{std::move(socketInfo.first), std::move(socketInfo.second), std::move(*client)}; + } + }); + + if (!sockAddrClient) { return 1; } - char buffer[1024] = {0}; - read(client_fd, buffer, sizeof(buffer)); - std::cout << "Received: " << buffer << std::endl; + auto const& [server_fd, serv_addr, client_fd] = *sockAddrClient; + + print::print("Server listening on port {}...\n", PORT); - const char *reply = "Hello from server!"; - send(client_fd, reply, strlen(reply), 0); + std::array buffer = {0}; + read(client_fd.get(), buffer.data(), buffer.size()); + print::print("Received: {}\n", std::string_view(buffer.data(), buffer.size())); - close(client_fd); - close(server_fd); + std::string_view const reply = "Hello from server!"; + send(client_fd.get(), reply.data(), reply.size(), 0); return 0; } \ No newline at end of file From 08a610fa282120a30dcb3fc57c25c1663e89c2b0 Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Mon, 10 Nov 2025 08:54:39 +0000 Subject: [PATCH 05/30] Store errors as error codes --- examples/core/network/tcp/client.cpp | 8 ++++--- examples/core/network/tcp/common.hpp | 12 +++++------ examples/core/network/tcp/server.cpp | 31 +++++++++++++++------------- 3 files changed, 28 insertions(+), 23 deletions(-) diff --git a/examples/core/network/tcp/client.cpp b/examples/core/network/tcp/client.cpp index 04fe87365..90705671a 100644 --- a/examples/core/network/tcp/client.cpp +++ b/examples/core/network/tcp/client.cpp @@ -13,7 +13,7 @@ using namespace morpheus::conf; int main() { auto const combineSockAndAddr = [](Socket sock) { - return createSockAddr("127.0.0.1", 8080).and_then([sock = std::move(sock)](struct sockaddr_in addr) mutable -> exp::expected, int> { + return createSockAddr("127.0.0.1", 8080).and_then([sock = std::move(sock)](struct sockaddr_in addr) mutable -> exp::expected, std::error_code> { return std::pair{std::move(sock), std::move(addr)}; }); }; @@ -23,8 +23,10 @@ int main() .and_then(createConnection); if (!sockAndAdds) { - return 1; + print::print("Error: {}\n", sockAndAdds.error().message()); + return EXIT_FAILURE; } + auto const& [sock, serv_addr] = *sockAndAdds; std::string_view const msg = "Hello from client!"; @@ -34,5 +36,5 @@ int main() read(sock.get(), buffer.data(), buffer.size()); print::print("Server says: {}\n", std::string_view(buffer.data(), buffer.size())); - return 0; + return EXIT_SUCCESS; } \ No newline at end of file diff --git a/examples/core/network/tcp/common.hpp b/examples/core/network/tcp/common.hpp index 398c2c22a..d70f47b58 100644 --- a/examples/core/network/tcp/common.hpp +++ b/examples/core/network/tcp/common.hpp @@ -23,29 +23,29 @@ struct SocketCloser { using Socket = std::unique_ptr; using SocketAndAddr = std::pair; -auto createSocket() -> conf::exp::expected { +auto createSocket() -> conf::exp::expected { int s = socket(AF_INET, SOCK_STREAM, 0); if (s < 0) { - return conf::exp::unexpected(s); + return conf::exp::unexpected(std::error_code(errno, std::system_category())); } return Socket(s); } -auto createSockAddr(std::string_view address, std::uint16_t port) -> conf::exp::expected { +auto createSockAddr(std::string_view address, std::uint16_t port) -> conf::exp::expected { struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(port); // Convert IPv4 address from text to binary form if (auto const result = inet_pton(AF_INET, address.data(), &addr.sin_addr); result <= 0) { - return conf::exp::unexpected(result); + return conf::exp::unexpected(std::error_code(errno, std::system_category())); } return addr; } -auto createConnection(SocketAndAddr sockInfo) -> conf::exp::expected { +auto createConnection(SocketAndAddr sockInfo) -> conf::exp::expected { auto [sock, serv_addr] = std::move(sockInfo); if (auto const result = connect(sock.get(), (struct sockaddr *)&serv_addr, sizeof(serv_addr)); result < 0) { - return conf::exp::unexpected(result); + return conf::exp::unexpected(std::error_code(errno, std::system_category())); } return std::pair{std::move(sock), std::move(serv_addr)}; } diff --git a/examples/core/network/tcp/server.cpp b/examples/core/network/tcp/server.cpp index ceda989aa..29f252bc3 100644 --- a/examples/core/network/tcp/server.cpp +++ b/examples/core/network/tcp/server.cpp @@ -8,7 +8,7 @@ using namespace morpheus; using namespace morpheus::conf; -auto createSockAddr(std::uint16_t port) -> conf::exp::expected { +auto createSockAddr(std::uint16_t port) -> conf::exp::expected { struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; // Bind to all interfaces @@ -16,11 +16,11 @@ auto createSockAddr(std::uint16_t port) -> conf::exp::expected exp::expected { +auto acceptConnection(SocketAndAddr const& socketInfo) -> exp::expected { int addrlen = sizeof(socketInfo.second); // Accept a client connection if (int client_fd = accept(socketInfo.first.get(), (struct sockaddr *)&socketInfo.second, (socklen_t *)&addrlen); client_fd < 0) { - return conf::exp::unexpected(client_fd); + return conf::exp::unexpected(std::error_code(errno, std::system_category())); } else{ return Socket(client_fd); @@ -31,32 +31,34 @@ int main() { std::uint16_t const PORT = 8080; - auto const setSockOptions = [](Socket sock) -> exp::expected { + auto const setSockOptions = [](Socket sock) -> exp::expected { // Allow reuse of the address/port int opt = 1; - setsockopt(sock.get(), SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)); + if (auto result = setsockopt(sock.get(), SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)); result < 0) { + return conf::exp::unexpected(std::error_code(errno, std::system_category())); + } return sock; }; - auto const combineSockAndAddr = [&](Socket sock) -> exp::expected { - return createSockAddr(PORT).and_then([sock = std::move(sock)](struct sockaddr_in addr) mutable -> exp::expected { + auto const combineSockAndAddr = [&](Socket sock) -> exp::expected { + return createSockAddr(PORT).and_then([sock = std::move(sock)](struct sockaddr_in addr) mutable -> exp::expected { return std::pair{std::move(sock), std::move(addr)}; }); }; - auto const bindAddress = [](SocketAndAddr sockInfo) -> exp::expected { + auto const bindAddress = [](SocketAndAddr sockInfo) -> exp::expected { auto [sock, addr] = std::move(sockInfo); // Bind socket to the address if (auto const result = bind(sock.get(), (struct sockaddr *)&addr, sizeof(addr)); result < 0) { - return conf::exp::unexpected(result); + return conf::exp::unexpected(std::error_code(errno, std::system_category())); } return std::pair{std::move(sock), std::move(addr)}; }; - auto const listenOnSocket = [](SocketAndAddr socketInfo) -> exp::expected { + auto const listenOnSocket = [](SocketAndAddr socketInfo) -> exp::expected { // Listen for connections (max backlog 3) if (auto const result = listen(socketInfo.first.get(), 3); result < 0) { - return conf::exp::unexpected(result); + return conf::exp::unexpected(std::error_code(errno, std::system_category())); } return socketInfo; }; @@ -66,7 +68,7 @@ int main() { .and_then(combineSockAndAddr) .and_then(bindAddress) .and_then(listenOnSocket) - .and_then([&PORT](SocketAndAddr socketInfo) -> exp::expected, int> { + .and_then([&PORT](SocketAndAddr socketInfo) -> exp::expected, std::error_code> { print::print("Server listening on port {}...\n", PORT); if (auto client = acceptConnection(socketInfo); !client) { return exp::unexpected(client.error()); @@ -77,7 +79,8 @@ int main() { }); if (!sockAddrClient) { - return 1; + print::print("Error: {}\n", sockAddrClient.error().message()); + return EXIT_FAILURE; } auto const& [server_fd, serv_addr, client_fd] = *sockAddrClient; @@ -91,5 +94,5 @@ int main() { std::string_view const reply = "Hello from server!"; send(client_fd.get(), reply.data(), reply.size(), 0); - return 0; + return EXIT_SUCCESS; } \ No newline at end of file From 872fff880ea01f4bcb4d1a96b197b5f3b74499eb Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sat, 13 Dec 2025 14:36:02 +0000 Subject: [PATCH 06/30] Move to blocking folder --- examples/core/network/tcp/CMakeLists.txt | 11 +--- .../core/network/tcp/blocking/CMakeLists.txt | 9 +++ examples/core/network/tcp/blocking/client.cpp | 56 +++++++++++++++++++ .../network/tcp/{ => blocking}/common.hpp | 17 ++++++ .../network/tcp/{ => blocking}/server.cpp | 22 +++++--- examples/core/network/tcp/client.cpp | 40 ------------- 6 files changed, 97 insertions(+), 58 deletions(-) create mode 100644 examples/core/network/tcp/blocking/CMakeLists.txt create mode 100644 examples/core/network/tcp/blocking/client.cpp rename examples/core/network/tcp/{ => blocking}/common.hpp (69%) rename examples/core/network/tcp/{ => blocking}/server.cpp (83%) delete mode 100644 examples/core/network/tcp/client.cpp diff --git a/examples/core/network/tcp/CMakeLists.txt b/examples/core/network/tcp/CMakeLists.txt index 30e497305..3d6a82696 100644 --- a/examples/core/network/tcp/CMakeLists.txt +++ b/examples/core/network/tcp/CMakeLists.txt @@ -1,9 +1,2 @@ -morpheus_add_executable(NAME client) -target_include_directories(client PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) -target_sources(client PRIVATE client.cpp) -target_link_libraries(client PRIVATE morpheus::core) - -morpheus_add_executable(NAME server) -target_include_directories(server PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) -target_sources(server PRIVATE server.cpp) -target_link_libraries(server PRIVATE morpheus::core) \ No newline at end of file +add_subdirectory(blocking) +add_subdirectory(non_blocking) diff --git a/examples/core/network/tcp/blocking/CMakeLists.txt b/examples/core/network/tcp/blocking/CMakeLists.txt new file mode 100644 index 000000000..30e497305 --- /dev/null +++ b/examples/core/network/tcp/blocking/CMakeLists.txt @@ -0,0 +1,9 @@ +morpheus_add_executable(NAME client) +target_include_directories(client PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) +target_sources(client PRIVATE client.cpp) +target_link_libraries(client PRIVATE morpheus::core) + +morpheus_add_executable(NAME server) +target_include_directories(server PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) +target_sources(server PRIVATE server.cpp) +target_link_libraries(server PRIVATE morpheus::core) \ No newline at end of file diff --git a/examples/core/network/tcp/blocking/client.cpp b/examples/core/network/tcp/blocking/client.cpp new file mode 100644 index 000000000..c86e322da --- /dev/null +++ b/examples/core/network/tcp/blocking/client.cpp @@ -0,0 +1,56 @@ +#include +#include +#include + +#include +#include + +#include + +using namespace morpheus; +using namespace morpheus::conf; + +int main() +{ + auto const combineSockAndAddr = [](Socket sock) { + return createSockAddr("127.0.0.1", 8080).and_then([sock = std::move(sock)](struct sockaddr_in addr) mutable -> exp::expected, std::error_code> { + return std::pair{std::move(sock), std::move(addr)}; + }); + }; + + auto const sendMsg = [](SocketAndAddr sockInfo) -> exp::expected { + auto const& [sock, serv_addr] = sockInfo; + if (auto const result = sendData(sock, "Hello from the client!")){ + print::print("Sent {} bytes to server\n", *result); + } + else { + return exp::unexpected(result.error()); + } + return sockInfo; + }; + + auto const receiveMsg = [](SocketAndAddr sockInfo) -> exp::expected { + auto const& [sock, serv_addr] = sockInfo; + if (auto const result = receiveData(sock)) { + return exp::unexpected(result.error()); + } + else { + print::print("Received {} bytes from server: {}\n", result->size(), *result); + } + return sockInfo; + }; + + auto sockAndAdds = createSocket() + .and_then(combineSockAndAddr) + .and_then(createConnection) + .and_then(sendMsg) + .and_then(receiveMsg); + + if (!sockAndAdds) { + print::print("Error: {}\n", sockAndAdds.error().message()); + return EXIT_FAILURE; + } + else{ + return EXIT_SUCCESS; + } +} \ No newline at end of file diff --git a/examples/core/network/tcp/common.hpp b/examples/core/network/tcp/blocking/common.hpp similarity index 69% rename from examples/core/network/tcp/common.hpp rename to examples/core/network/tcp/blocking/common.hpp index d70f47b58..bb18ca787 100644 --- a/examples/core/network/tcp/common.hpp +++ b/examples/core/network/tcp/blocking/common.hpp @@ -50,4 +50,21 @@ auto createConnection(SocketAndAddr sockInfo) -> conf::exp::expected conf::exp::expected { + if (auto result = send(sock.get(), data.data(), data.size(), 0); result < 0) { + return conf::exp::unexpected(std::error_code(errno, std::system_category())); + } else { + return static_cast(result); + } +} + +auto receiveData(Socket const& sock) -> conf::exp::expected { + std::array buffer = {0}; + if (auto result = read(sock.get(), buffer.data(), buffer.size()); result < 0) { + return conf::exp::unexpected(std::error_code(errno, std::system_category())); + } else { + return std::string(buffer.data(), static_cast(result)); + } +} + } // namespace morpheus \ No newline at end of file diff --git a/examples/core/network/tcp/server.cpp b/examples/core/network/tcp/blocking/server.cpp similarity index 83% rename from examples/core/network/tcp/server.cpp rename to examples/core/network/tcp/blocking/server.cpp index 29f252bc3..01c5b605b 100644 --- a/examples/core/network/tcp/server.cpp +++ b/examples/core/network/tcp/blocking/server.cpp @@ -63,20 +63,24 @@ int main() { return socketInfo; }; + auto const acceptConnectionAndFwdSocketInfo = [&PORT](SocketAndAddr socketInfo) -> exp::expected, std::error_code> { + print::print("Server listening on port {}...\n", PORT); + if (auto client = acceptConnection(socketInfo); !client) { + return exp::unexpected(client.error()); + } + else { + return std::tuple{std::move(socketInfo.first), std::move(socketInfo.second), std::move(*client)}; + } + }; + + + auto sockAddrClient = createSocket() .and_then(setSockOptions) .and_then(combineSockAndAddr) .and_then(bindAddress) .and_then(listenOnSocket) - .and_then([&PORT](SocketAndAddr socketInfo) -> exp::expected, std::error_code> { - print::print("Server listening on port {}...\n", PORT); - if (auto client = acceptConnection(socketInfo); !client) { - return exp::unexpected(client.error()); - } - else { - return std::tuple{std::move(socketInfo.first), std::move(socketInfo.second), std::move(*client)}; - } - }); + .and_then(acceptConnectionAndFwdSocketInfo); if (!sockAddrClient) { print::print("Error: {}\n", sockAddrClient.error().message()); diff --git a/examples/core/network/tcp/client.cpp b/examples/core/network/tcp/client.cpp deleted file mode 100644 index 90705671a..000000000 --- a/examples/core/network/tcp/client.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include -#include -#include - -#include -#include - -#include - -using namespace morpheus; -using namespace morpheus::conf; - -int main() -{ - auto const combineSockAndAddr = [](Socket sock) { - return createSockAddr("127.0.0.1", 8080).and_then([sock = std::move(sock)](struct sockaddr_in addr) mutable -> exp::expected, std::error_code> { - return std::pair{std::move(sock), std::move(addr)}; - }); - }; - - auto sockAndAdds = createSocket() - .and_then(combineSockAndAddr) - .and_then(createConnection); - - if (!sockAndAdds) { - print::print("Error: {}\n", sockAndAdds.error().message()); - return EXIT_FAILURE; - } - - auto const& [sock, serv_addr] = *sockAndAdds; - - std::string_view const msg = "Hello from client!"; - send(sock.get(), msg.data(), msg.size(), 0); - - std::array buffer = {0}; - read(sock.get(), buffer.data(), buffer.size()); - - print::print("Server says: {}\n", std::string_view(buffer.data(), buffer.size())); - return EXIT_SUCCESS; -} \ No newline at end of file From 33d0366827040ba9627a1e0a2ee1598e09b60427 Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sat, 13 Dec 2025 14:37:20 +0000 Subject: [PATCH 07/30] Run pre-commit --- examples/CMakeLists.txt | 2 +- .../core/network/tcp/blocking/CMakeLists.txt | 2 +- examples/core/network/tcp/blocking/client.cpp | 45 ++++++----- examples/core/network/tcp/blocking/common.hpp | 54 ++++++++----- examples/core/network/tcp/blocking/server.cpp | 76 +++++++++++-------- 5 files changed, 108 insertions(+), 71 deletions(-) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 8d2987185..037544fa3 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,2 +1,2 @@ add_subdirectory(core) -add_subdirectory(gfx) \ No newline at end of file +add_subdirectory(gfx) diff --git a/examples/core/network/tcp/blocking/CMakeLists.txt b/examples/core/network/tcp/blocking/CMakeLists.txt index 30e497305..8a1e90def 100644 --- a/examples/core/network/tcp/blocking/CMakeLists.txt +++ b/examples/core/network/tcp/blocking/CMakeLists.txt @@ -6,4 +6,4 @@ target_link_libraries(client PRIVATE morpheus::core) morpheus_add_executable(NAME server) target_include_directories(server PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_sources(server PRIVATE server.cpp) -target_link_libraries(server PRIVATE morpheus::core) \ No newline at end of file +target_link_libraries(server PRIVATE morpheus::core) diff --git a/examples/core/network/tcp/blocking/client.cpp b/examples/core/network/tcp/blocking/client.cpp index c86e322da..7bd0fc5d3 100644 --- a/examples/core/network/tcp/blocking/client.cpp +++ b/examples/core/network/tcp/blocking/client.cpp @@ -1,9 +1,9 @@ +#include #include #include -#include -#include #include +#include #include @@ -12,45 +12,50 @@ using namespace morpheus::conf; int main() { - auto const combineSockAndAddr = [](Socket sock) { - return createSockAddr("127.0.0.1", 8080).and_then([sock = std::move(sock)](struct sockaddr_in addr) mutable -> exp::expected, std::error_code> { - return std::pair{std::move(sock), std::move(addr)}; - }); + auto const combineSockAndAddr = [](Socket sock) + { + return createSockAddr("127.0.0.1", 8080) + .and_then([sock = std::move(sock)](struct sockaddr_in addr) mutable -> exp::expected, std::error_code> + { return std::pair{std::move(sock), std::move(addr)}; }); }; - auto const sendMsg = [](SocketAndAddr sockInfo) -> exp::expected { + auto const sendMsg = [](SocketAndAddr sockInfo) -> exp::expected + { auto const& [sock, serv_addr] = sockInfo; - if (auto const result = sendData(sock, "Hello from the client!")){ + if (auto const result = sendData(sock, "Hello from the client!")) + { print::print("Sent {} bytes to server\n", *result); } - else { + else + { return exp::unexpected(result.error()); } return sockInfo; }; - auto const receiveMsg = [](SocketAndAddr sockInfo) -> exp::expected { + auto const receiveMsg = [](SocketAndAddr sockInfo) -> exp::expected + { auto const& [sock, serv_addr] = sockInfo; - if (auto const result = receiveData(sock)) { + if (auto const result = receiveData(sock)) + { return exp::unexpected(result.error()); } - else { + else + { print::print("Received {} bytes from server: {}\n", result->size(), *result); } return sockInfo; }; - auto sockAndAdds = createSocket() - .and_then(combineSockAndAddr) - .and_then(createConnection) - .and_then(sendMsg) - .and_then(receiveMsg); + auto sockAndAdds = createSocket().and_then(combineSockAndAddr).and_then(createConnection).and_then(sendMsg).and_then(receiveMsg); - if (!sockAndAdds) { + if (!sockAndAdds) + { print::print("Error: {}\n", sockAndAdds.error().message()); return EXIT_FAILURE; } - else{ + else + { return EXIT_SUCCESS; } -} \ No newline at end of file +} diff --git a/examples/core/network/tcp/blocking/common.hpp b/examples/core/network/tcp/blocking/common.hpp index bb18ca787..65e8dcdd8 100644 --- a/examples/core/network/tcp/blocking/common.hpp +++ b/examples/core/network/tcp/blocking/common.hpp @@ -3,18 +3,22 @@ #include #include -#include #include +#include #include #include -namespace morpheus { +namespace morpheus +{ -struct SocketCloser { +struct SocketCloser +{ using pointer = int; /// Unique ptr should store a file descriptor handle, not a pointer - void operator()(pointer fd) const noexcept { - if (fd >= 0){ + void operator()(pointer fd) const noexcept + { + if (fd >= 0) + { ::close(fd); } } @@ -23,48 +27,62 @@ struct SocketCloser { using Socket = std::unique_ptr; using SocketAndAddr = std::pair; -auto createSocket() -> conf::exp::expected { +auto createSocket() -> conf::exp::expected +{ int s = socket(AF_INET, SOCK_STREAM, 0); - if (s < 0) { + if (s < 0) + { return conf::exp::unexpected(std::error_code(errno, std::system_category())); } return Socket(s); } -auto createSockAddr(std::string_view address, std::uint16_t port) -> conf::exp::expected { +auto createSockAddr(std::string_view address, std::uint16_t port) -> conf::exp::expected +{ struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(port); // Convert IPv4 address from text to binary form - if (auto const result = inet_pton(AF_INET, address.data(), &addr.sin_addr); result <= 0) { + if (auto const result = inet_pton(AF_INET, address.data(), &addr.sin_addr); result <= 0) + { return conf::exp::unexpected(std::error_code(errno, std::system_category())); } return addr; } -auto createConnection(SocketAndAddr sockInfo) -> conf::exp::expected { +auto createConnection(SocketAndAddr sockInfo) -> conf::exp::expected +{ auto [sock, serv_addr] = std::move(sockInfo); - if (auto const result = connect(sock.get(), (struct sockaddr *)&serv_addr, sizeof(serv_addr)); result < 0) { + if (auto const result = connect(sock.get(), (struct sockaddr*)&serv_addr, sizeof(serv_addr)); result < 0) + { return conf::exp::unexpected(std::error_code(errno, std::system_category())); } return std::pair{std::move(sock), std::move(serv_addr)}; } -auto sendData(Socket const& sock, std::string_view data) -> conf::exp::expected { - if (auto result = send(sock.get(), data.data(), data.size(), 0); result < 0) { +auto sendData(Socket const& sock, std::string_view data) -> conf::exp::expected +{ + if (auto result = send(sock.get(), data.data(), data.size(), 0); result < 0) + { return conf::exp::unexpected(std::error_code(errno, std::system_category())); - } else { + } + else + { return static_cast(result); } } -auto receiveData(Socket const& sock) -> conf::exp::expected { +auto receiveData(Socket const& sock) -> conf::exp::expected +{ std::array buffer = {0}; - if (auto result = read(sock.get(), buffer.data(), buffer.size()); result < 0) { + if (auto result = read(sock.get(), buffer.data(), buffer.size()); result < 0) + { return conf::exp::unexpected(std::error_code(errno, std::system_category())); - } else { + } + else + { return std::string(buffer.data(), static_cast(result)); } } -} // namespace morpheus \ No newline at end of file +} // namespace morpheus diff --git a/examples/core/network/tcp/blocking/server.cpp b/examples/core/network/tcp/blocking/server.cpp index 01c5b605b..19e4b6ab6 100644 --- a/examples/core/network/tcp/blocking/server.cpp +++ b/examples/core/network/tcp/blocking/server.cpp @@ -1,14 +1,15 @@ -#include #include +#include +#include // for sockaddr_in, inet_addr() #include -#include // for close() -#include // for sockaddr_in, inet_addr() +#include // for close() using namespace morpheus; using namespace morpheus::conf; -auto createSockAddr(std::uint16_t port) -> conf::exp::expected { +auto createSockAddr(std::uint16_t port) -> conf::exp::expected +{ struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; // Bind to all interfaces @@ -16,73 +17,86 @@ auto createSockAddr(std::uint16_t port) -> conf::exp::expected exp::expected { +auto acceptConnection(SocketAndAddr const& socketInfo) -> exp::expected +{ int addrlen = sizeof(socketInfo.second); // Accept a client connection - if (int client_fd = accept(socketInfo.first.get(), (struct sockaddr *)&socketInfo.second, (socklen_t *)&addrlen); client_fd < 0) { + if (int client_fd = accept(socketInfo.first.get(), (struct sockaddr*)&socketInfo.second, (socklen_t*)&addrlen); client_fd < 0) + { return conf::exp::unexpected(std::error_code(errno, std::system_category())); } - else{ + else + { return Socket(client_fd); } } -int main() { +int main() +{ std::uint16_t const PORT = 8080; - auto const setSockOptions = [](Socket sock) -> exp::expected { + auto const setSockOptions = [](Socket sock) -> exp::expected + { // Allow reuse of the address/port int opt = 1; - if (auto result = setsockopt(sock.get(), SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)); result < 0) { + if (auto result = setsockopt(sock.get(), SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)); result < 0) + { return conf::exp::unexpected(std::error_code(errno, std::system_category())); } return sock; }; - auto const combineSockAndAddr = [&](Socket sock) -> exp::expected { - return createSockAddr(PORT).and_then([sock = std::move(sock)](struct sockaddr_in addr) mutable -> exp::expected { - return std::pair{std::move(sock), std::move(addr)}; - }); + auto const combineSockAndAddr = [&](Socket sock) -> exp::expected + { + return createSockAddr(PORT).and_then([sock = std::move(sock)](struct sockaddr_in addr) mutable -> exp::expected + { return std::pair{std::move(sock), std::move(addr)}; }); }; - auto const bindAddress = [](SocketAndAddr sockInfo) -> exp::expected { + auto const bindAddress = [](SocketAndAddr sockInfo) -> exp::expected + { auto [sock, addr] = std::move(sockInfo); // Bind socket to the address - if (auto const result = bind(sock.get(), (struct sockaddr *)&addr, sizeof(addr)); result < 0) { + if (auto const result = bind(sock.get(), (struct sockaddr*)&addr, sizeof(addr)); result < 0) + { return conf::exp::unexpected(std::error_code(errno, std::system_category())); } return std::pair{std::move(sock), std::move(addr)}; }; - auto const listenOnSocket = [](SocketAndAddr socketInfo) -> exp::expected { + auto const listenOnSocket = [](SocketAndAddr socketInfo) -> exp::expected + { // Listen for connections (max backlog 3) - if (auto const result = listen(socketInfo.first.get(), 3); result < 0) { + if (auto const result = listen(socketInfo.first.get(), 3); result < 0) + { return conf::exp::unexpected(std::error_code(errno, std::system_category())); } return socketInfo; }; - auto const acceptConnectionAndFwdSocketInfo = [&PORT](SocketAndAddr socketInfo) -> exp::expected, std::error_code> { + auto const acceptConnectionAndFwdSocketInfo = + [&PORT](SocketAndAddr socketInfo) -> exp::expected, std::error_code> + { print::print("Server listening on port {}...\n", PORT); - if (auto client = acceptConnection(socketInfo); !client) { + if (auto client = acceptConnection(socketInfo); !client) + { return exp::unexpected(client.error()); } - else { + else + { return std::tuple{std::move(socketInfo.first), std::move(socketInfo.second), std::move(*client)}; } }; - - auto sockAddrClient = createSocket() - .and_then(setSockOptions) - .and_then(combineSockAndAddr) - .and_then(bindAddress) - .and_then(listenOnSocket) - .and_then(acceptConnectionAndFwdSocketInfo); - - if (!sockAddrClient) { + .and_then(setSockOptions) + .and_then(combineSockAndAddr) + .and_then(bindAddress) + .and_then(listenOnSocket) + .and_then(acceptConnectionAndFwdSocketInfo); + + if (!sockAddrClient) + { print::print("Error: {}\n", sockAddrClient.error().message()); return EXIT_FAILURE; } @@ -99,4 +113,4 @@ int main() { send(client_fd.get(), reply.data(), reply.size(), 0); return EXIT_SUCCESS; -} \ No newline at end of file +} From 69c97721a3d60e68cb7be88ecc978086e988db28 Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sat, 13 Dec 2025 14:46:00 +0000 Subject: [PATCH 08/30] Add place holder files --- examples/core/network/tcp/non_blocking/CMakeLists.txt | 9 +++++++++ examples/core/network/tcp/non_blocking/client.cpp | 4 ++++ examples/core/network/tcp/non_blocking/server.cpp | 4 ++++ 3 files changed, 17 insertions(+) create mode 100644 examples/core/network/tcp/non_blocking/CMakeLists.txt create mode 100644 examples/core/network/tcp/non_blocking/client.cpp create mode 100644 examples/core/network/tcp/non_blocking/server.cpp diff --git a/examples/core/network/tcp/non_blocking/CMakeLists.txt b/examples/core/network/tcp/non_blocking/CMakeLists.txt new file mode 100644 index 000000000..7bbd085c7 --- /dev/null +++ b/examples/core/network/tcp/non_blocking/CMakeLists.txt @@ -0,0 +1,9 @@ +morpheus_add_executable(NAME non_blocking_client) +target_include_directories(non_blocking_client PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) +target_sources(non_blocking_client PRIVATE client.cpp) +target_link_libraries(non_blocking_client PRIVATE morpheus::core) + +morpheus_add_executable(NAME non_blocking_server) +target_include_directories(non_blocking_server PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) +target_sources(non_blocking_server PRIVATE server.cpp) +target_link_libraries(non_blocking_server PRIVATE morpheus::core) diff --git a/examples/core/network/tcp/non_blocking/client.cpp b/examples/core/network/tcp/non_blocking/client.cpp new file mode 100644 index 000000000..d3a1f6af4 --- /dev/null +++ b/examples/core/network/tcp/non_blocking/client.cpp @@ -0,0 +1,4 @@ + +int main() +{ +} diff --git a/examples/core/network/tcp/non_blocking/server.cpp b/examples/core/network/tcp/non_blocking/server.cpp new file mode 100644 index 000000000..d3a1f6af4 --- /dev/null +++ b/examples/core/network/tcp/non_blocking/server.cpp @@ -0,0 +1,4 @@ + +int main() +{ +} From 062ae843693315bd4841129445774b31aea990d9 Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sat, 13 Dec 2025 14:47:33 +0000 Subject: [PATCH 09/30] Run pre-commit --- examples/core/network/tcp/non_blocking/client.cpp | 4 +--- examples/core/network/tcp/non_blocking/server.cpp | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/examples/core/network/tcp/non_blocking/client.cpp b/examples/core/network/tcp/non_blocking/client.cpp index d3a1f6af4..e51dccb50 100644 --- a/examples/core/network/tcp/non_blocking/client.cpp +++ b/examples/core/network/tcp/non_blocking/client.cpp @@ -1,4 +1,2 @@ -int main() -{ -} +int main() {} diff --git a/examples/core/network/tcp/non_blocking/server.cpp b/examples/core/network/tcp/non_blocking/server.cpp index d3a1f6af4..e51dccb50 100644 --- a/examples/core/network/tcp/non_blocking/server.cpp +++ b/examples/core/network/tcp/non_blocking/server.cpp @@ -1,4 +1,2 @@ -int main() -{ -} +int main() {} From 73f88cf6a30b63091c470eed26ec9443c2cfd63f Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sat, 13 Dec 2025 14:57:08 +0000 Subject: [PATCH 10/30] Prefer boost::unique_resource over std::unique_ptr --- examples/core/network/tcp/common.hpp | 72 ++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 examples/core/network/tcp/common.hpp diff --git a/examples/core/network/tcp/common.hpp b/examples/core/network/tcp/common.hpp new file mode 100644 index 000000000..51a533174 --- /dev/null +++ b/examples/core/network/tcp/common.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include + +#include + +#include +#include + +#include +#include + +namespace morpheus { + +struct SocketCloser { + using pointer = int; /// Unique ptr should store a file descriptor handle, not a pointer + void operator()(pointer fd) const noexcept { + if (fd >= 0){ + ::close(fd); + } + } +}; + +using Socket = boost::scope::unique_resource; +using SocketAndAddr = std::pair; + +auto createSocket() -> conf::exp::expected { + int s = socket(AF_INET, SOCK_STREAM, 0); + if (s < 0) { + return conf::exp::unexpected(std::error_code(errno, std::system_category())); + } + return Socket(s); +} + +auto createSockAddr(std::string_view address, std::uint16_t port) -> conf::exp::expected { + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + // Convert IPv4 address from text to binary form + if (auto const result = inet_pton(AF_INET, address.data(), &addr.sin_addr); result <= 0) { + return conf::exp::unexpected(std::error_code(errno, std::system_category())); + } + return addr; +} + +auto createConnection(SocketAndAddr sockInfo) -> conf::exp::expected { + auto [sock, serv_addr] = std::move(sockInfo); + if (auto const result = connect(sock.get(), (struct sockaddr *)&serv_addr, sizeof(serv_addr)); result < 0) { + return conf::exp::unexpected(std::error_code(errno, std::system_category())); + } + return std::pair{std::move(sock), std::move(serv_addr)}; +} + +auto sendData(Socket const& sock, std::string_view data) -> conf::exp::expected { + if (auto result = send(sock.get(), data.data(), data.size(), 0); result < 0) { + return conf::exp::unexpected(std::error_code(errno, std::system_category())); + } else { + return static_cast(result); + } +} + +auto receiveData(Socket const& sock) -> conf::exp::expected { + std::array buffer = {0}; + if (auto result = read(sock.get(), buffer.data(), buffer.size()); result < 0) { + return conf::exp::unexpected(std::error_code(errno, std::system_category())); + } else { + return std::string(buffer.data(), static_cast(result)); + } +} + +} // namespace morpheus \ No newline at end of file From c4a99832d8cb1d72505a22a8f7b1ccace4528fe2 Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sat, 13 Dec 2025 14:57:48 +0000 Subject: [PATCH 11/30] Run pre-commit --- examples/core/network/tcp/common.hpp | 54 ++++++++++++++++++---------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/examples/core/network/tcp/common.hpp b/examples/core/network/tcp/common.hpp index 51a533174..5b2ea5c16 100644 --- a/examples/core/network/tcp/common.hpp +++ b/examples/core/network/tcp/common.hpp @@ -5,18 +5,22 @@ #include -#include #include +#include #include #include -namespace morpheus { +namespace morpheus +{ -struct SocketCloser { +struct SocketCloser +{ using pointer = int; /// Unique ptr should store a file descriptor handle, not a pointer - void operator()(pointer fd) const noexcept { - if (fd >= 0){ + void operator()(pointer fd) const noexcept + { + if (fd >= 0) + { ::close(fd); } } @@ -25,48 +29,62 @@ struct SocketCloser { using Socket = boost::scope::unique_resource; using SocketAndAddr = std::pair; -auto createSocket() -> conf::exp::expected { +auto createSocket() -> conf::exp::expected +{ int s = socket(AF_INET, SOCK_STREAM, 0); - if (s < 0) { + if (s < 0) + { return conf::exp::unexpected(std::error_code(errno, std::system_category())); } return Socket(s); } -auto createSockAddr(std::string_view address, std::uint16_t port) -> conf::exp::expected { +auto createSockAddr(std::string_view address, std::uint16_t port) -> conf::exp::expected +{ struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(port); // Convert IPv4 address from text to binary form - if (auto const result = inet_pton(AF_INET, address.data(), &addr.sin_addr); result <= 0) { + if (auto const result = inet_pton(AF_INET, address.data(), &addr.sin_addr); result <= 0) + { return conf::exp::unexpected(std::error_code(errno, std::system_category())); } return addr; } -auto createConnection(SocketAndAddr sockInfo) -> conf::exp::expected { +auto createConnection(SocketAndAddr sockInfo) -> conf::exp::expected +{ auto [sock, serv_addr] = std::move(sockInfo); - if (auto const result = connect(sock.get(), (struct sockaddr *)&serv_addr, sizeof(serv_addr)); result < 0) { + if (auto const result = connect(sock.get(), (struct sockaddr*)&serv_addr, sizeof(serv_addr)); result < 0) + { return conf::exp::unexpected(std::error_code(errno, std::system_category())); } return std::pair{std::move(sock), std::move(serv_addr)}; } -auto sendData(Socket const& sock, std::string_view data) -> conf::exp::expected { - if (auto result = send(sock.get(), data.data(), data.size(), 0); result < 0) { +auto sendData(Socket const& sock, std::string_view data) -> conf::exp::expected +{ + if (auto result = send(sock.get(), data.data(), data.size(), 0); result < 0) + { return conf::exp::unexpected(std::error_code(errno, std::system_category())); - } else { + } + else + { return static_cast(result); } } -auto receiveData(Socket const& sock) -> conf::exp::expected { +auto receiveData(Socket const& sock) -> conf::exp::expected +{ std::array buffer = {0}; - if (auto result = read(sock.get(), buffer.data(), buffer.size()); result < 0) { + if (auto result = read(sock.get(), buffer.data(), buffer.size()); result < 0) + { return conf::exp::unexpected(std::error_code(errno, std::system_category())); - } else { + } + else + { return std::string(buffer.data(), static_cast(result)); } } -} // namespace morpheus \ No newline at end of file +} // namespace morpheus From c5b205c9f8e9bdc6e4a16a16f4729a25cbfb4b2a Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sat, 13 Dec 2025 15:12:50 +0000 Subject: [PATCH 12/30] Remove duplicate file --- examples/core/network/tcp/blocking/common.hpp | 4 +- examples/core/network/tcp/common.hpp | 90 ------------------- 2 files changed, 3 insertions(+), 91 deletions(-) delete mode 100644 examples/core/network/tcp/common.hpp diff --git a/examples/core/network/tcp/blocking/common.hpp b/examples/core/network/tcp/blocking/common.hpp index 65e8dcdd8..5b2ea5c16 100644 --- a/examples/core/network/tcp/blocking/common.hpp +++ b/examples/core/network/tcp/blocking/common.hpp @@ -3,6 +3,8 @@ #include #include +#include + #include #include @@ -24,7 +26,7 @@ struct SocketCloser } }; -using Socket = std::unique_ptr; +using Socket = boost::scope::unique_resource; using SocketAndAddr = std::pair; auto createSocket() -> conf::exp::expected diff --git a/examples/core/network/tcp/common.hpp b/examples/core/network/tcp/common.hpp deleted file mode 100644 index 5b2ea5c16..000000000 --- a/examples/core/network/tcp/common.hpp +++ /dev/null @@ -1,90 +0,0 @@ -#pragma once - -#include -#include - -#include - -#include -#include - -#include -#include - -namespace morpheus -{ - -struct SocketCloser -{ - using pointer = int; /// Unique ptr should store a file descriptor handle, not a pointer - void operator()(pointer fd) const noexcept - { - if (fd >= 0) - { - ::close(fd); - } - } -}; - -using Socket = boost::scope::unique_resource; -using SocketAndAddr = std::pair; - -auto createSocket() -> conf::exp::expected -{ - int s = socket(AF_INET, SOCK_STREAM, 0); - if (s < 0) - { - return conf::exp::unexpected(std::error_code(errno, std::system_category())); - } - return Socket(s); -} - -auto createSockAddr(std::string_view address, std::uint16_t port) -> conf::exp::expected -{ - struct sockaddr_in addr; - addr.sin_family = AF_INET; - addr.sin_port = htons(port); - // Convert IPv4 address from text to binary form - if (auto const result = inet_pton(AF_INET, address.data(), &addr.sin_addr); result <= 0) - { - return conf::exp::unexpected(std::error_code(errno, std::system_category())); - } - return addr; -} - -auto createConnection(SocketAndAddr sockInfo) -> conf::exp::expected -{ - auto [sock, serv_addr] = std::move(sockInfo); - if (auto const result = connect(sock.get(), (struct sockaddr*)&serv_addr, sizeof(serv_addr)); result < 0) - { - return conf::exp::unexpected(std::error_code(errno, std::system_category())); - } - return std::pair{std::move(sock), std::move(serv_addr)}; -} - -auto sendData(Socket const& sock, std::string_view data) -> conf::exp::expected -{ - if (auto result = send(sock.get(), data.data(), data.size(), 0); result < 0) - { - return conf::exp::unexpected(std::error_code(errno, std::system_category())); - } - else - { - return static_cast(result); - } -} - -auto receiveData(Socket const& sock) -> conf::exp::expected -{ - std::array buffer = {0}; - if (auto result = read(sock.get(), buffer.data(), buffer.size()); result < 0) - { - return conf::exp::unexpected(std::error_code(errno, std::system_category())); - } - else - { - return std::string(buffer.data(), static_cast(result)); - } -} - -} // namespace morpheus From 1c4dabea98aa4f1b5229b6411dbadceda9b2e7a5 Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sat, 13 Dec 2025 15:18:03 +0000 Subject: [PATCH 13/30] Remove unused headers --- examples/core/network/tcp/blocking/common.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/core/network/tcp/blocking/common.hpp b/examples/core/network/tcp/blocking/common.hpp index 5b2ea5c16..d53785bb2 100644 --- a/examples/core/network/tcp/blocking/common.hpp +++ b/examples/core/network/tcp/blocking/common.hpp @@ -8,7 +8,6 @@ #include #include -#include #include namespace morpheus From 49abae476680924a62a96976e9bcc0b5f726e27b Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sun, 14 Dec 2025 09:57:03 +0000 Subject: [PATCH 14/30] Remove further unused headers --- examples/core/network/tcp/blocking/client.cpp | 2 -- examples/core/network/tcp/blocking/common.hpp | 2 -- 2 files changed, 4 deletions(-) diff --git a/examples/core/network/tcp/blocking/client.cpp b/examples/core/network/tcp/blocking/client.cpp index 7bd0fc5d3..2aff2ce74 100644 --- a/examples/core/network/tcp/blocking/client.cpp +++ b/examples/core/network/tcp/blocking/client.cpp @@ -5,8 +5,6 @@ #include #include -#include - using namespace morpheus; using namespace morpheus::conf; diff --git a/examples/core/network/tcp/blocking/common.hpp b/examples/core/network/tcp/blocking/common.hpp index d53785bb2..019f18648 100644 --- a/examples/core/network/tcp/blocking/common.hpp +++ b/examples/core/network/tcp/blocking/common.hpp @@ -8,8 +8,6 @@ #include #include -#include - namespace morpheus { From fd222b548dcd1878a782e638aa848b19c62a8288 Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sun, 14 Dec 2025 10:01:15 +0000 Subject: [PATCH 15/30] Include what you use --- examples/core/network/tcp/blocking/common.hpp | 5 +++++ examples/core/network/tcp/blocking/server.cpp | 2 ++ 2 files changed, 7 insertions(+) diff --git a/examples/core/network/tcp/blocking/common.hpp b/examples/core/network/tcp/blocking/common.hpp index 019f18648..f1df48b81 100644 --- a/examples/core/network/tcp/blocking/common.hpp +++ b/examples/core/network/tcp/blocking/common.hpp @@ -8,6 +8,11 @@ #include #include +#include +#include +#include +#include + namespace morpheus { diff --git a/examples/core/network/tcp/blocking/server.cpp b/examples/core/network/tcp/blocking/server.cpp index 19e4b6ab6..fd71c14b4 100644 --- a/examples/core/network/tcp/blocking/server.cpp +++ b/examples/core/network/tcp/blocking/server.cpp @@ -5,6 +5,8 @@ #include #include // for close() +#include + using namespace morpheus; using namespace morpheus::conf; From a5f04390be9d40da6788837fd5e2693667ad09d6 Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sun, 14 Dec 2025 10:20:00 +0000 Subject: [PATCH 16/30] Abstract window/linux headers --- examples/core/network/tcp/blocking/client.cpp | 10 ++++++++-- examples/core/network/tcp/blocking/common.hpp | 10 ++++++++-- examples/core/network/tcp/blocking/server.cpp | 15 +++++++++++++-- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/examples/core/network/tcp/blocking/client.cpp b/examples/core/network/tcp/blocking/client.cpp index 2aff2ce74..30bf11ffb 100644 --- a/examples/core/network/tcp/blocking/client.cpp +++ b/examples/core/network/tcp/blocking/client.cpp @@ -2,8 +2,14 @@ #include #include -#include -#include +#if (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_PC_WINDOWS) + #include + #include +#else + #include + #include + #include +#endif using namespace morpheus; using namespace morpheus::conf; diff --git a/examples/core/network/tcp/blocking/common.hpp b/examples/core/network/tcp/blocking/common.hpp index f1df48b81..3f870681b 100644 --- a/examples/core/network/tcp/blocking/common.hpp +++ b/examples/core/network/tcp/blocking/common.hpp @@ -5,8 +5,14 @@ #include -#include -#include +#if (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_PC_WINDOWS) + #include + #include +#else + #include + #include + #include +#endif #include #include diff --git a/examples/core/network/tcp/blocking/server.cpp b/examples/core/network/tcp/blocking/server.cpp index fd71c14b4..601433010 100644 --- a/examples/core/network/tcp/blocking/server.cpp +++ b/examples/core/network/tcp/blocking/server.cpp @@ -1,11 +1,22 @@ -#include +#include "common.hpp" + +#include #include #include // for sockaddr_in, inet_addr() -#include #include // for close() +#if (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_PC_WINDOWS) + #include + #include +#else + #include + #include + #include +#endif + #include +#include using namespace morpheus; using namespace morpheus::conf; From 4850079d27229c40be96e951ffbcefdcf68d66f4 Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sun, 14 Dec 2025 10:24:21 +0000 Subject: [PATCH 17/30] Run pre-commit --- examples/core/network/tcp/blocking/server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/core/network/tcp/blocking/server.cpp b/examples/core/network/tcp/blocking/server.cpp index 601433010..afbd8f52d 100644 --- a/examples/core/network/tcp/blocking/server.cpp +++ b/examples/core/network/tcp/blocking/server.cpp @@ -4,7 +4,7 @@ #include #include // for sockaddr_in, inet_addr() -#include // for close() +#include // for close() #if (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_PC_WINDOWS) #include From 15c6c1ecaec44da3fd65522e6e1e7983c6fb7e89 Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sun, 14 Dec 2025 10:41:38 +0000 Subject: [PATCH 18/30] Split socket types across os --- examples/core/network/tcp/blocking/common.hpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/examples/core/network/tcp/blocking/common.hpp b/examples/core/network/tcp/blocking/common.hpp index 3f870681b..a3701626b 100644 --- a/examples/core/network/tcp/blocking/common.hpp +++ b/examples/core/network/tcp/blocking/common.hpp @@ -22,15 +22,30 @@ namespace morpheus { +#if (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_PC_WINDOWS) +using SocketHandle = ::SOCKET; +constexpr SocketHandle invalid_socket = INVALID_SOCKET; +#else +using SocketHandle = int; +constexpr SocketHandle invalid_socket = -1; +#endif + struct SocketCloser { - using pointer = int; /// Unique ptr should store a file descriptor handle, not a pointer + using pointer = SocketHandle; /// Unique ptr should store a file descriptor handle, not a pointer void operator()(pointer fd) const noexcept { +#if (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_PC_WINDOWS) + if (fd != INVALID_SOCKET) + { + ::closesocket(fd); + } +#else if (fd >= 0) { ::close(fd); } +#endif } }; From 62b9a7752fb9c82c8cfb1ce0b59b8ba85544f2bc Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sun, 14 Dec 2025 13:11:54 +0000 Subject: [PATCH 19/30] Abstract sockers errors --- examples/core/network/tcp/blocking/client.cpp | 4 +++- examples/core/network/tcp/blocking/common.hpp | 10 ++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/examples/core/network/tcp/blocking/client.cpp b/examples/core/network/tcp/blocking/client.cpp index 30bf11ffb..339c3b1f8 100644 --- a/examples/core/network/tcp/blocking/client.cpp +++ b/examples/core/network/tcp/blocking/client.cpp @@ -1,4 +1,6 @@ -#include +#include "common.hpp" + +#include #include #include diff --git a/examples/core/network/tcp/blocking/common.hpp b/examples/core/network/tcp/blocking/common.hpp index a3701626b..ea008225a 100644 --- a/examples/core/network/tcp/blocking/common.hpp +++ b/examples/core/network/tcp/blocking/common.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -24,10 +25,10 @@ namespace morpheus #if (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_PC_WINDOWS) using SocketHandle = ::SOCKET; -constexpr SocketHandle invalid_socket = INVALID_SOCKET; +constexpr SocketHandle invalidSocket = INVALID_SOCKET; #else using SocketHandle = int; -constexpr SocketHandle invalid_socket = -1; +constexpr SocketHandle invalidSocket = -1; #endif struct SocketCloser @@ -36,7 +37,7 @@ struct SocketCloser void operator()(pointer fd) const noexcept { #if (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_PC_WINDOWS) - if (fd != INVALID_SOCKET) + if (fd != invalidSocket) { ::closesocket(fd); } @@ -59,7 +60,8 @@ auto createSocket() -> conf::exp::expected { return conf::exp::unexpected(std::error_code(errno, std::system_category())); } - return Socket(s); + return Socket(s, SocketCloser{}); + } auto createSockAddr(std::string_view address, std::uint16_t port) -> conf::exp::expected From 1451568c20cd82d6c95d1ecd3a4ccd7f4a099654 Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sun, 14 Dec 2025 13:12:44 +0000 Subject: [PATCH 20/30] Run pre-commit --- examples/core/network/tcp/blocking/common.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/core/network/tcp/blocking/common.hpp b/examples/core/network/tcp/blocking/common.hpp index ea008225a..e1c5b16fa 100644 --- a/examples/core/network/tcp/blocking/common.hpp +++ b/examples/core/network/tcp/blocking/common.hpp @@ -61,7 +61,6 @@ auto createSocket() -> conf::exp::expected return conf::exp::unexpected(std::error_code(errno, std::system_category())); } return Socket(s, SocketCloser{}); - } auto createSockAddr(std::string_view address, std::uint16_t port) -> conf::exp::expected From 877db6a0724fbd3f7979ccdfd30eb2aa7db8e8e1 Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sun, 14 Dec 2025 14:36:00 +0000 Subject: [PATCH 21/30] Abstract error handling across os --- examples/core/network/tcp/blocking/common.hpp | 43 +++++++++++++------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/examples/core/network/tcp/blocking/common.hpp b/examples/core/network/tcp/blocking/common.hpp index e1c5b16fa..5cb6050f6 100644 --- a/examples/core/network/tcp/blocking/common.hpp +++ b/examples/core/network/tcp/blocking/common.hpp @@ -31,34 +31,51 @@ using SocketHandle = int; constexpr SocketHandle invalidSocket = -1; #endif +bool socketError(SocketHandle const sock); + struct SocketCloser { using pointer = SocketHandle; /// Unique ptr should store a file descriptor handle, not a pointer void operator()(pointer fd) const noexcept { -#if (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_PC_WINDOWS) - if (fd != invalidSocket) + if (!socketError(fd)) { +#if (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_PC_WINDOWS) ::closesocket(fd); - } #else - if (fd >= 0) - { ::close(fd); - } #endif + } } }; using Socket = boost::scope::unique_resource; using SocketAndAddr = std::pair; +bool socketError(SocketHandle const sock) +{ +#if (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_PC_WINDOWS) + return (sock == invalidSocket); +#else + return (sock < 0); +#endif +} + +auto socketErrorCode() -> std::error_code +{ +#if (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_PC_WINDOWS) + return std::error_code(WSAGetLastError(), std::system_category()); +#else + return std::error_code(errno, std::system_category()); +#endif +} + auto createSocket() -> conf::exp::expected { - int s = socket(AF_INET, SOCK_STREAM, 0); - if (s < 0) + auto const s = socket(AF_INET, SOCK_STREAM, 0); + if (socketError(s)) { - return conf::exp::unexpected(std::error_code(errno, std::system_category())); + return conf::exp::unexpected(socketErrorCode()); } return Socket(s, SocketCloser{}); } @@ -71,7 +88,7 @@ auto createSockAddr(std::string_view address, std::uint16_t port) -> conf::exp:: // Convert IPv4 address from text to binary form if (auto const result = inet_pton(AF_INET, address.data(), &addr.sin_addr); result <= 0) { - return conf::exp::unexpected(std::error_code(errno, std::system_category())); + return conf::exp::unexpected(socketErrorCode()); } return addr; } @@ -81,7 +98,7 @@ auto createConnection(SocketAndAddr sockInfo) -> conf::exp::expected conf::exp::expected< { if (auto result = send(sock.get(), data.data(), data.size(), 0); result < 0) { - return conf::exp::unexpected(std::error_code(errno, std::system_category())); + return conf::exp::unexpected(socketErrorCode()); } else { @@ -103,7 +120,7 @@ auto receiveData(Socket const& sock) -> conf::exp::expected buffer = {0}; if (auto result = read(sock.get(), buffer.data(), buffer.size()); result < 0) { - return conf::exp::unexpected(std::error_code(errno, std::system_category())); + return conf::exp::unexpected(socketErrorCode()); } else { From 005ba81f0ecc7d49c8df890e044a5c5caa898423 Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sun, 14 Dec 2025 18:15:04 +0000 Subject: [PATCH 22/30] Wrap read and write functions --- examples/core/network/tcp/blocking/common.hpp | 26 ++++++++++++++++--- examples/core/network/tcp/blocking/server.cpp | 4 +-- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/examples/core/network/tcp/blocking/common.hpp b/examples/core/network/tcp/blocking/common.hpp index 5cb6050f6..fb7e91289 100644 --- a/examples/core/network/tcp/blocking/common.hpp +++ b/examples/core/network/tcp/blocking/common.hpp @@ -55,9 +55,9 @@ using SocketAndAddr = std::pair; bool socketError(SocketHandle const sock) { #if (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_PC_WINDOWS) - return (sock == invalidSocket); + return (sock == invalidSocket); #else - return (sock < 0); + return (sock < 0); #endif } @@ -103,9 +103,27 @@ auto createConnection(SocketAndAddr sockInfo) -> conf::exp::expected(buf), static_cast(len), 0); +#else + return ::read(s, buf, len); +#endif +} + +inline auto socketWrite(SocketHandle s, void const* buf, size_t len) noexcept +{ +#if (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_PC_WINDOWS) + return ::send(s, static_cast(buf), static_cast(len), 0); +#else + return ::write(s, buf, len); +#endif +} + auto sendData(Socket const& sock, std::string_view data) -> conf::exp::expected { - if (auto result = send(sock.get(), data.data(), data.size(), 0); result < 0) + if (auto result = socketWrite(sock.get(), data.data(), data.size()); result < 0) { return conf::exp::unexpected(socketErrorCode()); } @@ -118,7 +136,7 @@ auto sendData(Socket const& sock, std::string_view data) -> conf::exp::expected< auto receiveData(Socket const& sock) -> conf::exp::expected { std::array buffer = {0}; - if (auto result = read(sock.get(), buffer.data(), buffer.size()); result < 0) + if (auto result = socketRead(sock.get(), buffer.data(), buffer.size()); result < 0) { return conf::exp::unexpected(socketErrorCode()); } diff --git a/examples/core/network/tcp/blocking/server.cpp b/examples/core/network/tcp/blocking/server.cpp index afbd8f52d..6d6025b6e 100644 --- a/examples/core/network/tcp/blocking/server.cpp +++ b/examples/core/network/tcp/blocking/server.cpp @@ -119,11 +119,11 @@ int main() print::print("Server listening on port {}...\n", PORT); std::array buffer = {0}; - read(client_fd.get(), buffer.data(), buffer.size()); + socketRead(client_fd.get(), buffer.data(), buffer.size()); print::print("Received: {}\n", std::string_view(buffer.data(), buffer.size())); std::string_view const reply = "Hello from server!"; - send(client_fd.get(), reply.data(), reply.size(), 0); + socketWrite(client_fd.get(), reply.data(), reply.size()); return EXIT_SUCCESS; } From aab3711d525e3edc78b4ad02d8f678a4a8b94bc1 Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sun, 14 Dec 2025 18:23:13 +0000 Subject: [PATCH 23/30] Scope OS headers --- examples/core/network/tcp/blocking/server.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/examples/core/network/tcp/blocking/server.cpp b/examples/core/network/tcp/blocking/server.cpp index 6d6025b6e..d5b4fad13 100644 --- a/examples/core/network/tcp/blocking/server.cpp +++ b/examples/core/network/tcp/blocking/server.cpp @@ -3,16 +3,13 @@ #include #include -#include // for sockaddr_in, inet_addr() -#include // for close() - #if (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_PC_WINDOWS) #include #include #else - #include + #include // for sockaddr_in, inet_addr() #include - #include + #include // for close() #endif #include From 61b493a91e40056b1f07dbb39d5f4677e8e2e6f8 Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sun, 14 Dec 2025 20:07:35 +0000 Subject: [PATCH 24/30] General accept method --- examples/core/network/tcp/blocking/server.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/core/network/tcp/blocking/server.cpp b/examples/core/network/tcp/blocking/server.cpp index d5b4fad13..7a38ce70d 100644 --- a/examples/core/network/tcp/blocking/server.cpp +++ b/examples/core/network/tcp/blocking/server.cpp @@ -31,19 +31,18 @@ auto acceptConnection(SocketAndAddr const& socketInfo) -> exp::expected exp::expected From ab17953c6c54757006413024f8406c3700ceccdf Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Wed, 17 Dec 2025 17:19:52 +0000 Subject: [PATCH 25/30] Add Winsocks library for windows --- examples/core/network/tcp/blocking/CMakeLists.txt | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/examples/core/network/tcp/blocking/CMakeLists.txt b/examples/core/network/tcp/blocking/CMakeLists.txt index 8a1e90def..761eaba71 100644 --- a/examples/core/network/tcp/blocking/CMakeLists.txt +++ b/examples/core/network/tcp/blocking/CMakeLists.txt @@ -1,9 +1,17 @@ morpheus_add_executable(NAME client) target_include_directories(client PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_sources(client PRIVATE client.cpp) -target_link_libraries(client PRIVATE morpheus::core) +target_link_libraries(client + PRIVATE + morpheus::core + $<$:ws2_32> +) morpheus_add_executable(NAME server) target_include_directories(server PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) target_sources(server PRIVATE server.cpp) -target_link_libraries(server PRIVATE morpheus::core) +target_link_libraries(server + PRIVATE + morpheus::core + $<$:ws2_32> +) From 457216311a0b8b7118d6d1c4ee2514082a6ec7c2 Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sat, 20 Dec 2025 09:01:07 +0000 Subject: [PATCH 26/30] Move to span access --- examples/core/network/tcp/blocking/client.cpp | 4 +-- examples/core/network/tcp/blocking/common.hpp | 26 ++++++++++++------- examples/core/network/tcp/blocking/server.cpp | 16 ++++++------ 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/examples/core/network/tcp/blocking/client.cpp b/examples/core/network/tcp/blocking/client.cpp index 339c3b1f8..3b26de98b 100644 --- a/examples/core/network/tcp/blocking/client.cpp +++ b/examples/core/network/tcp/blocking/client.cpp @@ -42,13 +42,13 @@ int main() auto const receiveMsg = [](SocketAndAddr sockInfo) -> exp::expected { auto const& [sock, serv_addr] = sockInfo; - if (auto const result = receiveData(sock)) + if (auto const result = receiveData(sock); !result) { return exp::unexpected(result.error()); } else { - print::print("Received {} bytes from server: {}\n", result->size(), *result); + print::print("Received {} bytes from server: {}\n", result->size(), std::string_view{reinterpret_cast(result->data()), result->size()}); } return sockInfo; }; diff --git a/examples/core/network/tcp/blocking/common.hpp b/examples/core/network/tcp/blocking/common.hpp index fb7e91289..3009f8600 100644 --- a/examples/core/network/tcp/blocking/common.hpp +++ b/examples/core/network/tcp/blocking/common.hpp @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -103,27 +104,27 @@ auto createConnection(SocketAndAddr sockInfo) -> conf::exp::expected buffer) noexcept { #if (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_PC_WINDOWS) return ::recv(s, static_cast(buf), static_cast(len), 0); #else - return ::read(s, buf, len); + return ::read(s, buffer.data(), buffer.size()); #endif } -inline auto socketWrite(SocketHandle s, void const* buf, size_t len) noexcept +inline auto socketWrite(SocketHandle s, std::span const buffer) noexcept { #if (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_PC_WINDOWS) return ::send(s, static_cast(buf), static_cast(len), 0); #else - return ::write(s, buf, len); + return ::write(s, buffer.data(), buffer.size()); #endif } -auto sendData(Socket const& sock, std::string_view data) -> conf::exp::expected +auto sendData(Socket const& sock, std::span const data) -> conf::exp::expected { - if (auto result = socketWrite(sock.get(), data.data(), data.size()); result < 0) + if (auto result = socketWrite(sock.get(), data); result < 0) { return conf::exp::unexpected(socketErrorCode()); } @@ -133,16 +134,21 @@ auto sendData(Socket const& sock, std::string_view data) -> conf::exp::expected< } } -auto receiveData(Socket const& sock) -> conf::exp::expected +auto sendData(Socket const& sock, std::string_view const str) -> conf::exp::expected { - std::array buffer = {0}; - if (auto result = socketRead(sock.get(), buffer.data(), buffer.size()); result < 0) + return sendData(sock, std::span{reinterpret_cast(str.data()), str.size()}); +} + +auto receiveData(Socket const& sock) -> conf::exp::expected, std::error_code> +{ + std::array buffer = {}; + if (auto result = socketRead(sock.get(), buffer); result < 0) { return conf::exp::unexpected(socketErrorCode()); } else { - return std::string(buffer.data(), static_cast(result)); + return std::vector(buffer.begin(), buffer.begin() + static_cast(result)); } } diff --git a/examples/core/network/tcp/blocking/server.cpp b/examples/core/network/tcp/blocking/server.cpp index 7a38ce70d..944177d97 100644 --- a/examples/core/network/tcp/blocking/server.cpp +++ b/examples/core/network/tcp/blocking/server.cpp @@ -33,7 +33,7 @@ auto acceptConnection(SocketAndAddr const& socketInfo) -> exp::expected buffer = {0}; - socketRead(client_fd.get(), buffer.data(), buffer.size()); - print::print("Received: {}\n", std::string_view(buffer.data(), buffer.size())); + std::array buffer = {}; + auto size = socketRead(client_fd.get(), buffer); + print::print("Received: {}\n", std::string_view(reinterpret_cast(buffer.data()), size)); std::string_view const reply = "Hello from server!"; - socketWrite(client_fd.get(), reply.data(), reply.size()); + socketWrite(client_fd.get(), {reinterpret_cast(reply.data()), reply.size()}); return EXIT_SUCCESS; } From 325e45c5288ac10f25829b76dfe7169b12400b83 Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sat, 20 Dec 2025 09:37:45 +0000 Subject: [PATCH 27/30] Add vector header. Bytes to char --- examples/core/network/tcp/blocking/common.hpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/core/network/tcp/blocking/common.hpp b/examples/core/network/tcp/blocking/common.hpp index 3009f8600..e83dec4db 100644 --- a/examples/core/network/tcp/blocking/common.hpp +++ b/examples/core/network/tcp/blocking/common.hpp @@ -20,6 +20,7 @@ #include #include #include +#include namespace morpheus { @@ -107,7 +108,7 @@ auto createConnection(SocketAndAddr sockInfo) -> conf::exp::expected buffer) noexcept { #if (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_PC_WINDOWS) - return ::recv(s, static_cast(buf), static_cast(len), 0); + return ::recv(s, reinterpret_cast(buffer.data()), static_cast(buffer.size()), 0); #else return ::read(s, buffer.data(), buffer.size()); #endif @@ -116,7 +117,7 @@ inline auto socketRead(SocketHandle s, std::span buffer) noexcept inline auto socketWrite(SocketHandle s, std::span const buffer) noexcept { #if (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_PC_WINDOWS) - return ::send(s, static_cast(buf), static_cast(len), 0); + return ::send(s, reinterpret_cast(buffer.data()), static_cast(buffer.size()), 0); #else return ::write(s, buffer.data(), buffer.size()); #endif From 3f416978dace855b742879e17653e8ffa22a9efa Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sun, 21 Dec 2025 08:08:48 +0000 Subject: [PATCH 28/30] Initialise Winsocks --- examples/core/network/tcp/blocking/client.cpp | 2 ++ examples/core/network/tcp/blocking/common.hpp | 15 +++++++++++++++ examples/core/network/tcp/blocking/server.cpp | 11 +++++++++-- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/examples/core/network/tcp/blocking/client.cpp b/examples/core/network/tcp/blocking/client.cpp index 3b26de98b..4d9a685b3 100644 --- a/examples/core/network/tcp/blocking/client.cpp +++ b/examples/core/network/tcp/blocking/client.cpp @@ -18,6 +18,8 @@ using namespace morpheus::conf; int main() { + SocketSystem initialiseSockers; + auto const combineSockAndAddr = [](Socket sock) { return createSockAddr("127.0.0.1", 8080) diff --git a/examples/core/network/tcp/blocking/common.hpp b/examples/core/network/tcp/blocking/common.hpp index e83dec4db..c8166bb0a 100644 --- a/examples/core/network/tcp/blocking/common.hpp +++ b/examples/core/network/tcp/blocking/common.hpp @@ -33,6 +33,21 @@ using SocketHandle = int; constexpr SocketHandle invalidSocket = -1; #endif +struct SocketSystem { +#if (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_PC_WINDOWS) + WSADATA wsaData; + SocketSystem() { + if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) + throw std::runtime_error("WSAStartup failed"); + } + ~SocketSystem() { WSACleanup(); } +#else + SocketSystem() = default; + ~SocketSystem() = default; +#endif +}; + + bool socketError(SocketHandle const sock); struct SocketCloser diff --git a/examples/core/network/tcp/blocking/server.cpp b/examples/core/network/tcp/blocking/server.cpp index 944177d97..7c67b8de7 100644 --- a/examples/core/network/tcp/blocking/server.cpp +++ b/examples/core/network/tcp/blocking/server.cpp @@ -43,13 +43,20 @@ auto acceptConnection(SocketAndAddr const& socketInfo) -> exp::expected exp::expected { // Allow reuse of the address/port - int opt = 1; - if (auto result = setsockopt(sock.get(), SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)); result < 0) + int const opt = 1; +#if (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_PC_WINDOWS) + int const optName = SO_EXCLUSIVEADDRUSE; +#else + int const optName = SO_REUSEADDR | SO_REUSEPORT; +#endif + if (auto result = setsockopt(sock.get(), SOL_SOCKET, optName, reinterpret_cast(&opt), sizeof(opt)); result < 0) { return conf::exp::unexpected(socketErrorCode()); } From c1f67401f818cdc5e68c927ce04eee5610c4a865 Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sun, 21 Dec 2025 08:26:58 +0000 Subject: [PATCH 29/30] Run pre-commit --- examples/core/network/tcp/blocking/client.cpp | 3 ++- examples/core/network/tcp/blocking/common.hpp | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/examples/core/network/tcp/blocking/client.cpp b/examples/core/network/tcp/blocking/client.cpp index 4d9a685b3..0b4f9c131 100644 --- a/examples/core/network/tcp/blocking/client.cpp +++ b/examples/core/network/tcp/blocking/client.cpp @@ -50,7 +50,8 @@ int main() } else { - print::print("Received {} bytes from server: {}\n", result->size(), std::string_view{reinterpret_cast(result->data()), result->size()}); + print::print( + "Received {} bytes from server: {}\n", result->size(), std::string_view{reinterpret_cast(result->data()), result->size()}); } return sockInfo; }; diff --git a/examples/core/network/tcp/blocking/common.hpp b/examples/core/network/tcp/blocking/common.hpp index c8166bb0a..6f451f5ae 100644 --- a/examples/core/network/tcp/blocking/common.hpp +++ b/examples/core/network/tcp/blocking/common.hpp @@ -33,10 +33,12 @@ using SocketHandle = int; constexpr SocketHandle invalidSocket = -1; #endif -struct SocketSystem { +struct SocketSystem +{ #if (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_PC_WINDOWS) WSADATA wsaData; - SocketSystem() { + SocketSystem() + { if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) throw std::runtime_error("WSAStartup failed"); } @@ -47,7 +49,6 @@ struct SocketSystem { #endif }; - bool socketError(SocketHandle const sock); struct SocketCloser From 7be963fdd51e9b7a8990e034d4776001c277f414 Mon Sep 17 00:00:00 2001 From: Antony Peacock Date: Sun, 21 Dec 2025 09:15:40 +0000 Subject: [PATCH 30/30] Server to full monadic sytle --- examples/core/network/tcp/blocking/server.cpp | 44 ++++++++++++++----- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/examples/core/network/tcp/blocking/server.cpp b/examples/core/network/tcp/blocking/server.cpp index 7c67b8de7..b025f968b 100644 --- a/examples/core/network/tcp/blocking/server.cpp +++ b/examples/core/network/tcp/blocking/server.cpp @@ -104,12 +104,43 @@ int main() } }; + auto receiveMessage = + [](std::tuple&& serverAndClient) -> exp::expected, std::error_code> + { + auto const data = receiveData(std::get<2>(serverAndClient)) + .and_then( + [](auto const& data) -> exp::expected, std::error_code> + { + print::print("Received: {}\n", std::string_view(reinterpret_cast(data.data()), data.size())); + return data; + }); + if (!data) + { + return exp::unexpected(data.error()); + } + return std::move(serverAndClient); + }; + + auto sendMessage = + [](std::tuple&& serverAndClient) -> exp::expected, std::error_code> + { + auto&& [server_fd, serv_addr, client_fd] = std::move(serverAndClient); + auto const sent = sendData(client_fd, "Hello from server!"); + if (!sent) + { + return exp::unexpected(sent.error()); + } + return std::tuple{std::move(server_fd), std::move(serv_addr), std::move(client_fd)}; + }; + auto sockAddrClient = createSocket() .and_then(setSockOptions) .and_then(combineSockAndAddr) .and_then(bindAddress) .and_then(listenOnSocket) - .and_then(acceptConnectionAndFwdSocketInfo); + .and_then(acceptConnectionAndFwdSocketInfo) + .and_then(receiveMessage) + .and_then(sendMessage); if (!sockAddrClient) { @@ -117,16 +148,5 @@ int main() return EXIT_FAILURE; } - auto const& [server_fd, serv_addr, client_fd] = *sockAddrClient; - - print::print("Server listening on port {}...\n", PORT); - - std::array buffer = {}; - auto size = socketRead(client_fd.get(), buffer); - print::print("Received: {}\n", std::string_view(reinterpret_cast(buffer.data()), size)); - - std::string_view const reply = "Hello from server!"; - socketWrite(client_fd.get(), {reinterpret_cast(reply.data()), reply.size()}); - return EXIT_SUCCESS; }