You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Photon Server向目标客户端发消息及客户端Peer存储问题咨询

关于Photon Server客户端管理与消息发送的解决方案

嘿,我来帮你捋清楚Photon Server里客户端Peer管理和消息发送的核心问题,这俩是多人服务器开发的基础,咱们一步步拆解:

一、先解决客户端Peer存入列表的问题

你当前的ClientCollection有两个小问题:一是列表没初始化(会导致空引用异常),二是缺少线程安全处理(Photon的回调是多线程触发的,直接操作列表会有并发风险)。另外,AddNewClient的参数用PeerBase是完全正确的——因为Photon所有客户端Peer最终都继承自这个基类。

修正后的ClientCollection代码

public class ClientCollection {
    // 用私有列表+锁保证线程安全
    private readonly List<PeerBase> _clients = new List<PeerBase>();
    private readonly object _lockObj = new object();

    public ClientCollection() { }

    public void AddNewClient(PeerBase peer) 
    {
        lock(_lockObj)
        {
            // 避免重复添加同一客户端
            if (!_clients.Contains(peer))
            {
                _clients.Add(peer);
                Debug.Log($"新客户端已连接,当前在线数:{_clients.Count}");
            }
        }
    }

    // 别忘了添加移除客户端的方法(断开连接时用)
    public void RemoveClient(PeerBase peer)
    {
        lock(_lockObj)
        {
            if (_clients.Contains(peer))
            {
                _clients.Remove(peer);
                Debug.Log($"客户端已断开,当前在线数:{_clients.Count}");
            }
        }
    }

    // 返回列表副本,避免外部直接修改原集合
    public List<PeerBase> GetAllClients()
    {
        lock(_lockObj)
        {
            return new List<PeerBase>(_clients);
        }
    }
}

什么时候调用AddNewClient

在你的自定义ServerPeer类里,CreateClientPeer方法会在新客户端连接时触发,这里是添加Peer到列表的最佳时机:

public class CustomServerPeer : ServerPeerBase
{
    private readonly ClientCollection _clientCollection;

    // 通过构造注入传递客户端集合(建议用单例或依赖注入框架管理)
    public CustomServerPeer(ClientCollection clientCollection)
    {
        _clientCollection = clientCollection;
    }

    protected override PeerBase CreateClientPeer(InitRequest initRequest)
    {
        // 创建你的自定义ClientPeer实例
        var clientPeer = new CustomClientPeer(initRequest);
        // 把新Peer加入集合
        _clientCollection.AddNewClient(clientPeer);
        return clientPeer;
    }

    // 客户端断开时从集合移除
    protected override void OnDisconnect(PeerBase peer, DisconnectReason reasonCode, string reasonDetail)
    {
        _clientCollection.RemoveClient(peer);
        base.OnDisconnect(peer, reasonCode, reasonDetail);
    }
}

二、消息发送的代码实现

Photon里发送消息主要用两种方式,根据场景选择:

1. 向单个客户端发送消息

适合一对一的交互(比如私聊、单独同步状态):

/// <summary>
/// 给单个客户端发送事件通知(单向,无需回复)
/// </summary>
public void SendEventToClient(PeerBase peer, byte eventCode, Dictionary<byte, object> eventData)
{
    if (peer != null && peer.IsConnected)
    {
        var sendParams = new SendParameters();
        sendParams.ChannelId = 0; // 使用默认通信通道
        sendParams.DeliveryMode = DeliveryMode.Reliable; // 可靠传输,确保消息送达
        peer.SendEvent(eventCode, eventData, sendParams);
    }
}

/// <summary>
/// 给单个客户端发送操作响应(请求-响应模式)
/// </summary>
public void SendOperationResponseToClient(PeerBase peer, short operationCode, Dictionary<byte, object> responseData, short returnCode = 0, string debugMessage = "")
{
    if (peer != null && peer.IsConnected)
    {
        var response = new OperationResponse(operationCode)
        {
            ReturnCode = returnCode,
            DebugMessage = debugMessage,
            Parameters = responseData
        };
        var sendParams = new SendParameters();
        sendParams.DeliveryMode = DeliveryMode.Reliable;
        peer.SendOperationResponse(response, sendParams);
    }
}

2. 向所有客户端广播消息

适合同步全局状态(比如玩家列表更新、公共事件通知):

/// <summary>
/// 给所有在线客户端广播事件
/// </summary>
public void BroadcastEventToAll(byte eventCode, Dictionary<byte, object> eventData)
{
    var clients = _clientCollection.GetAllClients();
    foreach (var client in clients)
    {
        if (client.IsConnected)
        {
            var sendParams = new SendParameters();
            sendParams.ChannelId = 0;
            sendParams.DeliveryMode = DeliveryMode.Reliable;
            client.SendEvent(eventCode, eventData, sendParams);
        }
    }
}

几个关键注意点

  • 线程安全:所有操作客户端列表的代码必须加锁,Photon的回调是多线程触发的,不加锁会出现集合修改异常。
  • 无效Peer清理:客户端断开时一定要从列表移除,避免持有无效引用导致发送消息失败。
  • 传输模式选择DeliveryMode.Reliable确保消息必达但延迟稍高,DeliveryMode.Unreliable适合对实时性要求高的内容(比如玩家位置同步)。

内容的提问来源于stack exchange,提问作者古金禾

火山引擎 最新活动