294 lines
10 KiB
C#
294 lines
10 KiB
C#
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()
|
||
{
|
||
// 使用默认端口0,Steam会自动分配
|
||
_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;
|
||
}
|
||
}
|
||
}
|