From 5ba62b5312ad0f8ec22180cb438d823c455472a3 Mon Sep 17 00:00:00 2001 From: HappyTanuki Date: Sun, 8 Jun 2025 01:11:23 +0900 Subject: [PATCH] =?UTF-8?q?udp/tcp=20=EB=B9=84=EB=8F=99=EA=B8=B0=20?= =?UTF-8?q?=ED=86=B5=EC=8B=A0=EC=9D=80=20=ED=98=84=EC=9E=AC=20=EA=B0=80?= =?UTF-8?q?=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Client/src/asteroid/main.cpp | 195 +++++++++++++++++++++-------------- impl/socket/iocp.cpp | 84 +++++++++++++-- impl/socket/socket.cpp | 21 ++++ impl/socket/udp_socket.cpp | 20 ++++ include/socket/iocp.h | 18 +++- include/socket/socket.h | 1 + include/socket/udp_socket.h | 1 + 7 files changed, 251 insertions(+), 89 deletions(-) diff --git a/Client/src/asteroid/main.cpp b/Client/src/asteroid/main.cpp index 75f8d7f..2a1aafc 100644 --- a/Client/src/asteroid/main.cpp +++ b/Client/src/asteroid/main.cpp @@ -7,104 +7,143 @@ #include "socket/udp_socket.h" #include "socket/wsa_manager.h" #include "utils/log.h" +#include "utils/snowflake.h" #include "vulkan_engine/vulkan/engine.h" #include "vulkan_engine/vulkan/graphics.h" - std::int32_t main(std::int32_t argc, gsl::zstring* argv) { +std::int32_t main(std::int32_t argc, gsl::zstring* argv) { Network::WSAManager wsamanager; - #if !defined(NDEBUG) - utils::setDefaultLogger(spdlog::level::level_enum::debug, "log.log", 1024, - 2); - #endif - utils::ThreadPool tp(0); - Network::IOCP iocp; - iocp.init(&tp, SessionProtocol::TCP); +#if !defined(NDEBUG) + utils::setDefaultLogger(spdlog::level::level_enum::debug, "log.log", 1024, 2); +#endif + utils::ThreadPool tp(0); + Network::IOCP iocp; + iocp.init(&tp, SessionProtocol::TLS); - Network::Address addr; - in6_addr in6addr; + Network::Address addr; + in6_addr in6addr; - addr.set(AF_INET6, "::1", 9010); + addr.set(AF_INET, "43.200.173.151", 9010); - Network::TCPSocket sock; - sock.init(AF_INET6); - if (sock.connect(addr) == INVALID_SOCKET) { - spdlog::error("connect()"); - std::exit(EXIT_FAILURE); - } + Network::Socket sock; + Network::TCPSocket TCPSock; + Network::UDPSocket UDPSock; + TCPSock.init(AF_INET); + UDPSock.init(AF_INET); + sock = TCPSock; + if (sock.connect(addr) == INVALID_SOCKET) { + spdlog::error("connect()"); + std::exit(EXIT_FAILURE); + } - Network::IOCPPASSINDATA* data = new Network::IOCPPASSINDATA(16 * 1024); - data->socket = std::make_shared(sock); - data->IOCPInstance = &iocp; - iocp.registerSocket(data); + // SSL_CTX* ctx = ::SSL_CTX_new(::OSSL_QUIC_client_method()); + SSL_CTX* ctx = SSL_CTX_new(TLS_client_method()); + if (ctx == NULL) { + printf("Failed to create the SSL_CTX\n"); + std::exit(EXIT_FAILURE); + } - std::vector send_data; - data->event = Network::IOCPEVENT::WRITE; - data->wsabuf.buf[0] = 'a'; - data->wsabuf.buf[1] = 'b'; - data->wsabuf.buf[2] = ' '; - data->wsabuf.len = 3; - send_data.push_back(data); - Network::IOCPPASSINDATA* data2 = new Network::IOCPPASSINDATA(16 * 1024); - data2->wsabuf.buf[0] = 'b'; - data2->wsabuf.buf[1] = 'a'; - data2->wsabuf.buf[2] = '\0'; - data2->wsabuf.len = 3; - send_data.push_back(data2); - iocp.send(sock.sock, &send_data); + // SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); + /* * 인증서 검증을 비활성화합니다. + * SSL_VERIFY_NONE 플래그를 사용하면 클라이언트는 서버 인증서를 요청하거나 + * 확인하지 않습니다. 경고: 보안상 매우 위험합니다. 테스트용으로만 사용하세요. + */ + SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); - Network::IOCPPASSINDATA* recv_data = new Network::IOCPPASSINDATA(16 * 1024); - recv_data->socket = std::make_shared(sock); - recv_data->IOCPInstance = &iocp; - while (!iocp.recv(recv_data)); // 어떤 데이터를 읽는걸 보장받고 싶다면 그냥 스린락 걸어버리기. + /* Use the default trusted certificate store */ + if (!SSL_CTX_set_default_verify_paths(ctx)) { + printf("Failed to set the default trusted certificate store\n"); + std::exit(EXIT_FAILURE); + } - spdlog::info("recv_data: {}", recv_data->wsabuf.buf); + Network::IOCPPASSINDATA* data = new Network::IOCPPASSINDATA(16 * 1024, ctx); + data->socket = std::make_shared(sock); + data->IOCPInstance = &iocp; - std::vector send_data2; - data->event = Network::IOCPEVENT::WRITE; - data->wsabuf.buf[0] = 'c'; - data->wsabuf.buf[1] = 'd'; - data->wsabuf.buf[2] = ' '; - data->wsabuf.len = 3; - send_data2.push_back(data); - data2 = new Network::IOCPPASSINDATA(16 * 1024); - data2->wsabuf.buf[0] = 'd'; - data2->wsabuf.buf[1] = 'c'; - data2->wsabuf.buf[2] = '\0'; - data2->wsabuf.len = 3; - send_data2.push_back(data2); - iocp.send(sock.sock, &send_data2); + char buf[4096]; + int ret = 0; + while ((ret = SSL_connect(data->ssl.get())) != 1) { + if (BIO_ctrl_pending(SSL_get_wbio(data->ssl.get())) > 0) { + // wbio에서 암호화된 데이터를 읽어서 + int bytes_to_write = + BIO_read(SSL_get_wbio(data->ssl.get()), buf, sizeof(buf)); + if (bytes_to_write > 0) { + // 소켓으로 전송 + send(sock.sock, buf, bytes_to_write, 0); + } + } + int err = SSL_get_error(data->ssl.get(), ret); + if (err == SSL_ERROR_WANT_READ) { + // OpenSSL이 소켓에서 데이터를 더 읽어오길 원함 + // 소켓에서 데이터를 읽어서 + int bytes_read = recv(sock.sock, buf, sizeof(buf), 0); + if (bytes_read > 0) { + // rbio에 써준다 + BIO_write(SSL_get_rbio(data->ssl.get()), buf, bytes_read); + } else if (bytes_read == 0) { + // 소켓 연결이 끊김 + fprintf(stderr, "Socket closed during handshake.\n"); + return 0; + } else { + // 소켓 에러 (EAGAIN/EWOULDBLOCK은 논블록킹 소켓에서 정상) + // 실제 프로덕션 코드에서는 이 부분을 정교하게 처리해야 합니다. + } + } else { + printf("Failed to connect to server\n"); + std::exit(EXIT_FAILURE); + } + continue; + } - recv_data = new Network::IOCPPASSINDATA(16 * 1024); - recv_data->socket = std::make_shared(sock); - recv_data->IOCPInstance = &iocp; - while (!iocp.recv(recv_data)); // 어떤 데이터를 읽는걸 보장받고 싶다면 그냥 - // 스린락 걸어버리기. + iocp.registerTCPSocket(data); - spdlog::info("recv_data: {}", recv_data->wsabuf.buf); + std::vector send_data; + data->event = Network::IOCPEVENT::WRITE; + auto snowflake = utils::GenerateID(); + auto timestamp = std::to_string(snowflake.timestamp); + ::memcpy( + data->wsabuf.buf, timestamp.c_str(), 128 + /*(data->bufsize < timestamp.size()) ? data->bufsize : timestamp.size()*/); + data->wsabuf.len = timestamp.size(); + send_data.push_back(data); + iocp.send(sock.sock, &send_data); - const veng::GlfwInitialization _glfw; + Network::IOCPPASSINDATA* recv_data = + new Network::IOCPPASSINDATA(16 * 1024, ctx); + recv_data->socket = std::make_shared(sock); + recv_data->IOCPInstance = &iocp; + while (!iocp.recv(recv_data)); // 어떤 데이터를 읽는걸 보장받고 싶다면 그냥 + // 스핀락 걸어버리기. - veng::Window window("Vulkan Engine", {800, 600}); - window.TryMoveToMonitor(0); + auto snowflake2 = utils::GenerateID(); + auto timestamp2 = std::to_string(snowflake2.timestamp); - veng::Graphics graphics(&window); - veng::Engine engine(&graphics, &tp); + spdlog::info("recv_data: {}", recv_data->wsabuf.buf); + spdlog::info("current stamp: {}", timestamp2); - engine.LoadModelAsset("assets/player.fbx", "player"); - engine.LoadModelAsset("assets/player_flame.fbx", "player_flame"); - engine.LoadModelAsset("assets/bullet.fbx", "bullet"); - engine.LoadModelAsset("assets/background.fbx", "background"); + const veng::GlfwInitialization _glfw; - engine.BeginPlay = BeginPlay; - engine.Tick = Tick; + veng::Window window("Vulkan Engine", {800, 600}); + window.TryMoveToMonitor(0); - engine.init(); + veng::Graphics graphics(&window); + veng::Engine engine(&graphics, &tp); - while (!window.ShouldClose()) { - glfwPollEvents(); + engine.LoadModelAsset("assets/player.fbx", "player"); + engine.LoadModelAsset("assets/player_flame.fbx", "player_flame"); + engine.LoadModelAsset("assets/bullet.fbx", "bullet"); + engine.LoadModelAsset("assets/background.fbx", "background"); - engine.Update(); - } + engine.BeginPlay = BeginPlay; + engine.Tick = Tick; - return EXIT_SUCCESS; - } + engine.init(); + + while (!window.ShouldClose()) { + glfwPollEvents(); + + engine.Update(); + } + + return EXIT_SUCCESS; +} diff --git a/impl/socket/iocp.cpp b/impl/socket/iocp.cpp index 6a63e70..e364637 100644 --- a/impl/socket/iocp.cpp +++ b/impl/socket/iocp.cpp @@ -40,7 +40,7 @@ void IOCP::destruct() { #endif } -void IOCP::registerSocket(IOCPPASSINDATA* data) { +void IOCP::registerTCPSocket(IOCPPASSINDATA* data) { #ifdef _WIN32 HANDLE returnData = ::CreateIoCompletionPort( (HANDLE)data->socket->sock, completionPort_, data->socket->sock, 0); @@ -50,20 +50,51 @@ void IOCP::registerSocket(IOCPPASSINDATA* data) { recv_data->event = IOCPEVENT::READ; recv_data->socket = data->socket; DWORD recvbytes = 0, flags = 0; - int result = ::WSARecv(recv_data->socket->sock, &recv_data->wsabuf, 1, - &recvbytes, &flags, &recv_data->overlapped, NULL); + + int result = SOCKET_ERROR; + + ::WSARecv(recv_data->socket->sock, &recv_data->wsabuf, 1, &recvbytes, &flags, + &recv_data->overlapped, NULL); if (result == SOCKET_ERROR) { int err = ::WSAGetLastError(); if (err != WSA_IO_PENDING) { - spdlog::error("WSARecv failed: {}", err); - // 반드시 여기서 리턴하거나 처리해야 합니다. + auto err_msg = std::format("WSARecv failed: {}", err); + throw std::runtime_error(err_msg); } } #endif } -int IOCP::recv(IOCPPASSINDATA* data) { //읽은 바이트수가 무조건 100임? 왜..? +void IOCP::registerUDPSocket(IOCPPASSINDATA* data, Address recv_addr) { +#ifdef _WIN32 + HANDLE returnData = ::CreateIoCompletionPort( + (HANDLE)data->socket->sock, completionPort_, data->socket->sock, 0); + if (returnData == 0) completionPort_ = returnData; + + IOCPPASSINDATA* recv_data = new IOCPPASSINDATA(data->bufsize); + recv_data->event = IOCPEVENT::READ; + recv_data->socket = data->socket; + DWORD recvbytes = 0, flags = 0; + + int result = SOCKET_ERROR; + + ::WSARecvFrom(recv_data->socket->sock, &recv_data->wsabuf, 1, &recvbytes, + &flags, &recv_addr.addr, &recv_addr.length, + &recv_data->overlapped, NULL); + + if (result == SOCKET_ERROR) { + int err = ::WSAGetLastError(); + if (err != WSA_IO_PENDING) { + auto err_msg = std::format("WSARecv failed: {}", err); + throw std::runtime_error(err_msg); + } + } + +#endif +} + +int IOCP::recv(IOCPPASSINDATA* data) { // 읽은 바이트수가 무조건 100임? 왜..? SOCKET sock = data->socket->sock; std::lock_guard lock(*GetRecvQueueMutex_(sock)); auto queue = GetRecvQueue_(sock); @@ -148,14 +179,49 @@ void IOCP::iocpWatcher_(utils::ThreadPool* IOCPThread) { auto queue_list = GetRecvQueue_(data->socket->sock); if (data->event == IOCPEVENT::READ) { if (proto_ == SessionProtocol::TLS || proto_ == SessionProtocol::QUIC) { + // DEBUG: BIO_write 전 OpenSSL 에러 스택 확인 (혹시 모를 이전 에러) + ERR_print_errors_fp(stderr); // 이미 오류 스택에 뭔가 있는지 확인용 + fprintf(stderr, "--- Before BIO_write ---\n"); + ::BIO_write(::SSL_get_rbio(data->ssl.get()), data->wsabuf.buf, cbTransfrred); + // DEBUG: BIO_write 후 OpenSSL 에러 스택 확인 (BIO_write에서 에러 발생 시) + ERR_print_errors_fp(stderr); // BIO_write에서도 에러가 발생할 수 있음 + fprintf(stderr, "--- After BIO_write, cbTransfrred: %lu ---\n", + cbTransfrred); + while ((red_data = ::SSL_read(data->ssl.get(), buf.data(), buf.size())) > 0) { queue_list->emplace_back(std::make_pair( std::vector(buf.begin(), buf.begin() + red_data), 0)); } + if (red_data == -1) { + auto ssl_error_code = SSL_get_error( + data->ssl.get(), red_data); // 여기서 SSL_get_error 결과 저장 + auto err_msg = std::format("SSL_read failed with SSL_get_error: {}", + ssl_error_code); + fprintf(stderr, "%s\n", err_msg.c_str()); + + // *** 가장 중요한 부분: SSL_ERROR_SSL일 때 상세 에러를 강제로 출력 시도 + // *** + if (ssl_error_code == SSL_ERROR_SSL) { + fprintf(stderr, "Detailed SSL_ERROR_SSL trace:\n"); + unsigned long err_peek; + // ERR_get_error()를 사용하여 스택의 모든 오류를 팝하고 출력 + while ((err_peek = ERR_get_error()) != 0) { + char err_str[256]; + ERR_error_string_n(err_peek, err_str, sizeof(err_str)); + fprintf(stderr, "OpenSSL stack error: %s\n", err_str); + } + } else { + // SSL_ERROR_SSL이 아닌 다른 오류 (SYSCALL, WANT_READ 등)일 경우 + // ERR_print_errors_fp는 여전히 유용할 수 있음 + ERR_print_errors_fp(stderr); + } + + throw std::runtime_error(err_msg); // 예외 발생 + } } else { ::memcpy(buf.data(), data->wsabuf.buf, data->transferredbytes); queue_list->emplace_back(std::make_pair( @@ -230,7 +296,8 @@ void IOCP::packet_sender_(SOCKET sock) { int data_len = 0; if (proto_ == SessionProtocol::TLS || proto_ == SessionProtocol::QUIC) { - int ret = ::SSL_write(front->ssl.get(), front->wsabuf.buf, front->wsabuf.len); + int ret = + ::SSL_write(front->ssl.get(), front->wsabuf.buf, front->wsabuf.len); if (ret <= 0) { int err = ::SSL_get_error(front->ssl.get(), ret); if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) { @@ -242,7 +309,8 @@ void IOCP::packet_sender_(SOCKET sock) { break; } - while ((data_len = ::BIO_read(::SSL_get_wbio(front->ssl.get()), buf.data(), buf.size())) > 0) { + while ((data_len = ::BIO_read(::SSL_get_wbio(front->ssl.get()), + buf.data(), buf.size())) > 0) { wsabuf.buf = buf.data(); wsabuf.len = data_len; diff --git a/impl/socket/socket.cpp b/impl/socket/socket.cpp index a10d899..bf63997 100644 --- a/impl/socket/socket.cpp +++ b/impl/socket/socket.cpp @@ -12,6 +12,7 @@ int Socket::init(int domain, int type, int protocol) { this->domain = domain; sock = ::socket(domain, type, protocol); + // sock = ::WSASocket(domain, type, protocol, NULL, 0, WSA_FLAG_OVERLAPPED); if (sock == INVALID_SOCKET) spdlog::critical("socket()"); valid_ = true; @@ -65,6 +66,26 @@ int Socket::bind(Address __addr) { return retVal; } +int Socket::connect(Address& serveraddr) { + std::string addr_string = serveraddr; + int retVal = -1; + if (serveraddr.family == AF_INET) + retVal = ::connect(sock, (const sockaddr*)&serveraddr.addr_in, + serveraddr.length); + else + retVal = ::connect(sock, (const sockaddr*)&serveraddr.addr_in6, + serveraddr.length); + memcpy(&remoteAddr, &serveraddr, sizeof(Address)); + if (retVal == INVALID_SOCKET) { +#ifdef _WIN32 + int err = WSAGetLastError(); + spdlog::error("connect() failed: WSA error {} (0x{:X})", err, err); +#endif + spdlog::error("connect()"); + } + return retVal; +} + int Socket::recvfrom(void* __restrict __buf, size_t __n, int __flags, struct Address& __addr) { std::lock_guard lock(readMutex); diff --git a/impl/socket/udp_socket.cpp b/impl/socket/udp_socket.cpp index 8b4e850..003b03f 100644 --- a/impl/socket/udp_socket.cpp +++ b/impl/socket/udp_socket.cpp @@ -4,4 +4,24 @@ namespace Network { int UDPSocket::init(int domain) { return init(domain, SOCK_DGRAM, 0); } +int UDPSocket::connect(Address &serveraddr) { + std::string addr_string = serveraddr; + int retVal = -1; + if (serveraddr.family == AF_INET) + retVal = ::connect(sock, (const sockaddr *)&serveraddr.addr_in, + serveraddr.length); + else + retVal = ::connect(sock, (const sockaddr *)&serveraddr.addr_in6, + serveraddr.length); + memcpy(&remoteAddr, &serveraddr, sizeof(Address)); + if (retVal == INVALID_SOCKET) { +#ifdef _WIN32 + int err = WSAGetLastError(); + spdlog::error("connect() failed: WSA error {} (0x{:X})", err, err); +#endif + spdlog::error("connect()"); + } + return retVal; +} + } // namespace Network diff --git a/include/socket/iocp.h b/include/socket/iocp.h index 4c7ab88..fefd94f 100644 --- a/include/socket/iocp.h +++ b/include/socket/iocp.h @@ -61,8 +61,19 @@ struct IOCPPASSINDATA { event = IOCPEVENT::QUIT; socket = nullptr; ssl = std::shared_ptr(::SSL_new(ctx), ::SSL_free); - ::SSL_set_bio(ssl.get(), ::BIO_new(::BIO_s_mem()), - ::BIO_new(::BIO_s_mem())); + if (ssl == nullptr) { + throw std::runtime_error("SSL_new failed. Check SSL_CTX or memory."); + } + BIO* rbio = BIO_new(BIO_s_mem()); + BIO* wbio = BIO_new(BIO_s_mem()); + + if (!rbio || !wbio) { + BIO_free(rbio); + BIO_free(wbio); + throw std::runtime_error("BIO_new failed"); + } + + SSL_set_bio(ssl.get(), rbio, wbio); transferredbytes = 0; this->bufsize = bufsize; IOCPInstance = nullptr; @@ -122,7 +133,8 @@ class IOCP { void destruct(); - void registerSocket(IOCPPASSINDATA* data); + void registerTCPSocket(IOCPPASSINDATA* data); + void registerUDPSocket(IOCPPASSINDATA* data, Address recv_addr); int recv(IOCPPASSINDATA* data); // data는 한 가지 소켓에 보내는 패킷만 담아야 합니다 diff --git a/include/socket/socket.h b/include/socket/socket.h index 049a7f4..2c8c650 100644 --- a/include/socket/socket.h +++ b/include/socket/socket.h @@ -19,6 +19,7 @@ class Socket { int setsockopt(int level, int optname, const char* optval, int optlen); int bind(Address __addr); + int connect(Network::Address& serveraddr); int recvfrom(void* __restrict __buf, size_t __n, int __flags, struct Address& __addr); diff --git a/include/socket/udp_socket.h b/include/socket/udp_socket.h index 28618ba..3ee6d84 100644 --- a/include/socket/udp_socket.h +++ b/include/socket/udp_socket.h @@ -10,6 +10,7 @@ class UDPSocket : public Socket { using Socket::init; using Socket::Socket; int init(int domain); + int connect(Network::Address& serveraddr); }; } // namespace Socket