Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
b780598
Add client server example with raw sockets
Twon Nov 9, 2025
6136a68
Mordernise client
Twon Nov 9, 2025
a610926
Remove unnecessary move
Twon Nov 9, 2025
96c59dc
Client/Server now using modern C++
Twon Nov 10, 2025
08a610f
Store errors as error codes
Twon Nov 10, 2025
872fff8
Move to blocking folder
Twon Dec 13, 2025
33d0366
Run pre-commit
Twon Dec 13, 2025
69c9772
Add place holder files
Twon Dec 13, 2025
062ae84
Run pre-commit
Twon Dec 13, 2025
73f88cf
Prefer boost::unique_resource over std::unique_ptr
Twon Dec 13, 2025
c4a9983
Run pre-commit
Twon Dec 13, 2025
c5b205c
Remove duplicate file
Twon Dec 13, 2025
1c4dabe
Remove unused headers
Twon Dec 13, 2025
49abae4
Remove further unused headers
Twon Dec 14, 2025
fd222b5
Include what you use
Twon Dec 14, 2025
a5f0439
Abstract window/linux headers
Twon Dec 14, 2025
4850079
Run pre-commit
Twon Dec 14, 2025
15c6c1e
Split socket types across os
Twon Dec 14, 2025
62b9a77
Abstract sockers errors
Twon Dec 14, 2025
1451568
Run pre-commit
Twon Dec 14, 2025
877db6a
Abstract error handling across os
Twon Dec 14, 2025
005ba81
Wrap read and write functions
Twon Dec 14, 2025
aab3711
Scope OS headers
Twon Dec 14, 2025
61b493a
General accept method
Twon Dec 14, 2025
ab17953
Add Winsocks library for windows
Twon Dec 17, 2025
4572163
Move to span access
Twon Dec 20, 2025
325e45c
Add vector header. Bytes to char
Twon Dec 20, 2025
3f41697
Initialise Winsocks
Twon Dec 21, 2025
c1f6740
Run pre-commit
Twon Dec 21, 2025
7be963f
Server to full monadic sytle
Twon Dec 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
add_subdirectory(core)
add_subdirectory(gfx)
1 change: 1 addition & 0 deletions examples/core/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add_subdirectory(network)
1 change: 1 addition & 0 deletions examples/core/network/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add_subdirectory(tcp)
2 changes: 2 additions & 0 deletions examples/core/network/tcp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
add_subdirectory(blocking)
add_subdirectory(non_blocking)
17 changes: 17 additions & 0 deletions examples/core/network/tcp/blocking/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +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
$<$<PLATFORM_ID:Windows>: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
$<$<PLATFORM_ID:Windows>:ws2_32>
)
70 changes: 70 additions & 0 deletions examples/core/network/tcp/blocking/client.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#include "common.hpp"

#include <morpheus/core/base/platform.hpp>
#include <morpheus/core/conformance/expected.hpp>
#include <morpheus/core/conformance/print.hpp>

#if (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_PC_WINDOWS)
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
#endif

using namespace morpheus;
using namespace morpheus::conf;

int main()
{
SocketSystem initialiseSockers;

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::pair<Socket, struct sockaddr_in>, std::error_code>
{ return std::pair{std::move(sock), std::move(addr)}; });
};

auto const sendMsg = [](SocketAndAddr sockInfo) -> exp::expected<SocketAndAddr, std::error_code>
{
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<SocketAndAddr, std::error_code>
{
auto const& [sock, serv_addr] = sockInfo;
if (auto const result = receiveData(sock); !result)
{
return exp::unexpected(result.error());
}
else
{
print::print(
"Received {} bytes from server: {}\n", result->size(), std::string_view{reinterpret_cast<char const*>(result->data()), result->size()});
}
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;
}
}
172 changes: 172 additions & 0 deletions examples/core/network/tcp/blocking/common.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
#pragma once

#include <morpheus/core/base/platform.hpp>
#include <morpheus/core/conformance/expected.hpp>
#include <morpheus/core/conformance/print.hpp>

#include <boost/scope/unique_resource.hpp>

#if (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_PC_WINDOWS)
#include <winsock2.h>
#include <ws2tcpip.h>
#else
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
#endif

#include <array>
#include <cstdint>
#include <span>
#include <string>
#include <system_error>
#include <vector>

namespace morpheus
{

#if (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_PC_WINDOWS)
using SocketHandle = ::SOCKET;
constexpr SocketHandle invalidSocket = INVALID_SOCKET;
#else
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
{
using pointer = SocketHandle; /// Unique ptr should store a file descriptor handle, not a pointer
void operator()(pointer fd) const noexcept
{
if (!socketError(fd))
{
#if (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_PC_WINDOWS)
::closesocket(fd);
#else
::close(fd);
#endif
}
}
};

using Socket = boost::scope::unique_resource<int, SocketCloser>;
using SocketAndAddr = std::pair<Socket, struct sockaddr_in>;

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<Socket, std::error_code>
{
auto const s = socket(AF_INET, SOCK_STREAM, 0);
if (socketError(s))
{
return conf::exp::unexpected(socketErrorCode());
}
return Socket(s, SocketCloser{});
}

auto createSockAddr(std::string_view address, std::uint16_t port) -> conf::exp::expected<struct sockaddr_in, std::error_code>
{
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(socketErrorCode());
}
return addr;
}

auto createConnection(SocketAndAddr sockInfo) -> conf::exp::expected<SocketAndAddr, std::error_code>
{
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(socketErrorCode());
}
return std::pair{std::move(sock), std::move(serv_addr)};
}

inline auto socketRead(SocketHandle s, std::span<std::byte> buffer) noexcept
{
#if (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_PC_WINDOWS)
return ::recv(s, reinterpret_cast<char*>(buffer.data()), static_cast<int>(buffer.size()), 0);
#else
return ::read(s, buffer.data(), buffer.size());
#endif
}

inline auto socketWrite(SocketHandle s, std::span<std::byte const> const buffer) noexcept
{
#if (MORPHEUS_BUILD_PLATFORM == MORPHEUS_TARGET_PLATFORM_PC_WINDOWS)
return ::send(s, reinterpret_cast<const char*>(buffer.data()), static_cast<int>(buffer.size()), 0);
#else
return ::write(s, buffer.data(), buffer.size());
#endif
}

auto sendData(Socket const& sock, std::span<std::byte const> const data) -> conf::exp::expected<std::size_t, std::error_code>
{
if (auto result = socketWrite(sock.get(), data); result < 0)
{
return conf::exp::unexpected(socketErrorCode());
}
else
{
return static_cast<std::size_t>(result);
}
}

auto sendData(Socket const& sock, std::string_view const str) -> conf::exp::expected<std::size_t, std::error_code>
{
return sendData(sock, std::span<std::byte const>{reinterpret_cast<std::byte const*>(str.data()), str.size()});
}

auto receiveData(Socket const& sock) -> conf::exp::expected<std::vector<std::byte>, std::error_code>
{
std::array<std::byte, 1024> buffer = {};
if (auto result = socketRead(sock.get(), buffer); result < 0)
{
return conf::exp::unexpected(socketErrorCode());
}
else
{
return std::vector<std::byte>(buffer.begin(), buffer.begin() + static_cast<std::size_t>(result));
}
}

} // namespace morpheus
Loading
Loading