From 466a80f02b4fda4b1c5514e4e0aa15652fe3d9df Mon Sep 17 00:00:00 2001 From: HappyTanuki Date: Tue, 27 May 2025 21:52:21 +0900 Subject: [PATCH] =?UTF-8?q?tcp/udp=20=EC=B6=94=EA=B0=80,=20todo:iocp=20ope?= =?UTF-8?q?nssl=20=EC=A7=80=EC=9B=90=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8=ED=95=98=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 7 +- CMakeSettings.json | 27 ++ Client/CMakeLists.txt | 32 +- Client/src/vulkan/instance_and_extensions.cpp | 2 +- impl/session/session.cpp | 10 + impl/socket/address.cpp | 105 +++++ impl/socket/iocp.cpp | 69 ++++ impl/socket/tcp_socket.cpp | 42 ++ impl/socket/udp_socket.cpp | 112 ++++++ impl/socket/wsa_manager.cpp | 20 + impl/transport/transport.cpp | 10 + impl/utils/generate_id.cpp | 32 ++ impl/utils/log.cpp | 27 ++ {Client/src => impl}/utils/thread_pool.cpp | 0 {Client/include => include}/precomp.h | 0 include/session/session.h | 22 + include/socket/address.h | 27 ++ include/socket/iocp.h | 376 ++++++++++++++++++ include/socket/tcp_socket.h | 18 + include/socket/udp_socket.h | 45 +++ include/socket/wsa_manager.h | 20 + include/transport/transport.h | 18 + include/utils/log.h | 9 + include/utils/snowflake.h | 32 ++ .../include => include}/utils/thread_pool.h | 0 25 files changed, 1056 insertions(+), 6 deletions(-) create mode 100644 CMakeSettings.json create mode 100644 impl/session/session.cpp create mode 100644 impl/socket/address.cpp create mode 100644 impl/socket/iocp.cpp create mode 100644 impl/socket/tcp_socket.cpp create mode 100644 impl/socket/udp_socket.cpp create mode 100644 impl/socket/wsa_manager.cpp create mode 100644 impl/transport/transport.cpp create mode 100644 impl/utils/generate_id.cpp create mode 100644 impl/utils/log.cpp rename {Client/src => impl}/utils/thread_pool.cpp (100%) rename {Client/include => include}/precomp.h (100%) create mode 100644 include/session/session.h create mode 100644 include/socket/address.h create mode 100644 include/socket/iocp.h create mode 100644 include/socket/tcp_socket.h create mode 100644 include/socket/udp_socket.h create mode 100644 include/socket/wsa_manager.h create mode 100644 include/transport/transport.h create mode 100644 include/utils/log.h create mode 100644 include/utils/snowflake.h rename {Client/include => include}/utils/thread_pool.h (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d2c560..ad387d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,8 +1,7 @@ cmake_minimum_required(VERSION 3.5) set(PROJECT_NAME "Asteroid") - -set(CMAKE_BUILD_TYPE Debug) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE) @@ -23,5 +22,9 @@ if(WIN32) endif() endif() +file(GLOB_RECURSE RootProjectSources CONFIGURE_DEPENDS + "impl/*.cpp" +) + add_subdirectory(Client) # add_subdirectory(Server) \ No newline at end of file diff --git a/CMakeSettings.json b/CMakeSettings.json new file mode 100644 index 0000000..0c5fbf9 --- /dev/null +++ b/CMakeSettings.json @@ -0,0 +1,27 @@ +{ + "configurations": [ + { + "name": "x64-Debug", + "generator": "Ninja", + "configurationType": "Debug", + "inheritEnvironments": [ "msvc_x64_x64" ], + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "" + }, + { + "name": "x64-Release", + "generator": "Ninja", + "configurationType": "RelWithDebInfo", + "buildRoot": "${projectDir}\\out\\build\\${name}", + "installRoot": "${projectDir}\\out\\install\\${name}", + "cmakeCommandArgs": "", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "msvc_x64_x64" ], + "variables": [] + } + ] +} \ No newline at end of file diff --git a/Client/CMakeLists.txt b/Client/CMakeLists.txt index ea574ef..65d0a80 100644 --- a/Client/CMakeLists.txt +++ b/Client/CMakeLists.txt @@ -52,7 +52,7 @@ file(GLOB_RECURSE Sources CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp" ) -add_executable(${PROJECT_NAME} ${Sources}) +add_executable(${PROJECT_NAME} ${RootProjectSources} ${Sources}) target_link_libraries(${PROJECT_NAME} PRIVATE Vulkan::Vulkan) target_link_libraries(${PROJECT_NAME} PRIVATE glm) target_link_libraries(${PROJECT_NAME} PRIVATE glfw) @@ -62,10 +62,22 @@ target_link_libraries(${PROJECT_NAME} PRIVATE assimp::assimp) target_link_libraries(${PROJECT_NAME} PRIVATE OpenSSL::Crypto) target_link_libraries(${PROJECT_NAME} PRIVATE OpenSSL::SSL) -target_include_directories(${PROJECT_NAME} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include") +if(WIN32) + target_link_libraries(${PROJECT_NAME} PRIVATE ws2_32) +endif() +if(UNIX AND NOT APPLE AND CMAKE_BUILD_TYPE STREQUAL "Release") + set_target_properties(${PROJECT_NAME} PROPERTIES + BUILD_WITH_INSTALL_RPATH TRUE + INSTALL_RPATH "$ORIGIN" + SKIP_BUILD_RPATH FALSE + BUILD_RPATH "$ORIGIN" + ) +endif() + +target_include_directories(${PROJECT_NAME} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../include" "${CMAKE_CURRENT_SOURCE_DIR}/include") target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20) -target_precompile_headers(${PROJECT_NAME} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include/precomp.h") +target_precompile_headers(${PROJECT_NAME} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../include/precomp.h") file(GLOB_RECURSE ShaderSources CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/shaders/*.vert" @@ -80,4 +92,18 @@ add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD "${CMAKE_CURRENT_SOURCE_DIR}/assets" "${CMAKE_CURRENT_BINARY_DIR}/assets" COMMENT "Copying assets to build directory" +) + +add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_if_different + "$" + "$" + COMMENT "Copying spdlog DLL/so to output directory" +) + +add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + "$" + "$" + COMMENT "Copying spdlog library files and symlinks to output directory" ) \ No newline at end of file diff --git a/Client/src/vulkan/instance_and_extensions.cpp b/Client/src/vulkan/instance_and_extensions.cpp index 5806859..e08e9ae 100644 --- a/Client/src/vulkan/instance_and_extensions.cpp +++ b/Client/src/vulkan/instance_and_extensions.cpp @@ -70,7 +70,7 @@ void Graphics::CreateInstance() { VkApplicationInfo app_info = {}; app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; app_info.pNext = nullptr; - app_info.pApplicationName = "Udemy Course"; + app_info.pApplicationName = "Asteroid"; app_info.applicationVersion = VK_MAKE_API_VERSION(0, 0, 0, 0); app_info.pEngineName = "VEng"; app_info.engineVersion = VK_MAKE_API_VERSION(0, 1, 0, 0); diff --git a/impl/session/session.cpp b/impl/session/session.cpp new file mode 100644 index 0000000..b9745b8 --- /dev/null +++ b/impl/session/session.cpp @@ -0,0 +1,10 @@ +#include "session/session.h" + +#include "utils/thread_pool.h" + +namespace happytanuki { + +Session::Session(utils::ThreadPool* tp, SessionProtocol proto) { +} + +} // namespace happytanuki diff --git a/impl/socket/address.cpp b/impl/socket/address.cpp new file mode 100644 index 0000000..bf8bbdb --- /dev/null +++ b/impl/socket/address.cpp @@ -0,0 +1,105 @@ +#include "socket/address.h" + +#include + +#include "precomp.h" + +namespace Socket { + +Address::Address() { zeroFill(); } + +Address::Address(int type, gsl::czstring presentationAddr, std::uint16_t port) { + set(type, presentationAddr, port); +} + +void Address::zeroFill() { memset(&addr_in6, 0, sizeof(addr_in6)); } + +void Address::set(int type, gsl::czstring presentationAddr, + std::uint16_t port) { + zeroFill(); + + if (type == AF_INET) { + addr_in.sin_family = AF_INET; + ::inet_pton(AF_INET, presentationAddr, &addr_in.sin_addr); + addr_in.sin_port = htons(port); + length = sizeof(sockaddr_in); + } else if (type == AF_INET6) { + addr_in6.sin6_family = AF_INET6; + ::inet_pton(AF_INET6, presentationAddr, &addr_in6.sin6_addr); + addr_in6.sin6_port = htons(port); + length = sizeof(sockaddr_in6); + } +} + +void Address::set(int type, in_addr_t addr, std::uint16_t port) { + zeroFill(); + + if (type == AF_INET) { + addr_in.sin_family = AF_INET; + addr_in.sin_addr.s_addr = htonl(addr); + addr_in.sin_port = htons(port); + length = sizeof(sockaddr_in); + } +} + +void Address::set(int type, in_addr addr, std::uint16_t port) { + zeroFill(); + + if (type == AF_INET) { + addr_in.sin_family = AF_INET; + addr_in.sin_addr = addr; + addr_in.sin_port = htons(port); + length = sizeof(sockaddr_in); + } +} + +void Address::set(int type, in6_addr addr, std::uint16_t port) { + zeroFill(); + + if (type == AF_INET6) { + addr_in6.sin6_family = AF_INET6; + addr_in6.sin6_addr = addr; + addr_in6.sin6_port = htons(port); + length = sizeof(sockaddr_in6); + } +} + +void Address::setType(int type) { + zeroFill(); + + if (type == AF_INET) + length = sizeof(sockaddr_in); + else if (type == AF_INET6) + length = sizeof(sockaddr_in6); +} + +Address::operator std::string() { + std::optional port = getPort(); + + if (!port) return std::string(); + + if (length == sizeof(addr_in)) { + char addrStr[INET_ADDRSTRLEN]; + ::inet_ntop(AF_INET, &addr_in.sin_addr, addrStr, sizeof(addrStr)); + + return std::format("{}:{}", addrStr, port.value()); + } else if (length == sizeof(addr_in6)) { + char addrStr[INET6_ADDRSTRLEN]; + ::inet_ntop(AF_INET6, &addr_in6.sin6_addr, addrStr, sizeof(addrStr)); + + return std::format("{}:{}", addrStr, port.value()); + } + + return std::string(); +} + +std::uint16_t Address::getPort() { + if (length == sizeof(addr_in)) + return ntohs(addr_in.sin_port); + else if (length == sizeof(addr_in6)) + return ntohs(addr_in6.sin6_port); + else + return 0; +} + +} // namespace Chattr diff --git a/impl/socket/iocp.cpp b/impl/socket/iocp.cpp new file mode 100644 index 0000000..3ee9bb3 --- /dev/null +++ b/impl/socket/iocp.cpp @@ -0,0 +1,69 @@ +#include "socket/iocp.h" + +#include "utils/thread_pool.h" + +namespace Socket { + +IOCP::IOCP() {} + +IOCP::~IOCP() { destruct(); } + +void IOCP::destruct() { +#ifdef __linux__ + uint64_t u = 1; + ::write(epollDetroyerFd, &u, sizeof(uint64_t)); + close(epollfd_); +#endif +} + +void IOCP::registerSocket(IOCPPASSINDATA* data) { + data->event = IOCPEVENT::READ; +#ifdef _WIN32 + HANDLE returnData = ::CreateIoCompletionPort( + (HANDLE)data->socket->sock, completionPort_, data->socket->sock, 0); + if (returnData == 0) completionPort_ = returnData; +#elif __linux__ + int flags = ::fcntl(data->socket->sock, F_GETFL); + flags |= O_NONBLOCK; + fcntl(data->socket->sock, F_SETFL, flags); + + struct epoll_event ev; + ev.events = EPOLLIN | EPOLLONESHOT; + data->sendQueue = std::make_shared>(); + ev.data.ptr = data; + int rc = epoll_ctl(epollfd_, EPOLL_CTL_ADD, data->socket->sock, &ev); + if (rc < 0) log::critical("epoll_ctl()"); +#endif +} + +int IOCP::recv(IOCPPASSINDATA* data, int bufferCount) { + data->event = IOCPEVENT::READ; +#ifdef _WIN32 + DWORD recvbytes = 0, flags = 0; + return ::WSARecv(data->socket->sock, &data->wsabuf, bufferCount, &recvbytes, + &flags, &data->overlapped, NULL); +#elif __linux__ + struct epoll_event ev; + ev.events = EPOLLIN | EPOLLONESHOT; + ev.data.ptr = data; + return ::epoll_ctl(epollfd_, EPOLL_CTL_MOD, data->socket->sock, &ev); +#endif +} + +int IOCP::send(IOCPPASSINDATA* data, int bufferCount, + int __flags) { + data->event = IOCPEVENT::WRITE; +#ifdef _WIN32 + DWORD sendbytes = 0; + return ::WSASend(data->socket->sock, &data->wsabuf, bufferCount, &sendbytes, + __flags, &data->overlapped, NULL); +#elif __linux__ + struct epoll_event ev; + ev.events = EPOLLIN | EPOLLOUT | EPOLLONESHOT; + ev.data.ptr = data; + data->sendQueue->push(data); + return ::epoll_ctl(epollfd_, EPOLL_CTL_MOD, data->socket->sock, &ev); +#endif +} + +} // namespace Chattr diff --git a/impl/socket/tcp_socket.cpp b/impl/socket/tcp_socket.cpp new file mode 100644 index 0000000..c5f6632 --- /dev/null +++ b/impl/socket/tcp_socket.cpp @@ -0,0 +1,42 @@ +#include "socket/tcp_socket.h" + +namespace Socket { + +int TCPSocket::init(int domain) { return init(domain, SOCK_STREAM, 0); } + +int TCPSocket::listen(int __n) { + int retVal = ::listen(sock, __n); + if (retVal == INVALID_SOCKET) spdlog::error("listen()"); + return retVal; +} + +void TCPSocket::accept(TCPSocket &newSock, Address &__addr) { + newSock.set(::accept(sock, &__addr.addr, &__addr.length), domain); + memcpy(&newSock.remoteAddr, &__addr, sizeof(Address)); + if (newSock.sock == INVALID_SOCKET) spdlog::error("accept()"); +} + +int TCPSocket::connect(Address &serveraddr) { + int retVal = + ::connect(sock, (struct sockaddr *)&serveraddr.addr, serveraddr.length); + memcpy(&remoteAddr, &serveraddr, sizeof(Address)); + if (retVal == INVALID_SOCKET) spdlog::error("connect()"); + return retVal; +} + +int TCPSocket::recv(void *__restrict __buf, size_t __n, int __flags) { + int retVal = ::recv(sock, (char *)__buf, __n, __flags); + if (retVal == SOCKET_ERROR) { + if (errno == EAGAIN || errno == EWOULDBLOCK) return retVal; + spdlog::error("recv()"); + } + return retVal; +} + +int TCPSocket::send(const void *__buf, size_t __n, int __flags) { + int retVal = ::send(sock, (char *)__buf, __n, __flags); + if (retVal == SOCKET_ERROR) spdlog::error("send()"); + return retVal; +} + +} // namespace Socket diff --git a/impl/socket/udp_socket.cpp b/impl/socket/udp_socket.cpp new file mode 100644 index 0000000..7f16a97 --- /dev/null +++ b/impl/socket/udp_socket.cpp @@ -0,0 +1,112 @@ +#include "socket/udp_socket.h" + +namespace Socket { + +UDPSocket::UDPSocket(int domain, int type, int protocol) { + init(domain, type, protocol); +} + +UDPSocket::~UDPSocket() { destruct(); } + +int UDPSocket::init(int domain, int type, int protocol) { + this->domain = domain; + + sock = ::socket(domain, type, protocol); + if (sock == INVALID_SOCKET) spdlog::critical("socket()"); + + valid_ = true; + + return 0; +} + +void UDPSocket::destruct() { + if (!valid_) return; +#ifdef _WIN32 + ::closesocket(sock); +#elif __linux__ + ::close(sock); +#endif + valid_ = false; +} + +UDPSocket::operator SOCKET() { + if (valid_) { + valid_ = false; + return sock; + } + spdlog::critical("No valid socket created."); + return INVALID_SOCKET; +} + +void UDPSocket::set(const SOCKET __sock, int __domain) { + if (__sock == INVALID_SOCKET) { + spdlog::critical("socket()"); + std::exit(EXIT_FAILURE); + } + + destruct(); + + sock = __sock; + valid_ = true; +}; + +int UDPSocket::setsockopt(int level, int optname, const char* optval, + int optlen) { + return ::setsockopt(sock, level, optname, optval, optlen); +} + +int UDPSocket::bind(Address __addr) { + bindAddr = __addr; + int retVal = ::bind(sock, &__addr.addr, __addr.length); + if (retVal == INVALID_SOCKET) { + spdlog::critical("bind()"); + std::exit(EXIT_FAILURE); + } + return retVal; +} + +int UDPSocket::recvfrom(void* __restrict __buf, size_t __n, int __flags, + struct Address& __addr) { + std::lock_guard lock(readMutex); + int retVal = ::recvfrom(sock, (char*)__buf, __n, __flags, &__addr.addr, + &__addr.length); + if (retVal == SOCKET_ERROR) spdlog::error("recvfrom()"); + return retVal; +} + +int UDPSocket::sendto(const void* __buf, size_t __n, int __flags, + struct Address __addr) { + std::lock_guard lock(writeMutex); + int retVal = + ::sendto(sock, (char*)__buf, __n, __flags, &__addr.addr, __addr.length); + if (retVal == SOCKET_ERROR) spdlog::error("sendto()"); + return retVal; +} + +UDPSocket::UDPSocket(const UDPSocket& other_) { + memcpy(this, &other_, sizeof(UDPSocket)); + valid_ = false; +} + +UDPSocket::UDPSocket(UDPSocket&& other_) noexcept { + other_.valid_ = false; + memcpy(this, &other_, sizeof(UDPSocket)); + valid_ = true; +} + +UDPSocket& UDPSocket::operator=(const UDPSocket& other_) { + memcpy(this, &other_, sizeof(UDPSocket)); + valid_ = false; + + return *this; +} + +UDPSocket& UDPSocket::operator=(UDPSocket&& other_) noexcept { + other_.valid_ = false; + memcpy(this, &other_, sizeof(UDPSocket)); + valid_ = true; + + return *this; +} + +} // namespace Socket diff --git a/impl/socket/wsa_manager.cpp b/impl/socket/wsa_manager.cpp new file mode 100644 index 0000000..2291cff --- /dev/null +++ b/impl/socket/wsa_manager.cpp @@ -0,0 +1,20 @@ +#include "socket/wsa_manager.h" + +namespace Socket { +WSAManager::WSAManager() { +#ifdef _WIN32 + WSADATA wsa; + if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) { + spdlog::critical("WSAStartup()"); + std::exit(EXIT_FAILURE); + } +#endif +} + +WSAManager::~WSAManager() { +#ifdef _WIN32 + WSACleanup(); +#endif +} + +} diff --git a/impl/transport/transport.cpp b/impl/transport/transport.cpp new file mode 100644 index 0000000..7d18b3b --- /dev/null +++ b/impl/transport/transport.cpp @@ -0,0 +1,10 @@ +#include "transport/transport.h" + +namespace happytanuki { +Transport::Transport(utils::ThreadPool* tp) { tp_ = tp; } + +void Transport::Send() {} + +void Transport::Recv() {} + +} // namespace happytanuki diff --git a/impl/utils/generate_id.cpp b/impl/utils/generate_id.cpp new file mode 100644 index 0000000..fe69015 --- /dev/null +++ b/impl/utils/generate_id.cpp @@ -0,0 +1,32 @@ +#include "utils/snowflake.h" + +#include + +namespace utils { + +static struct EpochInitializer { + EpochInitializer() { EPOCH = std::chrono::system_clock::now(); } + std::chrono::system_clock::time_point EPOCH; +} epochInitializer; + +Snowflake GenerateID() { + static std::mutex snowflakeGenerateMutex_; + + std::lock_guard lock(snowflakeGenerateMutex_); + + std::size_t tid = + std::hash{}(std::this_thread::get_id()); + + thread_local static int sequence = 0; + Snowflake id = {}; + + auto timestamp = std::chrono::duration_cast( + std::chrono::system_clock::now() - epochInitializer.EPOCH); + id.timestamp = timestamp.count(); + id.instance = tid; + id.sequence = sequence++; + + return id; +} + +}; // namespace Chattr diff --git a/impl/utils/log.cpp b/impl/utils/log.cpp new file mode 100644 index 0000000..7fff2ae --- /dev/null +++ b/impl/utils/log.cpp @@ -0,0 +1,27 @@ +#include "utils/log.h" + +#include "spdlog/sinks/rotating_file_sink.h" +#include "spdlog/sinks/stdout_color_sinks.h" +#ifdef _WIN32 +#include "spdlog/sinks/msvc_sink.h" +#endif + +namespace utils { + +void setDefaultLogger(spdlog::level::level_enum logLevel, + gsl::czstring logFileName, std::uint32_t logFileSize, + std::uint32_t logFileCount) { + std::vector sinks; + sinks.push_back(std::make_shared()); + sinks.push_back(std::make_shared( + logFileName, logFileSize, logFileCount, false)); +#ifdef _WIN32 + sinks.push_back(std::make_shared()); +#endif + auto chatteringLogger = std::make_shared( + "Chattering Logger", begin(sinks), end(sinks)); + chatteringLogger->set_level(logLevel); + spdlog::set_default_logger(chatteringLogger); +} + +} // namespace Chattr::log diff --git a/Client/src/utils/thread_pool.cpp b/impl/utils/thread_pool.cpp similarity index 100% rename from Client/src/utils/thread_pool.cpp rename to impl/utils/thread_pool.cpp diff --git a/Client/include/precomp.h b/include/precomp.h similarity index 100% rename from Client/include/precomp.h rename to include/precomp.h diff --git a/include/session/session.h b/include/session/session.h new file mode 100644 index 0000000..8bd973b --- /dev/null +++ b/include/session/session.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include + +#include "socket/iocp.h" +#include "utils/thread_pool.h" + +namespace happytanuki { + +enum class SessionProtocol { UDP, TCP, TLS, QUIC }; + +class Session { + public: + Session(utils::ThreadPool* tp, SessionProtocol proto); + + private: + Socket::IOCP iocp_; + utils::ThreadPool* tp_; +}; +} // namespace happytanuki diff --git a/include/socket/address.h b/include/socket/address.h new file mode 100644 index 0000000..61c153f --- /dev/null +++ b/include/socket/address.h @@ -0,0 +1,27 @@ +#pragma once + +namespace Socket { + +struct Address { + Address(); + Address(int type, gsl::czstring presentationAddr, std::uint16_t port); + + void zeroFill(); + void set(int type, gsl::czstring presentationAddr, std::uint16_t port); + void set(int type, in_addr_t addr, std::uint16_t port); + void set(int type, in_addr addr, std::uint16_t port); + void set(int type, in6_addr addr, std::uint16_t port); + void setType(int type); + + operator std::string(); + std::uint16_t getPort(); + + union { + struct sockaddr addr; + struct sockaddr_in addr_in; + struct sockaddr_in6 addr_in6; + }; + socklen_t length; +}; + +} // namespace Chattr diff --git a/include/socket/iocp.h b/include/socket/iocp.h new file mode 100644 index 0000000..16d6fe2 --- /dev/null +++ b/include/socket/iocp.h @@ -0,0 +1,376 @@ +#pragma once +#include +#include +#include + +#include "socket/tcp_socket.h" +#include "socket/wsa_manager.h" +#include "utils/thread_pool.h" + +#ifdef __linux__ + +typedef struct _OVERLAPPED { + char dummy; +} OVERLAPPED; + +typedef struct __WSABUF { + std::uint32_t len; + char* buf; +} WSABUF; + +#endif + +namespace Socket { + +class IOCP; + +enum class IOCPEVENT { QUIT, READ, WRITE }; + +struct IOCPPASSINDATA { + OVERLAPPED overlapped; + IOCPEVENT event; + std::shared_ptr socket; + std::uint32_t recvbytes; + std::uint32_t sendbytes; + std::uint32_t transferredbytes; + WSABUF wsabuf; + std::uint32_t bufsize; + IOCP* IOCPInstance; +#ifdef __linux__ + std::shared_ptr> sendQueue; +#endif + + IOCPPASSINDATA(std::uint32_t bufsize) { + std::memset(&overlapped, 0, sizeof(overlapped)); + event = IOCPEVENT::QUIT; + socket = nullptr; + recvbytes = 0; + sendbytes = 0; + transferredbytes = 0; + this->bufsize = bufsize; + IOCPInstance = nullptr; + + wsabuf.buf = new char[bufsize]; + } + IOCPPASSINDATA(const IOCPPASSINDATA& other) + : event(other.event), + socket(other.socket), + transferredbytes(other.transferredbytes), + bufsize(other.bufsize), + IOCPInstance(other.IOCPInstance) +#ifdef __linux__ + , + sendQueue(other.sendQueue) +#endif + { + std::memset(&overlapped, 0, sizeof(overlapped)); + recvbytes = 0; + sendbytes = 0; + wsabuf.buf = new char[bufsize]; + std::memcpy(wsabuf.buf, other.wsabuf.buf, other.wsabuf.len); + } + + ~IOCPPASSINDATA() { + if (wsabuf.buf != nullptr) delete wsabuf.buf; + } + + IOCPPASSINDATA& operator=(const IOCPPASSINDATA& other) { + if (this != &other) { + std::memset(&overlapped, 0, sizeof(overlapped)); + event = other.event; + socket = other.socket; + recvbytes = 0; + sendbytes = 0; + transferredbytes = other.transferredbytes; + bufsize = other.bufsize; + IOCPInstance = other.IOCPInstance; +#ifdef __linux__ + sendQueue = other.sendQueue; +#endif + wsabuf.buf = new char[bufsize]; + std::memcpy(wsabuf.buf, other.wsabuf.buf, other.wsabuf.len); + } + return *this; + } +}; + +class IOCP { + public: +#ifdef _WIN32 + static void iocpWather( + utils::ThreadPool* threadPool, HANDLE completionPort_, + std::function callback) { + IOCPPASSINDATA* data; + SOCKET sock; + DWORD cbTransfrred; + int retVal = GetQueuedCompletionStatus(completionPort_, &cbTransfrred, + (PULONG_PTR)&sock, + (LPOVERLAPPED*)&data, INFINITE); + if (retVal == 0 || cbTransfrred == 0) { + data->event = IOCPEVENT::QUIT; + spdlog::debug("Disconnected. [{}]", + (std::string)(data->socket->remoteAddr)); + } else { + data->transferredbytes = cbTransfrred; + } + threadPool->enqueueJob(callback, data); + threadPool->enqueueJob(iocpWather, completionPort_, callback); + }; +#elif __linux__ + static void socketReader( + ThreadPool* threadPool, epoll_event event, int epollfd, + std::function callback) { + pthread_t tid = pthread_self(); + + if (event.data.ptr == nullptr) { + spdlog::error("invalid call on {}", tid); + return; + } + + IOCPPASSINDATA* rootIocpData = (IOCPPASSINDATA*)event.data.ptr; + + std::lock_guard lock(rootIocpData->socket->readMutex); + while (true) { + char peekBuffer[1]; + int rc = rootIocpData->socket->recv(peekBuffer, 1, MSG_PEEK); + if (rc > 0) + ; + else if (rc == 0) { + rootIocpData->event = IOCPEVENT::QUIT; + spdlog::debug("Disconnected. [{}]", + (std::string)(rootIocpData->socket->remoteAddr)); + ::epoll_ctl(epollfd, EPOLL_CTL_DEL, rootIocpData->socket->sock, NULL); + threadPool->enqueueJob(callback, rootIocpData); + // delete rootIocpData; + return; + } else { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + spdlog::trace("No data to read on {}", tid); + return; + } else { + rootIocpData->event = IOCPEVENT::QUIT; + spdlog::debug("Disconnected. [{}]", + (std::string)(rootIocpData->socket->remoteAddr)); + ::epoll_ctl(epollfd, EPOLL_CTL_DEL, rootIocpData->socket->sock, NULL); + threadPool->enqueueJob(callback, rootIocpData); + // delete rootIocpData; + return; + } + } + Chattr::IOCPPASSINDATA* ptr = new Chattr::IOCPPASSINDATA(*rootIocpData); + ptr->wsabuf.len = 1500; + + int redSize = 0; + int headerSize = 8; + int totalRedSize = 0; + + while (totalRedSize < headerSize) { + redSize = ptr->socket->recv(ptr->buf + totalRedSize, + headerSize - totalRedSize, 0); + + if (redSize == SOCKET_ERROR) { + ptr->event = IOCPEVENT::QUIT; + spdlog::debug("Disconnected. [{}]", + (std::string)(ptr->socket->remoteAddr)); + ::epoll_ctl(epollfd, EPOLL_CTL_DEL, ptr->socket->sock, NULL); + threadPool->enqueueJob(callback, ptr); + // delete ptr; + return; + } else if (redSize == 0) { + ptr->event = IOCPEVENT::QUIT; + spdlog::debug("Disconnected. [{}]", + (std::string)(ptr->socket->remoteAddr)); + ::epoll_ctl(epollfd, EPOLL_CTL_DEL, ptr->socket->sock, NULL); + threadPool->enqueueJob(callback, ptr); + // delete ptr; + return; + } + totalRedSize += redSize; + } + + Packet packet; + ::memcpy(packet.serialized, ptr->buf, headerSize); + + redSize = 0; + int dataLength = ntohs(packet.__data.packetLength); + + while (totalRedSize < dataLength + headerSize) { + redSize = ptr->socket->recv(ptr->buf + totalRedSize, + dataLength + headerSize - totalRedSize, 0); + + if (redSize == SOCKET_ERROR) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + spdlog::trace("No data to read on {}", tid); + return; + } + ptr->event = IOCPEVENT::QUIT; + spdlog::debug("Disconnected. [{}]", + (std::string)(ptr->socket->remoteAddr)); + ::epoll_ctl(epollfd, EPOLL_CTL_DEL, ptr->socket->sock, NULL); + threadPool->enqueueJob(callback, ptr); + // delete ptr; + return; + } else if (redSize == 0) { + ptr->event = IOCPEVENT::QUIT; + spdlog::debug("Disconnected. [{}]", + (std::string)(ptr->socket->remoteAddr)); + ::epoll_ctl(epollfd, EPOLL_CTL_DEL, ptr->socket->sock, NULL); + threadPool->enqueueJob(callback, ptr); + // delete ptr; + return; + } + totalRedSize += redSize; + } + ptr->transferredbytes = totalRedSize; + threadPool->enqueueJob(callback, ptr); + } + }; + static void socketWriter( + ThreadPool* threadPool, epoll_event event, int epollfd, + std::function callback) { + pthread_t tid = pthread_self(); + + if (event.data.ptr == nullptr) { + spdlog::error("invalid call on {}", tid); + return; + } + + IOCPPASSINDATA* rootIocpData = (IOCPPASSINDATA*)event.data.ptr; + + std::lock_guard lock(rootIocpData->socket->writeMutex); + while (!rootIocpData->sendQueue->empty()) { + IOCPPASSINDATA* data = rootIocpData->sendQueue->front(); + rootIocpData->sendQueue->pop(); + + if (data == nullptr) { + spdlog::error("invalid call on {}", tid); + break; + } + + int packetSize = data->wsabuf.len; + int totalSentSize = 0; + int sentSize = 0; + + spdlog::trace("Sending to: [{}]", (std::string)data->socket->remoteAddr); + + while (totalSentSize < packetSize) { + sentSize = data->socket->send(data->buf + totalSentSize, + packetSize - totalSentSize, 0); + + if (sentSize == SOCKET_ERROR) { + if (errno == EAGAIN || errno == EWOULDBLOCK) { + spdlog::warn("buffer full"); + continue; + } + data->event = IOCPEVENT::QUIT; + spdlog::debug("Disconnected. [{}]", + (std::string)(data->socket->remoteAddr)); + ::epoll_ctl(epollfd, EPOLL_CTL_DEL, data->socket->sock, NULL); + threadPool->enqueueJob(callback, data); + // delete data; + return; + } + totalSentSize += sentSize; + } + data->transferredbytes = totalSentSize; + threadPool->enqueueJob(callback, data); + } + }; + static void iocpWatcher( + ThreadPool* threadPool, int epollfd, int epollDetroyerFd, + std::function callback) { + struct epoll_event events[FD_SETSIZE]; + pthread_t tid = pthread_self(); + + int nready = ::epoll_wait(epollfd, events, FD_SETSIZE, -1); + + for (int i = 0; i < nready; i++) { + struct epoll_event current_event = events[i]; + + if (current_event.events & EPOLLIN) { + if (current_event.data.fd == epollDetroyerFd) { + return; + } + std::function task(callback); + threadPool->enqueueJob(socketReader, current_event, epollfd, task); + } else if (current_event.events & EPOLLOUT) { + std::function task(callback); + threadPool->enqueueJob(socketWriter, current_event, epollfd, task); + } + if (--nready <= 0) break; + } + threadPool->enqueueJob(iocpWatcher, epollfd, epollDetroyerFd, callback); + }; +#endif + + IOCP(); + ~IOCP(); + + template + void init(utils::ThreadPool* __IOCPThread, _Callable&& callback) { + IOCPThread_ = __IOCPThread; +#ifdef _WIN32 + completionPort_ = + ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); + if (completionPort_ == NULL) { + spdlog::critical("CreateIoCompletionPort()"); + std::exit(EXIT_FAILURE); + } +#elif __linux__ + epollfd_ = ::epoll_create(1); + epollDetroyerFd = ::eventfd(0, EFD_NONBLOCK); + struct epoll_event ev; + ev.events = EPOLLIN; + ev.data.fd = epollDetroyerFd; + ::epoll_ctl(epollfd_, EPOLL_CTL_ADD, epollDetroyerFd, &ev); +#endif + auto boundFunc = [callback = std::move(callback)]( + utils::ThreadPool* __IOCPThread, + IOCPPASSINDATA* data) mutable { + callback(__IOCPThread, data); + }; + +#ifdef _WIN32 + int tCount = __IOCPThread->threadCount; + + spdlog::info("Resizing threadpool size to: {}", tCount * 2); + + __IOCPThread->respawnWorker(tCount * 2); + + for (int i = 0; i < tCount; i++) { + std::function task(boundFunc); + __IOCPThread->enqueueJob(iocpWather, completionPort_, task); + } +#elif __linux__ + __IOCPThread->respawnWorker(__IOCPThread->threadCount + 1); + spdlog::info("Spawning 1 Epoll Waiter..."); + __IOCPThread->enqueueJob(iocpWatcher, epollfd_, epollDetroyerFd, boundFunc); +#endif + } + + void destruct(); + + void registerSocket(IOCPPASSINDATA* data); + + int recv(IOCPPASSINDATA* data, int bufferCount); + int send(IOCPPASSINDATA* data, int bufferCount, int __flags); + +#ifdef __linux__ + int epollDetroyerFd = -1; +#endif + + private: + struct WSAManager* wsaManager = WSAManager::GetInstance(); + utils::ThreadPool* IOCPThread_; + +#ifdef _WIN32 + HANDLE completionPort_ = INVALID_HANDLE_VALUE; +#elif __linux__ + int epollfd_ = -1; + std::unordered_map, std::mutex> writeMutex; + std::unordered_map, std::queue> + writeBuffer; +#endif +}; + +} // namespace Socket diff --git a/include/socket/tcp_socket.h b/include/socket/tcp_socket.h new file mode 100644 index 0000000..8f5f43f --- /dev/null +++ b/include/socket/tcp_socket.h @@ -0,0 +1,18 @@ +#pragma once +#include "udp_socket.h" + +namespace Socket { + +class TCPSocket : public UDPSocket { +public: + using UDPSocket::UDPSocket; + using UDPSocket::init; + int init(int domain); + int listen(int __n); + void accept(TCPSocket& newSock, Address& addr); + int connect(Socket::Address& serveraddr); + int recv(void *__restrict __buf, size_t __n, int __flags); + int send(const void *__buf, size_t __n, int __flags); +}; + +} diff --git a/include/socket/udp_socket.h b/include/socket/udp_socket.h new file mode 100644 index 0000000..2d8c231 --- /dev/null +++ b/include/socket/udp_socket.h @@ -0,0 +1,45 @@ +#pragma once +#include "address.h" + +namespace Socket { + +struct Address; + +class UDPSocket { + public: + UDPSocket() = default; + UDPSocket(int domain, int type, int protocol); + ~UDPSocket(); + + int init(int domain, int type, int protocol); + void destruct(); + + operator SOCKET(); + void set(const SOCKET __sock, int __domain); + int setsockopt(int level, int optname, const char* optval, int optlen); + + int bind(Address __addr); + + int recvfrom(void* __restrict __buf, size_t __n, int __flags, + struct Address& __addr); + int sendto(const void* __buf, size_t __n, int __flags, struct Address __addr); + + UDPSocket(const UDPSocket&); + UDPSocket(UDPSocket&&) noexcept; + UDPSocket& operator=(const UDPSocket&); + UDPSocket& operator=(UDPSocket&&) noexcept; + + struct Address bindAddr = {}; + struct Address remoteAddr = {}; + + int domain = 0; + SOCKET sock = INVALID_SOCKET; + + std::mutex readMutex; + std::mutex writeMutex; + + protected: + bool valid_ = false; +}; + +} // namespace Socket diff --git a/include/socket/wsa_manager.h b/include/socket/wsa_manager.h new file mode 100644 index 0000000..3256e5e --- /dev/null +++ b/include/socket/wsa_manager.h @@ -0,0 +1,20 @@ +#pragma once + +namespace Socket { + +struct WSAManager { + public: + static WSAManager* GetInstance() { + static WSAManager instance; + return &instance; + } + + WSAManager(const WSAManager&) = delete; + WSAManager& operator=(const WSAManager&) = delete; + + private: + WSAManager(); + ~WSAManager(); +}; + +} diff --git a/include/transport/transport.h b/include/transport/transport.h new file mode 100644 index 0000000..6698759 --- /dev/null +++ b/include/transport/transport.h @@ -0,0 +1,18 @@ +#pragma once + +#include "utils/thread_pool.h" + +namespace happytanuki { + +class Transport { + public: + Transport(utils::ThreadPool* tp); + + void Send(); + void Recv(); + + private: + utils::ThreadPool* tp_; +}; + +} // namespace happytanuki diff --git a/include/utils/log.h b/include/utils/log.h new file mode 100644 index 0000000..3d20d30 --- /dev/null +++ b/include/utils/log.h @@ -0,0 +1,9 @@ +#pragma once + +namespace utils { + +void setDefaultLogger(spdlog::level::level_enum logLevel, + gsl::czstring logFileName, std::uint32_t logFileSize, + std::uint32_t logFileCount); + +} // namespace utils diff --git a/include/utils/snowflake.h b/include/utils/snowflake.h new file mode 100644 index 0000000..1e86bc9 --- /dev/null +++ b/include/utils/snowflake.h @@ -0,0 +1,32 @@ +#pragma once +#include + +namespace utils { + +struct Snowflake { + union { + struct { + std::uint64_t timestamp : 42; + std::uint64_t instance : 10; + std::uint64_t sequence : 12; + }; + std::uint64_t snowflake; + }; + + bool operator==(const Snowflake& other) const { + return snowflake == other.snowflake; + } +}; + +Snowflake GenerateID(); + +} // namespace Chattr + +namespace std { +template <> +struct hash { + std::size_t operator()(const utils::Snowflake& k) const { + return std::hash{}(k.snowflake); + } +}; +} // namespace std diff --git a/Client/include/utils/thread_pool.h b/include/utils/thread_pool.h similarity index 100% rename from Client/include/utils/thread_pool.h rename to include/utils/thread_pool.h