2025-09-17 10:52:48 +08:00

294 lines
10 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 Steamworks;
using UnityEngine;
namespace TH1_Logic.Steam
{
public class SimpleP2P
{
public static SimpleP2P Instance { get; } = new SimpleP2P();
// 连接映射表SteamID -> 连接句柄
private readonly Dictionary<CSteamID, HSteamNetConnection> _connections = new Dictionary<CSteamID, HSteamNetConnection>();
// 监听套接字
private HSteamListenSocket _listenSocket = HSteamListenSocket.Invalid;
// 回调
private Callback<SteamNetConnectionStatusChangedCallback_t> _cbConnectionStatusChanged;
// 事件委托
public event System.Action<CSteamID> OnPeerConnectedEvent;
public event System.Action<CSteamID> OnPeerDisconnectedEvent;
public event System.Action<CSteamID, byte[]> OnMessageReceivedEvent;
public event System.Action<string> OnConnectionErrorEvent;
// 初始化
public void Initialize()
{
_cbConnectionStatusChanged = Callback<SteamNetConnectionStatusChangedCallback_t>.Create(OnConnectionStatusChanged);
// 创建监听套接字
CreateListenSocket();
}
// 创建监听套接字
private void CreateListenSocket()
{
// 使用默认端口0Steam会自动分配
_listenSocket = SteamNetworkingSockets.CreateListenSocketP2P(0, 0, null);
if (_listenSocket == HSteamListenSocket.Invalid)
{
OnConnectionErrorEvent?.Invoke("Failed to create listen socket");
return;
}
LogSystem.LogInfo($"P2P Listen socket created: {_listenSocket}");
}
// 连接到指定玩家
public bool ConnectToPeer(CSteamID steamID)
{
if (_connections.ContainsKey(steamID))
{
LogSystem.LogInfo($"Already connected to {steamID}");
return true;
}
// 创建到目标玩家的连接
var identity = new SteamNetworkingIdentity();
identity.SetSteamID(steamID);
var connection = SteamNetworkingSockets.ConnectP2P(ref identity, 0, 0, null);
if (connection == HSteamNetConnection.Invalid)
{
OnConnectionErrorEvent?.Invoke($"Failed to connect to {steamID}");
return false;
}
_connections[steamID] = connection;
LogSystem.LogInfo($"Connecting to peer: {steamID}");
return true;
}
// 断开与指定玩家的连接
public void DisconnectFromPeer(CSteamID steamID)
{
if (!_connections.TryGetValue(steamID, out var connection))
return;
SteamNetworkingSockets.CloseConnection(connection, 0, "Disconnected by user", false);
_connections.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();
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();
switch (state)
{
case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_Connecting:
LogSystem.LogInfo($"Connecting to {remote}...");
break;
case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_FindingRoute:
LogSystem.LogInfo($"Finding route to {remote}...");
break;
case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_Connected:
LogSystem.LogInfo($"Connected to {remote}");
if (!_connections.ContainsKey(remote))
{
_connections[remote] = info.m_hConn;
}
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}");
HandleDisconnection(remote, info.m_hConn);
break;
case ESteamNetworkingConnectionState.k_ESteamNetworkingConnectionState_None:
// 连接被拒绝或失败
LogSystem.LogError($"Connection failed or rejected: {remote}");
HandleDisconnection(remote, info.m_hConn);
break;
}
}
// 处理断开连接
private void HandleDisconnection(CSteamID steamID, HSteamNetConnection connection)
{
if (_connections.ContainsKey(steamID))
{
_connections.Remove(steamID);
OnPeerDisconnectedEvent?.Invoke(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 void Broadcast(byte[] data, bool reliable = true)
{
foreach (var steamID in _connections.Keys)
{
SendTo(steamID, data, reliable);
}
}
// 轮询接收消息
public void PollMessages()
{
// 为每个连接轮询消息
foreach (var kvp in _connections)
{
var steamID = kvp.Key;
var connection = kvp.Value;
PollMessagesForConnection(steamID, connection);
}
}
// 为特定连接轮询消息
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(steamID, data);
}
catch (Exception e)
{
LogSystem.LogError($"Error processing message from {steamID}: {e.Message}");
}
finally
{
// 释放消息
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;
}
}
}