2026-05-09 16:57:17 +08:00

673 lines
27 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using Logic.CrashSight;
using Logic.Pool;
using MemoryPack;
using Steamworks;
using TH1_Logic.Net;
using UnityEngine;
namespace TH1_Logic.Steam
{
public class SimpleP2P
{
public static SimpleP2P Instance { get; } = new SimpleP2P();
// 推荐的虚拟端口范围
private static readonly int TargetPort = 1214;
// 连接映射表SteamID -> 连接句柄
private readonly Dictionary<CSteamID, HSteamNetConnection> _connections = new Dictionary<CSteamID, HSteamNetConnection>();
// 连接超时追踪
private readonly Dictionary<CSteamID, float> _connectionTimeouts = new Dictionary<CSteamID, float>();
private const float ConnectionTimeout = 30f; // 30秒超时
// 重连尝试计数
private readonly Dictionary<CSteamID, int> _retryCount = new Dictionary<CSteamID, int>();
private const int MaxRetryCount = 3;
// 监听套接字
private float _socketRecord;
private HSteamListenSocket _listenSocket;
// 回调
private Callback<SteamNetConnectionStatusChangedCallback_t> _cbConnectionStatusChanged;
private Callback<SteamNetworkingMessagesSessionRequest_t> _cbSessionRequest;
// 事件委托
public event Action<CSteamID> OnPeerConnectedEvent;
public event Action<CSteamID> OnPeerDisconnectedEvent;
public event Action<CSteamID, byte[]> OnMessageReceivedEvent;
public event Action<string> OnConnectionErrorEvent;
public bool IsInitialized => _listenSocket != HSteamListenSocket.Invalid;
// 初始化
public void Initialize()
{
_socketRecord = 3;
_listenSocket = HSteamListenSocket.Invalid;
_cbConnectionStatusChanged = Callback<SteamNetConnectionStatusChangedCallback_t>.Create(OnConnectionStatusChanged);
// 添加:注册 SteamNetworkingMessages 会话请求回调
_cbSessionRequest = Callback<SteamNetworkingMessagesSessionRequest_t>.Create(OnSessionRequest);
}
// 添加:处理 SteamNetworkingMessages 的会话请求
private void OnSessionRequest(SteamNetworkingMessagesSessionRequest_t callback)
{
var remoteSteamID = callback.m_identityRemote.GetSteamID();
LogSystem.LogInfo($"收到来自 {remoteSteamID} 的 NetworkingMessages 会话请求");
// 接受会话请求(必须调用,否则对方发送会失败)
SteamNetworkingMessages.AcceptSessionWithUser(ref callback.m_identityRemote);
LogSystem.LogInfo($"已接受来自 {remoteSteamID} 的会话");
}
// 创建监听套接字
private void CreateListenSocket()
{
ForceCleanupPort(TargetPort);
LogSystem.LogInfo($"尝试虚拟端口 {TargetPort}...");
_listenSocket = SteamNetworkingSockets.CreateListenSocketP2P(TargetPort, 0, null);
if (_listenSocket != HSteamListenSocket.Invalid)
{
LogSystem.LogInfo($"成功在虚拟端口 {TargetPort} 创建监听套接字: {_listenSocket}");
}
}
// 定时刷新套接字创建
private void RefreshListenSocket()
{
_socketRecord += Time.deltaTime;
if (_listenSocket == HSteamListenSocket.Invalid && _socketRecord > 3f)
{
_socketRecord = 0;
SteamNetworkingUtils.GetRelayNetworkStatus(out var relayStatus);
if (relayStatus.m_eAvail == ESteamNetworkingAvailability.k_ESteamNetworkingAvailability_Current)
{
CreateListenSocket();
}
}
}
private void ForceCleanupPort(int port)
{
LogSystem.LogInfo($"强制清理虚拟端口 {port}");
// 关闭所有现有连接
DisconnectAll();
// 关闭当前监听套接字
SteamNetworkingSockets.CloseListenSocket(_listenSocket);
_listenSocket = HSteamListenSocket.Invalid;
LogSystem.LogInfo("已关闭现有监听套接字");
// 强制清理特定端口的所有连接
SteamNetworkingSockets.RunCallbacks();
}
// 连接到指定玩家
public bool ConnectToPeer(CSteamID steamID)
{
if (LobbyManager.Instance.Lobby.IsLobbyOwner()) return false;
if (_connections.ContainsKey(steamID))
{
LogSystem.LogInfo($"Already connected to {steamID}");
return true;
}
// 先进行连接前检查
if (!PreConnectionCheck(steamID))
{
return false;
}
// 创建连接配置
var options = new SteamNetworkingConfigValue_t[1];
options[0] = new SteamNetworkingConfigValue_t();
options[0].m_eValue = ESteamNetworkingConfigValue.k_ESteamNetworkingConfig_TimeoutInitial;
options[0].m_eDataType = ESteamNetworkingConfigDataType.k_ESteamNetworkingConfig_Int32;
options[0].m_val.m_int32 = 15000; // 15秒超时
// 创建到目标玩家的连接
var identity = new SteamNetworkingIdentity();
identity.SetSteamID(steamID);
var connection = SteamNetworkingSockets.ConnectP2P(ref identity, TargetPort, options.Length, options);
if (connection == HSteamNetConnection.Invalid)
{
OnConnectionErrorEvent?.Invoke($"Failed to connect to {steamID}");
return false;
}
_connections[steamID] = connection;
_connectionTimeouts[steamID] = Time.time + ConnectionTimeout;
LogSystem.LogInfo($"Connecting to peer: {steamID}");
DiagnoseNetworkStatus(steamID);
return true;
}
// 连接前检查
private bool PreConnectionCheck(CSteamID targetSteamID)
{
// 检查Steam是否已登录
if (!SteamUser.BLoggedOn())
{
LogSystem.LogError("Steam not logged in");
return false;
}
// 检查目标用户是否在线
var friendState = SteamFriends.GetFriendPersonaState(targetSteamID);
if (friendState == EPersonaState.k_EPersonaStateOffline)
{
LogSystem.LogWarning($"Target user {targetSteamID} appears offline");
}
// 检查当前lobby信息
LogSystem.LogInfo("Checking lobby status...");
return true;
}
private void DiagnoseNetworkStatus(CSteamID targetSteamID)
{
LogSystem.LogInfo("=== P2P 连接诊断 ===");
// Steam 状态
LogSystem.LogInfo($"Steam 登录状态: {SteamUser.BLoggedOn()}");
LogSystem.LogInfo($"本地 Steam ID: {SteamUser.GetSteamID()}");
// 目标用户状态
var friendState = SteamFriends.GetFriendPersonaState(targetSteamID);
LogSystem.LogInfo($"目标用户状态: {friendState}");
// 检查是否是好友
var relationShip = SteamFriends.GetFriendRelationship(targetSteamID);
LogSystem.LogInfo($"好友关系: {relationShip}");
// Steam 网络状态
SteamNetworkingUtils.GetRelayNetworkStatus(out var relayStatus);
LogSystem.LogInfo($"Steam 中继网络: {relayStatus.m_eAvail}");
LogSystem.LogInfo($"中继网络调试信息: {relayStatus.m_debugMsg}");
if (relayStatus.m_eAvail != ESteamNetworkingAvailability.k_ESteamNetworkingAvailability_Current)
{
LogSystem.LogWarning($"Steam 中继网络问题: {relayStatus.m_debugMsg}");
}
}
// 更新方法 - 需要在主循环中调用
public void Update()
{
RefreshListenSocket();
CheckConnectionTimeouts();
}
// 检查连接超时
private void CheckConnectionTimeouts()
{
var currentTime = Time.time;
var timeoutList = new List<CSteamID>();
foreach (var kvp in _connectionTimeouts)
{
if (currentTime > kvp.Value)
{
timeoutList.Add(kvp.Key);
}
}
foreach (var steamID in timeoutList)
{
LogSystem.LogError($"Connection to {steamID} timed out");
_connectionTimeouts.Remove(steamID);
if (_connections.TryGetValue(steamID, out var conn))
{
SteamNetworkingSockets.CloseConnection(conn, 0, "Connection timeout", false);
_connections.Remove(steamID);
}
OnConnectionErrorEvent?.Invoke($"Connection to {steamID} timed out");
}
}
// 断开与指定玩家的连接
public void DisconnectFromPeer(CSteamID steamID)
{
if (!_connections.TryGetValue(steamID, out var connection))
return;
SteamNetworkingSockets.CloseConnection(connection, 0, "Disconnected by user", false);
_connections.Remove(steamID);
_connectionTimeouts.Remove(steamID);
LogSystem.LogInfo($"Disconnected from peer: {steamID}");
}
// 断开所有连接
public void DisconnectAll()
{
foreach (var kvp in _connections)
{
SteamNetworkingSockets.CloseConnection(kvp.Value, 0, "Disconnecting all", false);
}
_connections.Clear();
_connectionTimeouts.Clear();
_retryCount.Clear();
LogSystem.LogInfo("Disconnected from all peers");
}
// 连接状态变化回调
private void OnConnectionStatusChanged(SteamNetConnectionStatusChangedCallback_t info)
{
var state = info.m_info.m_eState;
// 获取连接信息
SteamNetworkingSockets.GetConnectionInfo(info.m_hConn, out var connectionInfo);
var remote = connectionInfo.m_identityRemote.GetSteamID();
// 清除超时计时器(如果连接状态有变化)
if (_connectionTimeouts.ContainsKey(remote) &&
state != ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_Connecting)
{
_connectionTimeouts.Remove(remote);
}
// 记录详细的连接信息用于诊断
LogSystem.LogInfo($"Connection details - Remote: {remote}, State: {state}");
LogSystem.LogInfo($"End reason: {info.m_info.m_eEndReason}");
switch (state)
{
case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_Connecting:
LogSystem.LogInfo($"Connecting to {remote}...");
// 检查这是否是传入连接(对方主动连接我们)
// 如果我们的连接字典中没有这个连接,说明是对方主动连接的
if (!_connections.ContainsKey(remote) && info.m_info.m_hListenSocket != HSteamListenSocket.Invalid)
{
LogSystem.LogInfo($"检测到来自 {remote} 的传入连接请求");
// 检查是否应该接受这个连接
if (ShouldAcceptIncomingConnection(remote))
{
LogSystem.LogInfo($"接受来自 {remote} 的连接");
var result = SteamNetworkingSockets.AcceptConnection(info.m_hConn);
if (result != EResult.k_EResultOK)
{
LogSystem.LogError($"无法接受连接: {result}");
SteamNetworkingSockets.CloseConnection(info.m_hConn, 0, "Failed to accept", false);
return;
}
_connections[remote] = info.m_hConn;
}
else
{
LogSystem.LogInfo($"拒绝来自 {remote} 的连接");
SteamNetworkingSockets.CloseConnection(info.m_hConn, 0, "Connection rejected", false);
}
}
else
{
LogSystem.LogInfo($"这是我们主动发起的连接到 {remote}");
}
break;
case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_FindingRoute:
LogSystem.LogInfo($"Finding route to {remote}...");
// 这个状态通常意味着正在通过Steam中继寻找路由
LogSystem.LogInfo("正在通过Steam中继网络寻找连接路径...");
break;
case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_Connected:
LogSystem.LogInfo($"Successfully connected to {remote}");
if (!_connections.ContainsKey(remote))
{
_connections[remote] = info.m_hConn;
}
// 清除超时和重试计数
_connectionTimeouts.Remove(remote);
_retryCount.Remove(remote);
OnPeerConnectedEvent?.Invoke(remote);
break;
case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_ClosedByPeer:
LogSystem.LogInfo($"Connection closed by peer: {remote}");
HandleDisconnection(remote, info.m_hConn);
break;
case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_ProblemDetectedLocally:
LogSystem.LogWarning($"Connection problem detected locally: {remote}");
LogSystem.LogWarning($"Problem details: {info.m_info.m_szEndDebug}");
HandleDisconnection(remote, info.m_hConn);
break;
case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_None:
// 检查具体的失败原因
var endReason = info.m_info.m_eEndReason;
LogSystem.LogError($"Connection failed - Reason: {endReason}");
// 提供更详细的错误信息
switch (endReason)
{
case (int)ESteamNetConnectionEnd.k_ESteamNetConnectionEnd_Misc_Timeout:
LogSystem.LogError("连接超时 - 可能的原因1.目标用户不在线 2.网络问题 3.防火墙阻止");
break;
case (int)ESteamNetConnectionEnd.k_ESteamNetConnectionEnd_Misc_InternalError:
LogSystem.LogError("内部错误 - Steam网络服务可能有问题");
break;
case (int)ESteamNetConnectionEnd.k_ESteamNetConnectionEnd_AppException_Generic:
LogSystem.LogError("应用程序异常 - 目标应用可能没有正确处理连接");
break;
case (int)ESteamNetConnectionEnd.k_ESteamNetConnectionEnd_Remote_Timeout:
LogSystem.LogError("远程超时 - 目标用户网络问题");
break;
default:
if (endReason >= 1000 && endReason < 2000)
{
LogSystem.LogError($"应用层拒绝连接 - 错误码: {endReason}可能原因1.对方未创建监听套接字 2.对方主动拒绝 3.对方游戏未运行");
}
else
{
LogSystem.LogError($"未知连接失败原因: {endReason}");
}
break;
}
HandleDisconnection(remote, info.m_hConn);
break;
}
}
// 检查是否应该接受这个连接
private bool ShouldAcceptIncomingConnection(CSteamID steamID)
{
// 基本检查确保是有效的Steam ID
if (!steamID.IsValid())
{
LogSystem.LogWarning($"接收到无效的Steam ID连接请求: {steamID}");
return false;
}
// 检查是否超过最大连接数
if (_connections.Count >= 10) // 假设最大10个连接
{
LogSystem.LogWarning("已达到最大连接数,拒绝新连接");
return false;
}
// 检查是否是好友(可选)
var relationship = SteamFriends.GetFriendRelationship(steamID);
LogSystem.LogInfo($"连接请求来自: {steamID}, 关系: {relationship}");
return true; // 默认接受所有连接
}
// 处理断开连接
private void HandleDisconnection(CSteamID steamID, HSteamNetConnection connection)
{
if (_connections.ContainsKey(steamID))
{
_connections.Remove(steamID);
OnPeerDisconnectedEvent?.Invoke(steamID);
}
_connectionTimeouts.Remove(steamID);
SteamNetworkingSockets.CloseConnection(connection, 0, "", false);
}
// 发送消息到指定玩家
public bool SendTo(CSteamID target, byte[] data, bool reliable = true, bool ordered = true)
{
if (data == null || data.Length == 0)
{
LogSystem.LogWarning("Trying to send null or empty data");
return false;
}
if (!_connections.TryGetValue(target, out var conn))
{
LogSystem.LogWarning($"No connection to {target}");
return false;
}
// 可靠传输=8不可靠传输=0
int flags = reliable ? 8 : 0;
// 如果需要有序传输 k_nSteamNetworkingSend_NoDelay (确保按序处理)
if (ordered) flags |= 1;
IntPtr ptr = IntPtr.Zero;
try
{
ptr = Marshal.AllocHGlobal(data.Length);
Marshal.Copy(data, 0, ptr, data.Length);
var result = SteamNetworkingSockets.SendMessageToConnection(conn, ptr, (uint)data.Length, flags, out _);
if (result != EResult.k_EResultOK)
{
LogSystem.LogError($"Failed to send message to {target}: {result}");
return false;
}
return true;
}
catch (Exception e)
{
LogSystem.LogError($"Exception while sending message: {e.Message}");
return false;
}
finally
{
if (ptr != IntPtr.Zero)
Marshal.FreeHGlobal(ptr);
}
}
// 发送消息到指定玩家
public bool SendToWithOutConnect(CSteamID target, byte[] data, bool reliable = true, bool ordered = true)
{
if (data == null || data.Length == 0)
{
LogSystem.LogWarning("Trying to send null or empty data");
return false;
}
// 创建目标身份标识
var identity = new SteamNetworkingIdentity();
identity.SetSteamID(target);
// 可靠传输=8不可靠传输=0
int flags = reliable ? 8 : 0;
// 如果需要有序传输 k_nSteamNetworkingSend_NoDelay (确保按序处理)
if (ordered) flags |= 1;
IntPtr ptr = IntPtr.Zero;
try
{
ptr = Marshal.AllocHGlobal(data.Length);
Marshal.Copy(data, 0, ptr, data.Length);
var result = SteamNetworkingMessages.SendMessageToUser(ref identity, ptr, (uint)data.Length, flags, 0);
if (result != EResult.k_EResultOK)
{
LogSystem.LogError($"Failed to send message to {target}: {result}");
return false;
}
return true;
}
catch (Exception e)
{
LogSystem.LogError($"Exception while sending message: {e.Message}");
return false;
}
finally
{
if (ptr != IntPtr.Zero)
Marshal.FreeHGlobal(ptr);
}
}
// 广播消息给所有连接的玩家
public void Broadcast(byte[] data, bool reliable = true)
{
foreach (var steamID in _connections.Keys)
{
SendTo(steamID, data, reliable);
}
}
// 轮询接收消息
public void PollMessages()
{
using var pooled = THCollectionPool.GetListHandle<CSteamID>(out var peers);
peers.AddRange(_connections.Keys);
// 使用快照遍历,避免处理消息时修改 _connections 导致枚举器失效
for (int i = 0; i < peers.Count; i++)
{
var steamID = peers[i];
if (!_connections.TryGetValue(steamID, out var connection))
{
continue;
}
PollMessagesForConnection(steamID, connection);
}
PollInviteMessages();
}
// 为特定连接轮询消息
private void PollMessagesForConnection(CSteamID steamID, HSteamNetConnection connection)
{
IntPtr[] messages = new IntPtr[32]; // 一次最多处理32条消息
int messageCount = SteamNetworkingSockets.ReceiveMessagesOnConnection(connection, messages, 32);
for (int i = 0; i < messageCount; i++)
{
var messagePtr = messages[i];
if (messagePtr == IntPtr.Zero) continue;
// try
// {
// // 获取消息结构
// var message = Marshal.PtrToStructure<SteamNetworkingMessage_t>(messagePtr);
//
// // 复制数据
// byte[] data = new byte[message.m_cbSize];
// Marshal.Copy(message.m_pData, data, 0, message.m_cbSize);
//
// // 触发接收事件
// GameNetReceiver.Instance.OnMessageReceived(data);
// }
// catch (Exception e)
// {
// LogSystem.LogError($"Error processing message from {steamID}: {e.Message}");
// }
// finally
// {
// // 释放消息 - 这是关键,必须释放消息内存
// SteamNetworkingMessage_t.Release(messagePtr);
// }
// 获取消息结构
var message = Marshal.PtrToStructure<SteamNetworkingMessage_t>(messagePtr);
// 复制数据
byte[] data = new byte[message.m_cbSize];
Marshal.Copy(message.m_pData, data, 0, message.m_cbSize);
// 触发接收事件
GameNetReceiver.Instance.OnMessageReceived(data);
// 释放消息 - 这是关键,必须释放消息内存
SteamNetworkingMessage_t.Release(messagePtr);
}
}
// 非特定连接轮询消息
public void PollInviteMessages()
{
IntPtr[] messages = new IntPtr[10];
int messageCount = SteamNetworkingMessages.ReceiveMessagesOnChannel(0, messages, 10);
for (int i = 0; i < messageCount; i++)
{
var messagePtr = messages[i];
if (messagePtr == IntPtr.Zero) continue;
// 获取消息结构
var message = Marshal.PtrToStructure<SteamNetworkingMessage_t>(messagePtr);
// 复制数据
byte[] data = new byte[message.m_cbSize];
Marshal.Copy(message.m_pData, data, 0, message.m_cbSize);
// 触发接收事件
GameNetReceiver.Instance.OnMessageReceived(data);
// 释放消息 - 这是关键,必须释放消息内存
SteamNetworkingMessage_t.Release(messagePtr);
}
}
// 获取连接状态
public bool IsConnectedTo(CSteamID steamID)
{
return _connections.ContainsKey(steamID);
}
// 获取所有连接的玩家
public IEnumerable<CSteamID> GetConnectedPeers()
{
return _connections.Keys;
}
// 获取连接数量
public int GetConnectionCount()
{
return _connections.Count;
}
// 清理资源
public void Cleanup()
{
DisconnectAll();
if (_listenSocket != HSteamListenSocket.Invalid)
{
SteamNetworkingSockets.CloseListenSocket(_listenSocket);
_listenSocket = HSteamListenSocket.Invalid;
}
_cbConnectionStatusChanged?.Dispose();
_cbConnectionStatusChanged = null;
_cbSessionRequest?.Dispose();
_cbSessionRequest = null;
}
// 获取详细的连接信息用于调试
public void LogDetailedConnectionInfo(CSteamID steamID)
{
if (!_connections.TryGetValue(steamID, out var connection))
{
LogSystem.LogWarning($"No connection found for {steamID}");
return;
}
if (SteamNetworkingSockets.GetConnectionInfo(connection, out var info))
{
LogSystem.LogInfo("=== 详细连接信息 ===");
LogSystem.LogInfo($"连接状态: {info.m_eState}");
LogSystem.LogInfo($"结束原因: {info.m_eEndReason}");
LogSystem.LogInfo($"用户数据: {info.m_nUserData}");
LogSystem.LogInfo($"监听套接字: {info.m_hListenSocket}");
LogSystem.LogInfo($"远程地址: {info.m_addrRemote}");
LogSystem.LogInfo($"连接描述: {info.m_szConnectionDescription}");
LogSystem.LogInfo($"结束调试信息: {info.m_szEndDebug}");
LogSystem.LogInfo("===================");
}
}
}
}