From af181bb133cd0dc5f7c499f609d098f721482809 Mon Sep 17 00:00:00 2001 From: Ayndpa Date: Tue, 18 Nov 2025 21:03:01 +0800 Subject: [PATCH] Refactor Steam Networking Components - Removed SteamMessageHandler class and its implementation files. - Integrated control packet handling directly into the SteamMessageHandler. - Updated TCPClient and TCPServer classes to improve connection management and error handling. - Added SteamNetworkingManager class to manage Steam networking, including lobby creation and connection handling. - Implemented callbacks for Steam Friends and Matchmaking to handle lobby events. - Enhanced message forwarding logic between TCP clients and Steam connections. - Introduced control packet handling for ping responses. - Improved thread safety with mutexes for shared resources. --- online_game_tool.cpp | 313 ++++-------------- .../control_packets.cpp | 0 .../control_packets.h | 0 .../steam_message_handler.cpp | 24 +- .../steam_message_handler.h | 6 +- steamnet/steam_networking_manager.cpp | 292 ++++++++++++++++ steamnet/steam_networking_manager.h | 141 ++++++++ tcp/tcp_client.cpp | 20 +- tcp/tcp_server.cpp | 20 +- tcp/tcp_server.h | 12 +- 10 files changed, 547 insertions(+), 281 deletions(-) rename control_packets.cpp => steamnet/control_packets.cpp (100%) rename control_packets.h => steamnet/control_packets.h (100%) rename steam_message_handler.cpp => steamnet/steam_message_handler.cpp (79%) rename steam_message_handler.h => steamnet/steam_message_handler.h (67%) create mode 100644 steamnet/steam_networking_manager.cpp create mode 100644 steamnet/steam_networking_manager.h diff --git a/online_game_tool.cpp b/online_game_tool.cpp index 601a7d2..fb81bb8 100644 --- a/online_game_tool.cpp +++ b/online_game_tool.cpp @@ -10,207 +10,36 @@ #include #include #include -#include -#include -#include -#include #include #include #include "tcp_server.h" #include "tcp/tcp_client.h" -#include "steam_message_handler.h" +#include "steamnet/steam_networking_manager.h" using boost::asio::ip::tcp; -// Callback class for Steam Friends -class SteamFriendsCallbacks { -public: - SteamFriendsCallbacks() {} - STEAM_CALLBACK(SteamFriendsCallbacks, OnGameRichPresenceJoinRequested, GameRichPresenceJoinRequested_t); -}; - -// Global variables for Steam Networking -HSteamNetConnection g_hConnection = k_HSteamNetConnection_Invalid; -bool g_isConnected = false; -HSteamListenSocket hListenSock = k_HSteamListenSocket_Invalid; -ISteamNetworkingSockets* m_pInterface = nullptr; -bool forwarding = false; -std::unique_ptr server; - -// Connection config for improved P2P reliability -SteamNetworkingConfigValue_t g_connectionConfig[2]; -int g_retryCount = 0; -const int MAX_RETRIES = 3; -CSteamID g_hostSteamID; -int g_currentVirtualPort = 0; - // New variables for multiple connections and TCP clients std::vector connections; -std::map clientMap; +std::map> clientMap; std::mutex clientMutex; std::mutex connectionsMutex; // Add mutex for connections int localPort = 0; -bool g_isHost = false; -bool g_isClient = false; - -// User info structure -struct UserInfo { - CSteamID steamID; - std::string name; - int ping; - bool isRelay; -}; -std::map userMap; - -// Callback function for connection status changes -void OnSteamNetConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t *pInfo) -{ - std::lock_guard lock(clientMutex); - std::cout << "Connection status changed: " << pInfo->m_info.m_eState << " for connection " << pInfo->m_hConn << std::endl; - if (pInfo->m_info.m_eState == k_ESteamNetworkingConnectionState_ProblemDetectedLocally) { - std::cout << "Connection failed: " << pInfo->m_info.m_szEndDebug << std::endl; - } - if (pInfo->m_eOldState == k_ESteamNetworkingConnectionState_None && pInfo->m_info.m_eState == k_ESteamNetworkingConnectionState_Connecting) - { - // Incoming connection, accept it - SteamNetworkingSockets()->AcceptConnection(pInfo->m_hConn); - { - std::lock_guard lockConn(connectionsMutex); - connections.push_back(pInfo->m_hConn); - } - g_hConnection = pInfo->m_hConn; // Keep for backward compatibility if needed - g_isConnected = true; - std::cout << "Accepted incoming connection from " << pInfo->m_info.m_identityRemote.GetSteamID().ConvertToUint64() << std::endl; - // Add user info - SteamNetConnectionInfo_t info; - SteamNetConnectionRealTimeStatus_t status; - if (m_pInterface->GetConnectionInfo(pInfo->m_hConn, &info) && m_pInterface->GetConnectionRealTimeStatus(pInfo->m_hConn, &status, 0, nullptr)) { - UserInfo userInfo; - userInfo.steamID = pInfo->m_info.m_identityRemote.GetSteamID(); - userInfo.name = SteamFriends()->GetFriendPersonaName(userInfo.steamID); - userInfo.ping = status.m_nPing; - userInfo.isRelay = (info.m_idPOPRelay != 0); - userMap[pInfo->m_hConn] = userInfo; - std::cout << "Incoming connection details: ping=" << status.m_nPing << "ms, relay=" << (info.m_idPOPRelay != 0 ? "yes" : "no") << std::endl; - } - // Removed: Create TCP Client here - now lazy connect on first message - } - else if (pInfo->m_eOldState == k_ESteamNetworkingConnectionState_Connecting && pInfo->m_info.m_eState == k_ESteamNetworkingConnectionState_Connected) - { - // Client connected successfully - g_isConnected = true; - std::cout << "Connected to host" << std::endl; - // Add user info - SteamNetConnectionInfo_t info; - SteamNetConnectionRealTimeStatus_t status; - if (m_pInterface->GetConnectionInfo(pInfo->m_hConn, &info) && m_pInterface->GetConnectionRealTimeStatus(pInfo->m_hConn, &status, 0, nullptr)) { - UserInfo userInfo; - userInfo.steamID = pInfo->m_info.m_identityRemote.GetSteamID(); - userInfo.name = SteamFriends()->GetFriendPersonaName(userInfo.steamID); - userInfo.ping = status.m_nPing; - userInfo.isRelay = (info.m_idPOPRelay != 0); - userMap[pInfo->m_hConn] = userInfo; - std::cout << "Outgoing connection details: ping=" << status.m_nPing << "ms, relay=" << (info.m_idPOPRelay != 0 ? "yes" : "no") << std::endl; - } - } - else if (pInfo->m_info.m_eState == k_ESteamNetworkingConnectionState_ClosedByPeer || pInfo->m_info.m_eState == k_ESteamNetworkingConnectionState_ProblemDetectedLocally) - { - // Connection closed - g_isConnected = false; - g_hConnection = k_HSteamNetConnection_Invalid; - // Remove from connections - { - std::lock_guard lockConn(connectionsMutex); - auto it = connections.begin(); - while (it != connections.end()) { - if (*it == pInfo->m_hConn) { - it = connections.erase(it); - } else { - ++it; - } - } - } - // Remove from userMap - userMap.erase(pInfo->m_hConn); - // Cleanup TCP Client - if (clientMap.count(pInfo->m_hConn)) { - clientMap[pInfo->m_hConn]->disconnect(); - delete clientMap[pInfo->m_hConn]; - clientMap.erase(pInfo->m_hConn); - std::cout << "Cleaned up TCP Client for connection" << std::endl; - } - std::cout << "Connection closed" << std::endl; - - // Retry connection if client and retries left - if (g_isClient && !g_isConnected && g_retryCount < MAX_RETRIES) { - g_retryCount++; - g_currentVirtualPort++; - SteamNetworkingIdentity identity; - identity.SetSteamID(g_hostSteamID); - HSteamNetConnection newConn = m_pInterface->ConnectP2P(identity, g_currentVirtualPort, 2, g_connectionConfig); - if (newConn != k_HSteamNetConnection_Invalid) { - g_hConnection = newConn; - std::cout << "Retrying connection attempt " << g_retryCount << " with virtual port " << g_currentVirtualPort << std::endl; - } else { - std::cerr << "Failed to initiate retry connection" << std::endl; - } - } - } -} - -void SteamFriendsCallbacks::OnGameRichPresenceJoinRequested(GameRichPresenceJoinRequested_t *pCallback) { - CSteamID hostSteamID = pCallback->m_steamIDFriend; - if (!g_isHost && !g_isConnected) { - g_isClient = true; - g_hostSteamID = hostSteamID; - g_retryCount = 0; - g_currentVirtualPort = 0; - SteamNetworkingIdentity identity; - identity.SetSteamID(hostSteamID); - g_hConnection = m_pInterface->ConnectP2P(identity, g_currentVirtualPort, 2, g_connectionConfig); - if (g_hConnection != k_HSteamNetConnection_Invalid) { - std::cout << "Joined game room via invite from " << hostSteamID.ConvertToUint64() << ", attempting connection with virtual port " << g_currentVirtualPort << std::endl; - // Start TCP Server - server = std::make_unique(8888); - if (!server->start()) { - std::cerr << "Failed to start TCP server" << std::endl; - } - } - } -} +std::unique_ptr server; int main() { - // Initialize Steam API - if (!SteamAPI_Init()) { - std::cerr << "Failed to initialize Steam API" << std::endl; - return 1; - } - - // Initialize Steam Friends callbacks - SteamFriendsCallbacks steamFriendsCallbacks; - - // Initialize Steam Networking Sockets - SteamNetworkingUtils()->InitRelayNetworkAccess(); - - // Set global callback for connection status changes - SteamNetworkingUtils()->SetGlobalCallback_SteamNetConnectionStatusChanged(OnSteamNetConnectionStatusChanged); - - m_pInterface = SteamNetworkingSockets(); - - // Initialize connection config for better P2P reliability - g_connectionConfig[0].SetInt32(k_ESteamNetworkingConfig_TimeoutInitial, 10000); // 10 seconds initial timeout - g_connectionConfig[1].SetInt32(k_ESteamNetworkingConfig_NagleTime, 0); // Disable Nagle for UDP - - // Initialize boost::asio io_context boost::asio::io_context io_context; - // Create Steam Message Handler - SteamMessageHandler messageHandler(io_context, m_pInterface, connections, clientMap, clientMutex, connectionsMutex, server, g_isHost, localPort); + // Initialize Steam Networking Manager + SteamNetworkingManager steamManager; + if (!steamManager.initialize()) { + std::cerr << "Failed to initialize Steam Networking Manager" << std::endl; + return 1; + } // Initialize GLFW if (!glfwInit()) { std::cerr << "Failed to initialize GLFW" << std::endl; - SteamAPI_Shutdown(); + steamManager.shutdown(); return -1; } @@ -238,17 +67,9 @@ int main() { ImGui_ImplGlfw_InitForOpenGL(window, true); ImGui_ImplOpenGL3_Init("#version 130"); - // Get friends list - std::vector> friendsList; - int friendCount = SteamFriends()->GetFriendCount(k_EFriendFlagAll); - for (int i = 0; i < friendCount; ++i) { - CSteamID friendID = SteamFriends()->GetFriendByIndex(i, k_EFriendFlagAll); - const char* name = SteamFriends()->GetFriendPersonaName(friendID); - friendsList.push_back({friendID, name}); - } - - // Start message handler - messageHandler.start(); + // Set message handler dependencies + steamManager.setMessageHandlerDependencies(io_context, clientMap, clientMutex, server, localPort); + steamManager.startMessageHandler(); // Steam Networking variables bool isHost = false; @@ -256,6 +77,27 @@ int main() { char joinBuffer[256] = ""; char filterBuffer[256] = ""; + // Lambda to render invite friends UI + auto renderInviteFriends = [&]() { + ImGui::InputText("过滤朋友", filterBuffer, IM_ARRAYSIZE(filterBuffer)); + ImGui::Text("朋友:"); + for (const auto& friendPair : steamManager.getFriendsList()) { + std::string nameStr = friendPair.second; + std::string filterStr(filterBuffer); + // Convert to lowercase for case-insensitive search + std::transform(nameStr.begin(), nameStr.end(), nameStr.begin(), ::tolower); + std::transform(filterStr.begin(), filterStr.end(), filterStr.begin(), ::tolower); + if (filterStr.empty() || nameStr.find(filterStr) != std::string::npos) { + ImGui::PushID(friendPair.first.ConvertToUint64()); + if (ImGui::Button(("邀请 " + friendPair.second).c_str())) { + // Send invite via Steam + SteamFriends()->InviteUserToGame(friendPair.first, "加入我的游戏房间!"); + } + ImGui::PopID(); + } + } + }; + // Main loop while (!glfwWindowShouldClose(window)) { // Poll events @@ -277,74 +119,52 @@ int main() { } ImGui::Separator(); - if (!g_isHost && !g_isConnected) { + if (!steamManager.isHost() && !steamManager.isConnected()) { if (ImGui::Button("主持游戏房间")) { - // Create listen socket - hListenSock = m_pInterface->CreateListenSocketP2P(0, 0, nullptr); - if (hListenSock != k_HSteamListenSocket_Invalid) { - g_isHost = true; - std::cout << "Created listen socket for hosting game room" << std::endl; - // Set Rich Presence - std::string connectStr = std::to_string(SteamUser()->GetSteamID().ConvertToUint64()); - SteamFriends()->SetRichPresence("connect", connectStr.c_str()); - SteamFriends()->SetRichPresence("status", "主持游戏房间"); - std::cout << "Hosting game room. Connection string: " << connectStr << std::endl; - } else { - std::cerr << "Failed to create listen socket for hosting" << std::endl; - } + steamManager.startHosting(); + } + if (ImGui::Button("搜索游戏房间")) { + steamManager.searchLobbies(); } ImGui::InputText("主机Steam ID", joinBuffer, IM_ARRAYSIZE(joinBuffer)); if (ImGui::Button("加入游戏房间")) { uint64 hostID = std::stoull(joinBuffer); - CSteamID hostSteamID(hostID); - g_isClient = true; - g_hostSteamID = hostSteamID; - g_retryCount = 0; - g_currentVirtualPort = 0; - // Connect to host - SteamNetworkingIdentity identity; - identity.SetSteamID(hostSteamID); - g_hConnection = m_pInterface->ConnectP2P(identity, g_currentVirtualPort, 2, g_connectionConfig); - if (g_hConnection != k_HSteamNetConnection_Invalid) { - std::cout << "Attempting to connect to host " << hostSteamID.ConvertToUint64() << " with virtual port " << g_currentVirtualPort << std::endl; - // Connection initiated, wait for callback to confirm - std::cout << "Connecting to host..." << std::endl; + if (steamManager.joinHost(hostID)) { // Start TCP Server - server = std::make_unique(8888); + server = std::make_unique(8888, &steamManager); if (!server->start()) { std::cerr << "Failed to start TCP server" << std::endl; } } } + // Display available lobbies + if (!steamManager.getLobbies().empty()) { + ImGui::Text("可用房间:"); + for (const auto& lobbyID : steamManager.getLobbies()) { + std::string lobbyName = "房间 " + std::to_string(lobbyID.ConvertToUint64()); + if (ImGui::Button(lobbyName.c_str())) { + steamManager.joinLobby(lobbyID); + } + } + } } - if (g_isHost) { + if (steamManager.isHost()) { ImGui::Text("正在主持游戏房间。邀请朋友!"); ImGui::Separator(); ImGui::InputInt("本地端口", &localPort); ImGui::Separator(); - ImGui::InputText("过滤朋友", filterBuffer, IM_ARRAYSIZE(filterBuffer)); - ImGui::Text("朋友:"); - for (const auto& friendPair : friendsList) { - std::string nameStr = friendPair.second; - std::string filterStr(filterBuffer); - // Convert to lowercase for case-insensitive search - std::transform(nameStr.begin(), nameStr.end(), nameStr.begin(), ::tolower); - std::transform(filterStr.begin(), filterStr.end(), filterStr.begin(), ::tolower); - if (filterStr.empty() || nameStr.find(filterStr) != std::string::npos) { - ImGui::PushID(friendPair.first.ConvertToUint64()); - if (ImGui::Button(("邀请 " + friendPair.second).c_str())) { - // Send invite via Steam - SteamFriends()->InviteUserToGame(friendPair.first, "加入我的游戏房间!"); - } - ImGui::PopID(); - } - } + renderInviteFriends(); + } + if (steamManager.isConnected() && !steamManager.isHost()) { + ImGui::Text("已连接到游戏房间。邀请朋友!"); + ImGui::Separator(); + renderInviteFriends(); } ImGui::End(); // Room status window - only show when hosting or joined - if (g_isHost || g_isClient) { + if (steamManager.isHost() || steamManager.isClient()) { ImGui::Begin("房间状态"); if (server) { ImGui::Text("房间内玩家: %d", server->getClientCount() + 1); // +1 for host @@ -352,7 +172,7 @@ int main() { { std::lock_guard lock(clientMutex); std::lock_guard lockConn(connectionsMutex); - ImGui::Text("连接的好友: %d", (int)connections.size()); + ImGui::Text("连接的好友: %d", (int)steamManager.getConnections().size()); ImGui::Text("活跃的TCP客户端: %d", (int)clientMap.size()); } ImGui::Separator(); @@ -364,7 +184,7 @@ int main() { ImGui::TableHeadersRow(); { std::lock_guard lock(clientMutex); - for (const auto& pair : userMap) { + for (const auto& pair : steamManager.getUserMap()) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text("%s", pair.second.name.c_str()); @@ -393,21 +213,14 @@ int main() { } // Stop message handler - messageHandler.stop(); + steamManager.stopMessageHandler(); // Cleanup - if (g_hConnection != k_HSteamNetConnection_Invalid) { - m_pInterface->CloseConnection(g_hConnection, 0, nullptr, false); - } - if (hListenSock != k_HSteamListenSocket_Invalid) { - m_pInterface->CloseListenSocket(hListenSock); - } // Cleanup TCP Clients { std::lock_guard lock(clientMutex); for (auto& pair : clientMap) { pair.second->disconnect(); - delete pair.second; } clientMap.clear(); } @@ -419,7 +232,7 @@ int main() { ImGui::DestroyContext(); glfwDestroyWindow(window); glfwTerminate(); - SteamAPI_Shutdown(); + steamManager.shutdown(); return 0; } \ No newline at end of file diff --git a/control_packets.cpp b/steamnet/control_packets.cpp similarity index 100% rename from control_packets.cpp rename to steamnet/control_packets.cpp diff --git a/control_packets.h b/steamnet/control_packets.h similarity index 100% rename from control_packets.h rename to steamnet/control_packets.h diff --git a/steam_message_handler.cpp b/steamnet/steam_message_handler.cpp similarity index 79% rename from steam_message_handler.cpp rename to steamnet/steam_message_handler.cpp index 0dfcd8a..fafb91f 100644 --- a/steam_message_handler.cpp +++ b/steamnet/steam_message_handler.cpp @@ -10,13 +10,22 @@ const char* CONTROL_PREFIX = "CONTROL:"; const size_t CONTROL_PREFIX_LEN = 8; -SteamMessageHandler::SteamMessageHandler(boost::asio::io_context& io_context, ISteamNetworkingSockets* interface, std::vector& connections, std::map& clientMap, std::mutex& clientMutex, std::mutex& connectionsMutex, std::unique_ptr& server, bool& g_isHost, int& localPort) +SteamMessageHandler::SteamMessageHandler(boost::asio::io_context& io_context, ISteamNetworkingSockets* interface, std::vector& connections, std::map>& clientMap, std::mutex& clientMutex, std::mutex& connectionsMutex, std::unique_ptr& server, bool& g_isHost, int& localPort) : io_context_(io_context), m_pInterface_(interface), connections_(connections), clientMap_(clientMap), clientMutex_(clientMutex), connectionsMutex_(connectionsMutex), server_(server), g_isHost_(g_isHost), localPort_(localPort), running_(false) {} SteamMessageHandler::~SteamMessageHandler() { stop(); } +void SteamMessageHandler::handleControlPacket(const char* data, size_t size, HSteamNetConnection conn) { + std::string_view packetData(data, size); + std::cout << "Received control packet: " << packetData << " from connection " << conn << std::endl; + // Add handling logic here + if (packetData == "ping") { + std::cout << "Responding to ping" << std::endl; + } +} + void SteamMessageHandler::start() { if (running_) return; running_ = true; @@ -71,7 +80,7 @@ void SteamMessageHandler::pollMessages() { } // Lazy connect: Create TCP Client on first message if not already connected if (clientMap_.find(conn) == clientMap_.end() && g_isHost_ && localPort_ > 0) { - TCPClient* client = new TCPClient("localhost", localPort_); + auto client = std::make_shared("localhost", localPort_); if (client->connect()) { client->setReceiveCallback([conn, this](const char* data, size_t size) { std::lock_guard lock(clientMutex_); @@ -79,20 +88,19 @@ void SteamMessageHandler::pollMessages() { }); client->setDisconnectCallback([conn, this]() { std::lock_guard lock(clientMutex_); - m_pInterface_->CloseConnection(conn, 0, nullptr, false); - std::cout << "Closed Steam connection due to TCP client disconnect" << std::endl; + if (clientMap_.count(conn)) { + clientMap_[conn]->disconnect(); + clientMap_.erase(conn); + std::cout << "TCP client disconnected, removed from map" << std::endl; + } }); clientMap_[conn] = client; std::cout << "Created TCP Client for connection on first message" << std::endl; } else { std::cerr << "Failed to connect TCP Client for connection" << std::endl; - delete client; } } // Send to corresponding TCP client if exists (for host) - if (clientMap_.count(conn)) { - clientMap_[conn]->send((const char*)pIncomingMsg->m_pData, pIncomingMsg->m_cbSize); - } } pIncomingMsg->Release(); } diff --git a/steam_message_handler.h b/steamnet/steam_message_handler.h similarity index 67% rename from steam_message_handler.h rename to steamnet/steam_message_handler.h index 660ac72..ffdfdde 100644 --- a/steam_message_handler.h +++ b/steamnet/steam_message_handler.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include "tcp_server.h" @@ -13,7 +14,7 @@ class SteamMessageHandler { public: - SteamMessageHandler(boost::asio::io_context& io_context, ISteamNetworkingSockets* interface, std::vector& connections, std::map& clientMap, std::mutex& clientMutex, std::mutex& connectionsMutex, std::unique_ptr& server, bool& g_isHost, int& localPort); + SteamMessageHandler(boost::asio::io_context& io_context, ISteamNetworkingSockets* interface, std::vector& connections, std::map>& clientMap, std::mutex& clientMutex, std::mutex& connectionsMutex, std::unique_ptr& server, bool& g_isHost, int& localPort); ~SteamMessageHandler(); void start(); @@ -22,11 +23,12 @@ public: private: void run(); void pollMessages(); + void handleControlPacket(const char* data, size_t size, HSteamNetConnection conn); boost::asio::io_context& io_context_; ISteamNetworkingSockets* m_pInterface_; std::vector& connections_; - std::map& clientMap_; + std::map>& clientMap_; std::mutex& clientMutex_; std::mutex& connectionsMutex_; std::unique_ptr& server_; diff --git a/steamnet/steam_networking_manager.cpp b/steamnet/steam_networking_manager.cpp new file mode 100644 index 0000000..5f60374 --- /dev/null +++ b/steamnet/steam_networking_manager.cpp @@ -0,0 +1,292 @@ +#include "steam_networking_manager.h" +#include +#include + +SteamNetworkingManager* SteamNetworkingManager::instance = nullptr; + +// Static callback function +void SteamNetworkingManager::OnSteamNetConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t *pInfo) { + if (instance) { + instance->handleConnectionStatusChanged(pInfo); + } +} + +SteamFriendsCallbacks::SteamFriendsCallbacks(SteamNetworkingManager* manager) : manager_(manager) {} + +void SteamFriendsCallbacks::OnGameRichPresenceJoinRequested(GameRichPresenceJoinRequested_t *pCallback) { + if (manager_) { + const char* connectStr = SteamFriends()->GetFriendRichPresence(pCallback->m_steamIDFriend, "connect"); + if (connectStr && connectStr[0] != '\0') { + uint64 lobbyID = std::stoull(connectStr); + CSteamID lobbySteamID(lobbyID); + if (!manager_->isHost() && !manager_->isConnected()) { + manager_->joinLobby(lobbySteamID); + } + } + } +} + +SteamNetworkingManager::SteamNetworkingManager() + : m_pInterface(nullptr), hListenSock(k_HSteamListenSocket_Invalid), g_isHost(false), g_isClient(false), g_isConnected(false), + g_hConnection(k_HSteamNetConnection_Invalid), g_retryCount(0), g_currentVirtualPort(0), + io_context_(nullptr), clientMap_(nullptr), clientMutex_(nullptr), server_(nullptr), localPort_(nullptr), messageHandler_(nullptr), + steamFriendsCallbacks(this), steamMatchmakingCallbacks(this), currentLobby(k_steamIDNil) { + // Initialize connection config + g_connectionConfig[0].SetInt32(k_ESteamNetworkingConfig_TimeoutInitial, 10000); + g_connectionConfig[1].SetInt32(k_ESteamNetworkingConfig_NagleTime, 0); +} + +SteamNetworkingManager::~SteamNetworkingManager() { + stopMessageHandler(); + delete messageHandler_; + shutdown(); +} + +bool SteamNetworkingManager::initialize() { + instance = this; + if (!SteamAPI_Init()) { + std::cerr << "Failed to initialize Steam API" << std::endl; + return false; + } + + SteamNetworkingUtils()->InitRelayNetworkAccess(); + SteamNetworkingUtils()->SetGlobalCallback_SteamNetConnectionStatusChanged(OnSteamNetConnectionStatusChanged); + + m_pInterface = SteamNetworkingSockets(); + + // Get friends list + int friendCount = SteamFriends()->GetFriendCount(k_EFriendFlagAll); + for (int i = 0; i < friendCount; ++i) { + CSteamID friendID = SteamFriends()->GetFriendByIndex(i, k_EFriendFlagAll); + const char* name = SteamFriends()->GetFriendPersonaName(friendID); + friendsList.push_back({friendID, name}); + } + + return true; +} + +void SteamNetworkingManager::shutdown() { + leaveLobby(); + if (g_hConnection != k_HSteamNetConnection_Invalid) { + m_pInterface->CloseConnection(g_hConnection, 0, nullptr, false); + } + if (hListenSock != k_HSteamListenSocket_Invalid) { + m_pInterface->CloseListenSocket(hListenSock); + } + SteamAPI_Shutdown(); +} + +bool SteamNetworkingManager::createLobby() { + SteamAPICall_t hSteamAPICall = SteamMatchmaking()->CreateLobby(k_ELobbyTypePublic, 4); + if (hSteamAPICall == k_uAPICallInvalid) { + std::cerr << "Failed to create lobby" << std::endl; + return false; + } + // Call result will be handled by callback + return true; +} + +void SteamNetworkingManager::leaveLobby() { + if (currentLobby != k_steamIDNil) { + SteamMatchmaking()->LeaveLobby(currentLobby); + currentLobby = k_steamIDNil; + } +} + +bool SteamNetworkingManager::searchLobbies() { + lobbies.clear(); + SteamAPICall_t hSteamAPICall = SteamMatchmaking()->RequestLobbyList(); + if (hSteamAPICall == k_uAPICallInvalid) { + std::cerr << "Failed to request lobby list" << std::endl; + return false; + } + // Results will be handled by callback + return true; +} + +bool SteamNetworkingManager::joinLobby(CSteamID lobbyID) { + if (SteamMatchmaking()->JoinLobby(lobbyID) != k_EResultOK) { + std::cerr << "Failed to join lobby" << std::endl; + return false; + } + // Connection will be handled by callback + return true; +} + +bool SteamNetworkingManager::startHosting() { + if (!createLobby()) { + return false; + } + hListenSock = m_pInterface->CreateListenSocketP2P(0, 0, nullptr); + if (hListenSock != k_HSteamListenSocket_Invalid) { + g_isHost = true; + std::cout << "Created listen socket for hosting game room" << std::endl; + // Rich Presence is set in OnLobbyCreated callback + return true; + } else { + std::cerr << "Failed to create listen socket for hosting" << std::endl; + leaveLobby(); + return false; + } +} + +void SteamNetworkingManager::stopHosting() { + if (hListenSock != k_HSteamListenSocket_Invalid) { + m_pInterface->CloseListenSocket(hListenSock); + hListenSock = k_HSteamListenSocket_Invalid; + } + leaveLobby(); + g_isHost = false; +} + +bool SteamNetworkingManager::joinHost(uint64 hostID) { + CSteamID hostSteamID(hostID); + g_isClient = true; + g_hostSteamID = hostSteamID; + g_retryCount = 0; + g_currentVirtualPort = 0; + SteamNetworkingIdentity identity; + identity.SetSteamID(hostSteamID); + g_hConnection = m_pInterface->ConnectP2P(identity, g_currentVirtualPort, 2, g_connectionConfig); + if (g_hConnection != k_HSteamNetConnection_Invalid) { + std::cout << "Attempting to connect to host " << hostSteamID.ConvertToUint64() << " with virtual port " << g_currentVirtualPort << std::endl; + return true; + } else { + std::cerr << "Failed to initiate connection" << std::endl; + return false; + } +} + +void SteamNetworkingManager::setMessageHandlerDependencies(boost::asio::io_context& io_context, std::map>& clientMap, std::mutex& clientMutex, std::unique_ptr& server, int& localPort) { + io_context_ = &io_context; + clientMap_ = &clientMap; + clientMutex_ = &clientMutex; + server_ = &server; + localPort_ = &localPort; + messageHandler_ = new SteamMessageHandler(io_context, m_pInterface, connections, clientMap, clientMutex, connectionsMutex, server, g_isHost, localPort); +} + +void SteamNetworkingManager::startMessageHandler() { + if (messageHandler_) { + messageHandler_->start(); + } +} + +void SteamNetworkingManager::stopMessageHandler() { + if (messageHandler_) { + messageHandler_->stop(); + } +} + +void SteamNetworkingManager::handleConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t *pInfo) { + std::lock_guard lock(connectionsMutex); + std::cout << "Connection status changed: " << pInfo->m_info.m_eState << " for connection " << pInfo->m_hConn << std::endl; + if (pInfo->m_info.m_eState == k_ESteamNetworkingConnectionState_ProblemDetectedLocally) { + std::cout << "Connection failed: " << pInfo->m_info.m_szEndDebug << std::endl; + } + if (pInfo->m_eOldState == k_ESteamNetworkingConnectionState_None && pInfo->m_info.m_eState == k_ESteamNetworkingConnectionState_Connecting) { + m_pInterface->AcceptConnection(pInfo->m_hConn); + connections.push_back(pInfo->m_hConn); + g_hConnection = pInfo->m_hConn; + g_isConnected = true; + std::cout << "Accepted incoming connection from " << pInfo->m_info.m_identityRemote.GetSteamID().ConvertToUint64() << std::endl; + // Add user info + SteamNetConnectionInfo_t info; + SteamNetConnectionRealTimeStatus_t status; + if (m_pInterface->GetConnectionInfo(pInfo->m_hConn, &info) && m_pInterface->GetConnectionRealTimeStatus(pInfo->m_hConn, &status, 0, nullptr)) { + UserInfo userInfo; + userInfo.steamID = pInfo->m_info.m_identityRemote.GetSteamID(); + userInfo.name = SteamFriends()->GetFriendPersonaName(userInfo.steamID); + userInfo.ping = status.m_nPing; + userInfo.isRelay = (info.m_idPOPRelay != 0); + userMap[pInfo->m_hConn] = userInfo; + std::cout << "Incoming connection details: ping=" << status.m_nPing << "ms, relay=" << (info.m_idPOPRelay != 0 ? "yes" : "no") << std::endl; + } + } else if (pInfo->m_eOldState == k_ESteamNetworkingConnectionState_Connecting && pInfo->m_info.m_eState == k_ESteamNetworkingConnectionState_Connected) { + g_isConnected = true; + std::cout << "Connected to host" << std::endl; + // Add user info + SteamNetConnectionInfo_t info; + SteamNetConnectionRealTimeStatus_t status; + if (m_pInterface->GetConnectionInfo(pInfo->m_hConn, &info) && m_pInterface->GetConnectionRealTimeStatus(pInfo->m_hConn, &status, 0, nullptr)) { + UserInfo userInfo; + userInfo.steamID = pInfo->m_info.m_identityRemote.GetSteamID(); + userInfo.name = SteamFriends()->GetFriendPersonaName(userInfo.steamID); + userInfo.ping = status.m_nPing; + userInfo.isRelay = (info.m_idPOPRelay != 0); + userMap[pInfo->m_hConn] = userInfo; + std::cout << "Outgoing connection details: ping=" << status.m_nPing << "ms, relay=" << (info.m_idPOPRelay != 0 ? "yes" : "no") << std::endl; + } + } else if (pInfo->m_info.m_eState == k_ESteamNetworkingConnectionState_ClosedByPeer || pInfo->m_info.m_eState == k_ESteamNetworkingConnectionState_ProblemDetectedLocally) { + g_isConnected = false; + g_hConnection = k_HSteamNetConnection_Invalid; + // Remove from connections + auto it = std::find(connections.begin(), connections.end(), pInfo->m_hConn); + if (it != connections.end()) { + connections.erase(it); + } + userMap.erase(pInfo->m_hConn); + std::cout << "Connection closed" << std::endl; + + // Retry if client + if (g_isClient && !g_isConnected && g_retryCount < MAX_RETRIES) { + g_retryCount++; + g_currentVirtualPort++; + SteamNetworkingIdentity identity; + identity.SetSteamID(g_hostSteamID); + HSteamNetConnection newConn = m_pInterface->ConnectP2P(identity, g_currentVirtualPort, 2, g_connectionConfig); + if (newConn != k_HSteamNetConnection_Invalid) { + g_hConnection = newConn; + std::cout << "Retrying connection attempt " << g_retryCount << " with virtual port " << g_currentVirtualPort << std::endl; + } else { + std::cerr << "Failed to initiate retry connection" << std::endl; + } + } + } +} + +SteamMatchmakingCallbacks::SteamMatchmakingCallbacks(SteamNetworkingManager* manager) : manager_(manager) {} + +void SteamMatchmakingCallbacks::OnLobbyCreated(LobbyCreated_t *pCallback) { + if (pCallback->m_eResult == k_EResultOK) { + manager_->currentLobby = pCallback->m_ulSteamIDLobby; + std::cout << "Lobby created: " << manager_->currentLobby.ConvertToUint64() << std::endl; + // Set Rich Presence with lobby ID + std::string lobbyStr = std::to_string(manager_->currentLobby.ConvertToUint64()); + SteamFriends()->SetRichPresence("connect", lobbyStr.c_str()); + SteamFriends()->SetRichPresence("status", "主持游戏房间"); + } else { + std::cerr << "Failed to create lobby" << std::endl; + } +} + +void SteamMatchmakingCallbacks::OnLobbyListReceived(LobbyMatchList_t *pCallback) { + manager_->lobbies.clear(); + for (uint32 i = 0; i < pCallback->m_nLobbiesMatching; ++i) { + CSteamID lobbyID = SteamMatchmaking()->GetLobbyByIndex(i); + manager_->lobbies.push_back(lobbyID); + } + std::cout << "Received " << pCallback->m_nLobbiesMatching << " lobbies" << std::endl; +} + +void SteamMatchmakingCallbacks::OnLobbyEntered(LobbyEnter_t *pCallback) { + if (pCallback->m_EChatRoomEnterResponse == k_EChatRoomEnterResponseSuccess) { + manager_->currentLobby = pCallback->m_ulSteamIDLobby; + std::cout << "Entered lobby: " << pCallback->m_ulSteamIDLobby << std::endl; + // Only join host if not the host + if (!manager_->isHost()) { + CSteamID hostID = SteamMatchmaking()->GetLobbyOwner(pCallback->m_ulSteamIDLobby); + if (manager_->joinHost(hostID.ConvertToUint64())) { + // Start TCP Server if dependencies are set + if (manager_->server_ && !(*manager_->server_)) { + *manager_->server_ = std::make_unique(8888, manager_); + if (!(*manager_->server_)->start()) { + std::cerr << "Failed to start TCP server" << std::endl; + } + } + } + } + } else { + std::cerr << "Failed to enter lobby" << std::endl; + } +} \ No newline at end of file diff --git a/steamnet/steam_networking_manager.h b/steamnet/steam_networking_manager.h new file mode 100644 index 0000000..ba0b63d --- /dev/null +++ b/steamnet/steam_networking_manager.h @@ -0,0 +1,141 @@ +#ifndef STEAM_NETWORKING_MANAGER_H +#define STEAM_NETWORKING_MANAGER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "steam_message_handler.h" + +// Forward declarations +class TCPClient; +class TCPServer; +class SteamNetworkingManager; + +// Callback class for Steam Friends +class SteamFriendsCallbacks { +public: + SteamFriendsCallbacks(SteamNetworkingManager* manager); + STEAM_CALLBACK(SteamFriendsCallbacks, OnGameRichPresenceJoinRequested, GameRichPresenceJoinRequested_t); +private: + SteamNetworkingManager* manager_; +}; + +// Callback class for Steam Matchmaking +class SteamMatchmakingCallbacks { +public: + SteamMatchmakingCallbacks(SteamNetworkingManager* manager); + STEAM_CALLBACK(SteamMatchmakingCallbacks, OnLobbyCreated, LobbyCreated_t); + STEAM_CALLBACK(SteamMatchmakingCallbacks, OnLobbyListReceived, LobbyMatchList_t); + STEAM_CALLBACK(SteamMatchmakingCallbacks, OnLobbyEntered, LobbyEnter_t); +private: + SteamNetworkingManager* manager_; +}; + +// User info structure +struct UserInfo { + CSteamID steamID; + std::string name; + int ping; + bool isRelay; +}; + +class SteamNetworkingManager { +public: + static SteamNetworkingManager* instance; + SteamNetworkingManager(); + ~SteamNetworkingManager(); + + bool initialize(); + void shutdown(); + + // Hosting + bool startHosting(); + void stopHosting(); + + // Lobby + bool createLobby(); + void leaveLobby(); + bool searchLobbies(); + bool joinLobby(CSteamID lobbyID); + const std::vector& getLobbies() const { return lobbies; } + CSteamID getCurrentLobby() const { return currentLobby; } + + // Joining + bool joinHost(uint64 hostID); + void disconnect(); + + // Getters + bool isHost() const { return g_isHost; } + bool isClient() const { return g_isClient; } + bool isConnected() const { return g_isConnected; } + const std::vector>& getFriendsList() const { return friendsList; } + const std::map& getUserMap() const { return userMap; } + const std::vector& getConnections() const { return connections; } + HSteamNetConnection getConnection() const { return g_hConnection; } + ISteamNetworkingSockets* getInterface() const { return m_pInterface; } + + void setMessageHandlerDependencies(boost::asio::io_context& io_context, std::map>& clientMap, std::mutex& clientMutex, std::unique_ptr& server, int& localPort); + + // Message handler + void startMessageHandler(); + void stopMessageHandler(); + + // For callbacks + void setHostSteamID(CSteamID id) { g_hostSteamID = id; } + CSteamID getHostSteamID() const { return g_hostSteamID; } + + friend class SteamFriendsCallbacks; + friend class SteamMatchmakingCallbacks; + +private: + // Steam API + ISteamNetworkingSockets* m_pInterface; + + // Hosting + HSteamListenSocket hListenSock; + bool g_isHost; + bool g_isClient; + bool g_isConnected; + HSteamNetConnection g_hConnection; + CSteamID g_hostSteamID; + + // Lobby + std::vector lobbies; + CSteamID currentLobby; + + // Connections + std::vector connections; + std::map userMap; + std::mutex connectionsMutex; + + // Connection config + SteamNetworkingConfigValue_t g_connectionConfig[2]; + int g_retryCount; + const int MAX_RETRIES = 3; + int g_currentVirtualPort; + + // Friends + std::vector> friendsList; + SteamFriendsCallbacks steamFriendsCallbacks; + SteamMatchmakingCallbacks steamMatchmakingCallbacks; + + // Message handler dependencies + boost::asio::io_context* io_context_; + std::map>* clientMap_; + std::mutex* clientMutex_; + std::unique_ptr* server_; + int* localPort_; + SteamMessageHandler* messageHandler_; + + // Callback + static void OnSteamNetConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t *pInfo); + void handleConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t *pInfo); +}; + +#endif // STEAM_NETWORKING_MANAGER_H \ No newline at end of file diff --git a/tcp/tcp_client.cpp b/tcp/tcp_client.cpp index efe7eff..5cd39b3 100644 --- a/tcp/tcp_client.cpp +++ b/tcp/tcp_client.cpp @@ -28,16 +28,21 @@ bool TCPClient::connect() { void TCPClient::disconnect() { if (disconnected_) return; disconnected_ = true; - if (disconnectCallback_) { - disconnectCallback_(); - } connected_ = false; io_context_.stop(); if (clientThread_.joinable()) { - clientThread_.join(); + if (clientThread_.get_id() == std::this_thread::get_id()) { + clientThread_.detach(); + } else { + clientThread_.join(); + } } - if (socket_->is_open()) { - socket_->close(); + try { + if (socket_->is_open()) { + socket_->close(); + } + } catch (const std::exception& e) { + std::cerr << "Error closing socket: " << e.what() << std::endl; } } @@ -90,6 +95,9 @@ void TCPClient::handle_read(const boost::system::error_code& error, std::size_t } else { std::cerr << "Read failed: " << error.message() << std::endl; } + if (disconnectCallback_) { + disconnectCallback_(); + } disconnect(); } } \ No newline at end of file diff --git a/tcp/tcp_server.cpp b/tcp/tcp_server.cpp index 655a579..dddc2f3 100644 --- a/tcp/tcp_server.cpp +++ b/tcp/tcp_server.cpp @@ -1,8 +1,9 @@ #include "tcp_server.h" +#include "../steamnet/steam_networking_manager.h" #include #include -TCPServer::TCPServer(int port) : port_(port), running_(false), acceptor_(io_context_), work_(boost::asio::make_work_guard(io_context_)), hasAcceptedConnection_(false) {} +TCPServer::TCPServer(int port, SteamNetworkingManager* manager) : port_(port), running_(false), acceptor_(io_context_), work_(boost::asio::make_work_guard(io_context_)), hasAcceptedConnection_(false), manager_(manager), forwarding_(false) {} TCPServer::~TCPServer() { stop(); } @@ -80,18 +81,21 @@ void TCPServer::start_read(std::shared_ptr socket) { auto buffer = std::make_shared>(1024); socket->async_read_some(boost::asio::buffer(*buffer), [this, socket, buffer](const boost::system::error_code& error, std::size_t bytes_transferred) { if (!error) { - // std::cout << "Received " << bytes_transferred << " bytes from client" << std::endl; - if (!forwarding) { - forwarding = true; - if (g_isConnected) { - m_pInterface->SendMessageToConnection(g_hConnection, buffer->data(), bytes_transferred, k_nSteamNetworkingSend_Reliable, nullptr); + std::cout << "Received " << bytes_transferred << " bytes from TCP client" << std::endl; + if (!forwarding_) { + forwarding_ = true; + if (manager_->isConnected()) { + std::cout << "Forwarding TCP message to Steam connection" << std::endl; + manager_->getInterface()->SendMessageToConnection(manager_->getConnection(), buffer->data(), bytes_transferred, k_nSteamNetworkingSend_Reliable, nullptr); + } else { + std::cout << "Not connected to Steam, skipping forward" << std::endl; } - forwarding = false; + forwarding_ = false; } sendToAll(buffer->data(), bytes_transferred, socket); start_read(socket); } else { - std::cout << "Client disconnected" << std::endl; + std::cout << "TCP client disconnected or error: " << error.message() << std::endl; // Remove client std::lock_guard lock(clientsMutex_); clients_.erase(std::remove(clients_.begin(), clients_.end(), socket), clients_.end()); diff --git a/tcp/tcp_server.h b/tcp/tcp_server.h index a599dd1..ef0e1bd 100644 --- a/tcp/tcp_server.h +++ b/tcp/tcp_server.h @@ -10,18 +10,14 @@ #include #include -using boost::asio::ip::tcp; +class SteamNetworkingManager; -// Extern declarations for global variables used in TCPServer -extern HSteamNetConnection g_hConnection; -extern bool g_isConnected; -extern ISteamNetworkingSockets* m_pInterface; -extern bool forwarding; +using boost::asio::ip::tcp; // TCP Server class class TCPServer { public: - TCPServer(int port); + TCPServer(int port, SteamNetworkingManager* manager); ~TCPServer(); bool start(); @@ -43,4 +39,6 @@ private: std::mutex clientsMutex_; std::thread serverThread_; bool hasAcceptedConnection_; + SteamNetworkingManager* manager_; + bool forwarding_; }; \ No newline at end of file