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.
This commit is contained in:
Ayndpa
2025-11-18 21:03:01 +08:00
parent 676d39d6a2
commit af181bb133
10 changed files with 547 additions and 281 deletions

View File

@@ -10,207 +10,36 @@
#include <map>
#include <algorithm>
#include <cstring>
#include <steam_api.h>
#include <isteamnetworkingsockets.h>
#include <isteamnetworkingutils.h>
#include <steamnetworkingtypes.h>
#include <boost/asio.hpp>
#include <memory>
#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<TCPServer> 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<HSteamNetConnection> connections;
std::map<HSteamNetConnection, TCPClient*> clientMap;
std::map<HSteamNetConnection, std::shared_ptr<TCPClient>> 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<HSteamNetConnection, UserInfo> userMap;
// Callback function for connection status changes
void OnSteamNetConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t *pInfo)
{
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<TCPServer>(8888);
if (!server->start()) {
std::cerr << "Failed to start TCP server" << std::endl;
}
}
}
}
std::unique_ptr<TCPServer> 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<std::pair<CSteamID, std::string>> 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<TCPServer>(8888);
server = std::make_unique<TCPServer>(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<std::mutex> lock(clientMutex);
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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;
}

View File

@@ -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<HSteamNetConnection>& connections, std::map<HSteamNetConnection, TCPClient*>& clientMap, std::mutex& clientMutex, std::mutex& connectionsMutex, std::unique_ptr<TCPServer>& server, bool& g_isHost, int& localPort)
SteamMessageHandler::SteamMessageHandler(boost::asio::io_context& io_context, ISteamNetworkingSockets* interface, std::vector<HSteamNetConnection>& connections, std::map<HSteamNetConnection, std::shared_ptr<TCPClient>>& clientMap, std::mutex& clientMutex, std::mutex& connectionsMutex, std::unique_ptr<TCPServer>& 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<TCPClient>("localhost", localPort_);
if (client->connect()) {
client->setReceiveCallback([conn, this](const char* data, size_t size) {
std::lock_guard<std::mutex> lock(clientMutex_);
@@ -79,20 +88,19 @@ void SteamMessageHandler::pollMessages() {
});
client->setDisconnectCallback([conn, this]() {
std::lock_guard<std::mutex> 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();
}

View File

@@ -5,6 +5,7 @@
#include <map>
#include <mutex>
#include <thread>
#include <memory>
#include <boost/asio.hpp>
#include <steamnetworkingtypes.h>
#include "tcp_server.h"
@@ -13,7 +14,7 @@
class SteamMessageHandler {
public:
SteamMessageHandler(boost::asio::io_context& io_context, ISteamNetworkingSockets* interface, std::vector<HSteamNetConnection>& connections, std::map<HSteamNetConnection, TCPClient*>& clientMap, std::mutex& clientMutex, std::mutex& connectionsMutex, std::unique_ptr<TCPServer>& server, bool& g_isHost, int& localPort);
SteamMessageHandler(boost::asio::io_context& io_context, ISteamNetworkingSockets* interface, std::vector<HSteamNetConnection>& connections, std::map<HSteamNetConnection, std::shared_ptr<TCPClient>>& clientMap, std::mutex& clientMutex, std::mutex& connectionsMutex, std::unique_ptr<TCPServer>& 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<HSteamNetConnection>& connections_;
std::map<HSteamNetConnection, TCPClient*>& clientMap_;
std::map<HSteamNetConnection, std::shared_ptr<TCPClient>>& clientMap_;
std::mutex& clientMutex_;
std::mutex& connectionsMutex_;
std::unique_ptr<TCPServer>& server_;

View File

@@ -0,0 +1,292 @@
#include "steam_networking_manager.h"
#include <iostream>
#include <algorithm>
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<HSteamNetConnection, std::shared_ptr<TCPClient>>& clientMap, std::mutex& clientMutex, std::unique_ptr<TCPServer>& 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<std::mutex> 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<TCPServer>(8888, manager_);
if (!(*manager_->server_)->start()) {
std::cerr << "Failed to start TCP server" << std::endl;
}
}
}
}
} else {
std::cerr << "Failed to enter lobby" << std::endl;
}
}

View File

@@ -0,0 +1,141 @@
#ifndef STEAM_NETWORKING_MANAGER_H
#define STEAM_NETWORKING_MANAGER_H
#include <vector>
#include <map>
#include <mutex>
#include <memory>
#include <steam_api.h>
#include <isteamnetworkingsockets.h>
#include <isteamnetworkingutils.h>
#include <steamnetworkingtypes.h>
#include <isteammatchmaking.h>
#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<CSteamID>& 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<std::pair<CSteamID, std::string>>& getFriendsList() const { return friendsList; }
const std::map<HSteamNetConnection, UserInfo>& getUserMap() const { return userMap; }
const std::vector<HSteamNetConnection>& 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<HSteamNetConnection, std::shared_ptr<TCPClient>>& clientMap, std::mutex& clientMutex, std::unique_ptr<TCPServer>& 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<CSteamID> lobbies;
CSteamID currentLobby;
// Connections
std::vector<HSteamNetConnection> connections;
std::map<HSteamNetConnection, UserInfo> 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<std::pair<CSteamID, std::string>> friendsList;
SteamFriendsCallbacks steamFriendsCallbacks;
SteamMatchmakingCallbacks steamMatchmakingCallbacks;
// Message handler dependencies
boost::asio::io_context* io_context_;
std::map<HSteamNetConnection, std::shared_ptr<TCPClient>>* clientMap_;
std::mutex* clientMutex_;
std::unique_ptr<TCPServer>* server_;
int* localPort_;
SteamMessageHandler* messageHandler_;
// Callback
static void OnSteamNetConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t *pInfo);
void handleConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t *pInfo);
};
#endif // STEAM_NETWORKING_MANAGER_H

View File

@@ -28,17 +28,22 @@ bool TCPClient::connect() {
void TCPClient::disconnect() {
if (disconnected_) return;
disconnected_ = true;
if (disconnectCallback_) {
disconnectCallback_();
}
connected_ = false;
io_context_.stop();
if (clientThread_.joinable()) {
if (clientThread_.get_id() == std::this_thread::get_id()) {
clientThread_.detach();
} else {
clientThread_.join();
}
}
try {
if (socket_->is_open()) {
socket_->close();
}
} catch (const std::exception& e) {
std::cerr << "Error closing socket: " << e.what() << std::endl;
}
}
void TCPClient::send(const std::string& message) {
@@ -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();
}
}

View File

@@ -1,8 +1,9 @@
#include "tcp_server.h"
#include "../steamnet/steam_networking_manager.h"
#include <iostream>
#include <algorithm>
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<tcp::socket> socket) {
auto buffer = std::make_shared<std::vector<char>>(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<std::mutex> lock(clientsMutex_);
clients_.erase(std::remove(clients_.begin(), clients_.end(), socket), clients_.end());

View File

@@ -10,18 +10,14 @@
#include <isteamnetworkingutils.h>
#include <steamnetworkingtypes.h>
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_;
};