diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..78c9c0b --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "subprojects/libevent"] + path = subprojects/libevent + url = https://github.com/libevent/libevent diff --git a/meson.build b/meson.build index 67e8fb3..4486b98 100644 --- a/meson.build +++ b/meson.build @@ -1,4 +1,4 @@ -project('FeatherMC', ['c', 'cpp'], version : '0.0', meson_version : '>= 0.49', default_options : [ +project('FeatherMC', ['c', 'cpp'], version : '0.0', meson_version : '>= 0.55', default_options : [ 'warning_level=2', ]) @@ -14,8 +14,28 @@ endif threads_dep = dependency('threads') +cmake = import('cmake') + +libevent_vars = cmake.subproject_options() +libevent_vars.add_cmake_defines({ + 'EVENT__DISABLE_OPENSSL' : true, + 'EVENT__DISABLE_MBEDTLS' : true, + 'EVENT__DISABLE_BENCHMARK' : true, + 'EVENT__DISABLE_TESTS' : true, + 'EVENT__DISABLE_REGRESS' : true, + 'EVENT__DISABLE_SAMPLES' : true, + 'EVENT__LIBRARY_TYPE' : 'STATIC', +}) + +libevent_proj = cmake.subproject('libevent', options : libevent_vars) +libevent_dep = libevent_proj.dependency('event_static') + +feather_deps = [ threads_dep, libevent_dep ] if feather_platform == 'windows' - ws2_32_dep = feather_compiler.find_library('ws2_32') + ws2_32_dep = feather_compiler.find_library('ws2_32') + iphlpapi_dep = feather_compiler.find_library('iphlpapi') + + feather_deps += [ ws2_32_dep, iphlpapi_dep ] endif subdir('src') diff --git a/src/DedicatedServer.cpp b/src/DedicatedServer.cpp index fd849cd..182f087 100644 --- a/src/DedicatedServer.cpp +++ b/src/DedicatedServer.cpp @@ -3,8 +3,12 @@ namespace Feather { DedicatedServer::DedicatedServer(uint16_t port) - : m_socket(port) + : m_listener(port) { + while (1) + { + + } } DedicatedServer::~DedicatedServer() diff --git a/src/DedicatedServer.h b/src/DedicatedServer.h index 572707e..772c298 100644 --- a/src/DedicatedServer.h +++ b/src/DedicatedServer.h @@ -1,6 +1,6 @@ #pragma once -#include "util/SocketUtil.h" +#include "network/TCPListener.h" namespace Feather { @@ -10,6 +10,6 @@ namespace Feather DedicatedServer(uint16_t port); ~DedicatedServer(); private: - Sockets::TCPSocket m_socket; + Network::TCPListener m_listener; }; } diff --git a/src/Main.cpp b/src/Main.cpp index 14e81e4..d24c38e 100644 --- a/src/Main.cpp +++ b/src/Main.cpp @@ -1,5 +1,6 @@ #include #include "config/ServerProperties.h" +#include "DedicatedServer.h" using namespace Feather; @@ -9,5 +10,7 @@ int main() properties.Save(); printf("Starting server on port %d\n", properties.serverPort.GetValue()); + auto server = DedicatedServer(properties.serverPort.GetValue()); + return 1; } diff --git a/src/meson.build b/src/meson.build index 492a157..8bd1c1b 100644 --- a/src/meson.build +++ b/src/meson.build @@ -2,24 +2,15 @@ feather_src = [ 'Main.cpp', 'DedicatedServer.cpp', + 'network/NetworkManager.cpp', + 'network/TCPListener.cpp', + 'util/StringUtil.cpp', - 'util/SocketUtil.cpp', 'config/Properties.cpp', 'config/ServerProperties.cpp', ] -if feather_platform == 'windows' - feather_deps = [ - threads_dep, - ws2_32_dep, - ] -else - feather_deps = [ - threads_dep, - ] -endif - executable('FeatherMC', feather_src, dependencies : feather_deps, include_directories : include_directories('.'), diff --git a/src/network/NetworkManager.cpp b/src/network/NetworkManager.cpp new file mode 100644 index 0000000..75e8e46 --- /dev/null +++ b/src/network/NetworkManager.cpp @@ -0,0 +1,59 @@ +#include "NetworkManager.h" + +#include +#include + +namespace Feather::Network +{ + namespace + { + void LogCallback(int severity, const char* msg) + { + printf("libevent: %s\n", msg); + } + } + + NetworkManager::NetworkManager() + { +#ifdef _WIN32 + WSADATA wsa_data = {}; + WSAStartup(MAKEWORD(2, 2), &wsa_data); +#endif + + event_set_log_callback(LogCallback); + +#if defined(EVTHREAD_USE_WINDOWS_THREADS_IMPLEMENTED) + evthread_use_windows_threads(); +#elif defined(EVTHREAD_USE_PTHREADS_IMPLEMENTED) + evthread_use_pthreads(); +#else +# error No threading implemented for EVTHREAD +#endif + m_eventBase = event_base_new(); + m_eventThread = std::thread([]() { NetworkManager::Instance().EventLoop(); }); + } + + NetworkManager::~NetworkManager() { + event_base_loopbreak(m_eventBase); + m_eventThread.join(); + + event_base_free(m_eventBase); + libevent_global_shutdown(); + +#ifdef _WIN32 + WSACleanup(); +#endif + } + + static NetworkManager s_networkManager; + + NetworkManager& NetworkManager::Instance() + { + return s_networkManager; + } + + void NetworkManager::EventLoop() + { + event_base_loop(m_eventBase, EVLOOP_NO_EXIT_ON_EMPTY); + } +} \ No newline at end of file diff --git a/src/network/NetworkManager.h b/src/network/NetworkManager.h new file mode 100644 index 0000000..2763b32 --- /dev/null +++ b/src/network/NetworkManager.h @@ -0,0 +1,26 @@ +#pragma once +#include + +struct event_base; + +namespace Feather::Network +{ + class NetworkManager + { + public: + NetworkManager(); + ~NetworkManager(); + + static NetworkManager& Instance(); + + void EventLoop(); + + inline event_base* GetEventBase() const { return m_eventBase; } + + private: + event_base* m_eventBase; + + std::thread m_eventThread; + }; + +} \ No newline at end of file diff --git a/src/network/TCPListener.cpp b/src/network/TCPListener.cpp new file mode 100644 index 0000000..1540aee --- /dev/null +++ b/src/network/TCPListener.cpp @@ -0,0 +1,142 @@ +#include "TCPListener.h" +#include "NetworkManager.h" + +#include +#include + +namespace Feather::Network +{ + class TCPSocket + { + public: + TCPSocket() + { + m_socket = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); + + // Can't create IPv6 socket? Create an IPv4 one. + if (!(m_ipv6 = IsValid())) + m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + } + + ~TCPSocket() + { + Close(); + } + + bool IsIPV6() const { return m_ipv6; } + + bool MarkReusable() + { + return evutil_make_listen_socket_reuseable(m_socket) == 0; + } + + bool MarkDualBind() + { + constexpr uint32_t zero = 0; + return setsockopt(m_socket, IPPROTO_IPV6, IPV6_V6ONLY, + reinterpret_cast(&zero), + sizeof(zero)) == 0; + } + + bool Bind(uint16_t port) + { + sockaddr_in6 name_ipv6 = {}; + name_ipv6.sin6_family = AF_INET6; + name_ipv6.sin6_port = ntohs(port); + + sockaddr_in name_ipv4 = {}; + name_ipv4.sin_family = AF_INET; + name_ipv4.sin_port = ntohs(port); + + return m_ipv6 + ? BindGeneric(name_ipv6) + : BindGeneric(name_ipv4); + } + + bool MarkNonBlocking() + { + return evutil_make_socket_nonblocking(m_socket) == 0; + } + + bool Close() + { + if (!IsValid()) + return true; + + return evutil_closesocket(m_socket) == 0; + } + + template + evconnlistener* Listen(evconnlistener_cb callback, T* self) + { + if (listen(m_socket, SOMAXCONN) != 0) + return nullptr; + + return evconnlistener_new( + NetworkManager::Instance().GetEventBase(), + callback, reinterpret_cast(self), + LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, + 0, + m_socket); + } + + bool IsValid() const { return m_socket != EVUTIL_INVALID_SOCKET; } + + private: + + template + bool BindGeneric(const T& name) + { + return bind(m_socket, reinterpret_cast(&name), sizeof(name)) == 0; + } + + evutil_socket_t m_socket; + bool m_ipv6; + }; + + namespace + { + void TCPListenerCallback(evconnlistener* evListener, evutil_socket_t socket, sockaddr* addr, int len, void* self) + { + TCPListener* listener = static_cast(self); + + listener->Accept(); + } + } + + TCPListener::TCPListener(uint16_t port) + { + // Setup the listen socket. + auto socket = std::make_unique(); + + if (!socket->IsValid()) + return; + + if (!socket->MarkReusable()) + return; + + if (socket->IsIPV6() && !socket->MarkDualBind()) + return; + + if (!socket->Bind(port)) + return; + + if (!socket->MarkNonBlocking()) + return; + + if (!socket->Listen(TCPListenerCallback, this)) + return; + + m_socket = std::move(socket); + } + + TCPListener::~TCPListener() + { + + } + + void TCPListener::Accept() + { + printf("Frog"); + } +} \ No newline at end of file diff --git a/src/network/TCPListener.h b/src/network/TCPListener.h new file mode 100644 index 0000000..2461266 --- /dev/null +++ b/src/network/TCPListener.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include + +namespace Feather::Network +{ + class TCPSocket; + + class TCPListener + { + public: + TCPListener(uint16_t port); + ~TCPListener(); + + void Accept(); + + private: + std::unique_ptr m_socket; + }; + + +} \ No newline at end of file diff --git a/src/util/SocketUtil.cpp b/src/util/SocketUtil.cpp deleted file mode 100644 index 02eb336..0000000 --- a/src/util/SocketUtil.cpp +++ /dev/null @@ -1,172 +0,0 @@ -#include "SocketUtil.h" - -#ifdef _WIN32 -# include -# include -#else -# include -# include -# include -# include -#endif - -namespace Feather::Sockets -{ - class TCPSocketHandle - { - public: - TCPSocketHandle() - { - m_socket = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); - - // Can't create IPv6 socket? Create an IPv4 one. - if (!(m_ipv6 = IsValid())) - m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); - } - - ~TCPSocketHandle() - { - Close(); - } - - bool IsIPV6() const { return m_ipv6; } - - bool MarkReusable() - { - constexpr uint32_t one = 1; - return setsockopt(m_socket, SOL_SOCKET, SO_REUSEADDR, - reinterpret_cast(&one), - socklen_t(sizeof(one))) == 0; - } - - bool MarkDualBind() - { - constexpr uint32_t zero = 0; - return setsockopt(m_socket, IPPROTO_IPV6, IPV6_V6ONLY, - reinterpret_cast(&zero), - sizeof(zero)) == 0; - } - - bool Bind(uint16_t port) - { - sockaddr_in6 name_ipv6 = {}; - name_ipv6.sin6_family = AF_INET6; - name_ipv6.sin6_port = ntohs(port); - - sockaddr_in name_ipv4 = {}; - name_ipv4.sin_family = AF_INET; - name_ipv4.sin_port = ntohs(port); - - return m_ipv6 - ? BindGeneric(name_ipv6) - : BindGeneric(name_ipv4); - } - - bool Listen() - { - return listen(m_socket, SOMAXCONN) == 0; - } - -#ifdef _WIN32 - bool IsValid() const { return m_socket != INVALID_SOCKET; } - - bool MarkNonBlocking() - { - unsigned long one = 1; - return ioctlsocket(m_socket, FIONBIO, &one) != SOCKET_ERROR; - } - - bool Close() - { - return shutdown(m_socket, SD_BOTH) == 0 && closesocket(m_socket) == 0; - } - - static bool InitSocketSystem() - { - WSADATA wsa_data; - return WSAStartup(MAKEWORD(1, 1), &wsa_data) == 0; - } - - static bool CloseSocketSystem() - { - return WSACleanup() == 0; - } - - using SocketType = SOCKET; -#else - bool IsValid() const { return m_socket >= 0; } - - bool MarkNonBlocking() - { - return fcntl(fd, F_SETFL, O_NONBLOCK) != -1; - } - - bool Close() - { - return shutdown(m_socket, SHUT_RDWR) == 0 && close(m_socket) == 0; - } - - static bool InitSocketSystem() - { - return true; - } - - static bool CloseSocketSystem() - { - return true; - } - - using SocketType = int; -#endif - - private: - - template - bool BindGeneric(const T& name) - { - return bind(m_socket, reinterpret_cast(&name), sizeof(name)) == 0; - } - - SocketType m_socket; - bool m_ipv6; - }; - - TCPSocket::TCPSocket(uint16_t port) - { - auto socket = std::make_unique(); - - if (!socket->IsValid()) - return; - - if (!socket->MarkReusable()) - return; - - if (socket->IsIPV6() && !socket->MarkDualBind()) - return; - - if (!socket->Bind(port)) - return; - - if (socket->Listen()) - return; - - m_socket = std::move(socket); - } - - TCPSocket::~TCPSocket() - { - - } - - namespace - { - class SocketInitializer - { - public: - SocketInitializer() { TCPSocketHandle::InitSocketSystem(); } - ~SocketInitializer() { TCPSocketHandle::CloseSocketSystem(); } - }; - - static SocketInitializer s_socketInitializer; - } -} \ No newline at end of file diff --git a/src/util/SocketUtil.h b/src/util/SocketUtil.h deleted file mode 100644 index 0b96a73..0000000 --- a/src/util/SocketUtil.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include -#include - -namespace Feather::Sockets -{ - class TCPSocketHandle; - - class TCPSocket - { - public: - TCPSocket(uint16_t port); - ~TCPSocket(); - - inline bool IsValid() const { return m_socket != nullptr; } - - private: - std::unique_ptr m_socket; - }; -} \ No newline at end of file diff --git a/subprojects/libevent b/subprojects/libevent new file mode 160000 index 0000000..948ad30 --- /dev/null +++ b/subprojects/libevent @@ -0,0 +1 @@ +Subproject commit 948ad3043590a36db6c8299bf0fb00ac3e1235ef