一、问题背景:Photon在WebGL上的“连接拦路虎”
很多做实时联机游戏、互动直播的开发者,应该都用过Photon这个工具——它就像一个“联机中转站”,能帮我们快速实现多个玩家之间的实时数据同步,比如游戏里的玩家移动、实时聊天、组队状态更新这些功能。但不少人把做好的项目打包成WebGL版本(就是能直接在浏览器里跑的版本)时,会遇到一个特别闹心的问题:明明在PC端、移动端原生平台上连得好好的,到了WebGL就突然连不上Photon服务器,查日志会发现是WebSocket相关的错误。
这不是Photon本身的bug,而是浏览器对WebSocket的限制导致的。简单说,浏览器为了安全,不允许未加密的WebSocket连接(也就是ws://开头的)直接连到非本地的服务器,而且对WebSocket的跨域、端口、证书都有严格要求;再加上Photon默认的一些配置,就很容易触发连接失败。接下来我们就一步步拆解怎么解决这个问题。
二、替代方案:不用原生WebSocket的Photon连接方式
如果不想跟浏览器的WebSocket限制死磕,我们可以换一种连接方式——Photon支持用HTTP长轮询(Long Polling)来模拟实时通信,本质是浏览器不断发HTTP请求,服务器有新数据就立刻返回,没有就等一会儿再返回,就像浏览器“蹲点”拿数据,能完美绕过WebSocket的所有限制。
2.1 技术栈统一说明
我们所有示例都用Unity作为开发引擎,因为Photon最常用的场景就是Unity游戏开发,WebGL打包也是Unity的核心功能之一。
2.2 具体实现步骤
首先,我们要把Unity项目里的Photon连接配置改成用HTTP长轮询。原来的Photon连接代码一般是默认用WebSocket的,我们只需要改一行配置就行。
// 统一技术栈:Unity 2022.3 + Photon Realtime 4.1.10
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
public class PhotonWebGLConnector : MonoBehaviourPunCallbacks
{
// 你的Photon服务器地址(和原生平台的地址一致)
public string photonServerAddress = "wss://your-photon-server:5055";
// 你的Photon应用ID(从Photon官网申请的唯一标识)
public string photonAppId = "YOUR_APP_ID";
void Start()
{
// 关键修改:设置Photon的连接协议为HTTP长轮询
// 只有WebGL平台才用这个协议,原生平台还是用默认的WebSocket
#if UNITY_WEBGL
PhotonNetwork.NetworkingClient.LoadBalancingPeer.Protocol = ConnectionProtocol.Http;
#endif
// 连接Photon服务器的标准代码
if (!PhotonNetwork.IsConnected)
{
PhotonNetwork.ConnectUsingSettings();
PhotonNetwork.GameVersion = "1.0";
}
}
// 连接成功的回调函数
public override void OnConnectedToMaster()
{
Debug.Log("Photon连接成功!");
// 连接成功后可以加入房间、创建房间等操作
PhotonNetwork.JoinRandomRoom();
}
// 连接失败的回调函数
public override void OnDisconnected(DisconnectCause cause)
{
Debug.Log($"Photon连接失败,原因:{cause}");
}
}
这里要注意几个细节:一是#if UNITY_WEBGL这个条件编译,意思是只有打包成WebGL的时候才会执行改协议的代码,PC、移动端原生平台还是用原来的WebSocket,不会影响其他平台的性能;二是HTTP长轮询的连接地址,不用改,还是和原来的WebSocket地址一样,Photon会自动适配。
2.3 替代方案的优缺点
这个方案的优点非常明显:一是完全绕过了浏览器对WebSocket的所有限制,不用管证书、跨域、端口的问题,只要你的Photon服务器能正常接收HTTP请求就行;二是实现起来特别简单,只需要加一行配置,原来的Photon代码几乎不用改,学习成本极低;三是兼容性好,所有支持HTML5的浏览器都能跑,包括一些旧版本的浏览器。
缺点也有:HTTP长轮询的实时性比WebSocket差一点,毕竟是靠不断发请求来拿数据,会有几十毫秒到几百毫秒的延迟,对实时性要求特别高的场景(比如第一人称射击游戏的精准同步)可能会有影响;另外,HTTP长轮询的流量消耗比WebSocket高,因为每次请求都会带HTTP头,长时间联机的话,流量会比WebSocket多一些。
三、协议栈调优:如果坚持用WebSocket的优化方法
如果你的项目对实时性要求特别高,必须用WebSocket,那我们也可以通过调优协议栈来解决连接问题,核心就是满足浏览器对WebSocket的所有安全要求。
3.1 浏览器WebSocket的核心限制
要调优,首先得知道浏览器到底限制了什么:
- 必须用加密连接:浏览器只允许ws://(未加密)连接本地服务器(比如localhost),如果要连接公网服务器,必须用wss://(加密);
- 必须有合法的SSL证书:wss://连接需要服务器有受浏览器信任的SSL证书,不能是自签名的证书;
- 必须满足跨域要求:如果你的WebGL项目是从A域名加载的,Photon服务器的域名必须允许A域名的跨域请求;
- 端口限制:浏览器不允许用一些特殊端口(比如22、25)来建立WebSocket连接。
3.2 具体调优步骤
我们一步步来解决这些限制:
3.2.1 给Photon服务器配置SSL证书
首先,我们要把Photon服务器的WebSocket连接改成wss://,并且配上合法的SSL证书。这里我们用免费的Let's Encrypt证书来演示,适合个人开发者和小团队。
第一步,先在Photon服务器上安装certbot工具(用来申请Let's Encrypt证书):
# 以Ubuntu服务器为例,安装certbot
apt update && apt install certbot
第二步,申请证书(假设你的Photon服务器域名是photon.example.com):
# 申请证书,会自动验证域名所有权
certbot certonly --standalone -d photon.example.com
第三步,把证书配置到Photon服务器。打开Photon服务器的配置文件(一般在Photon根目录的deploy/LoadBalancing/bin/PhotonServer.config),找到WebSocket相关的配置,改成这样:
<WebSocketSettings>
<Enabled>True</Enabled>
<BindAddress>0.0.0.0</BindAddress>
<Port>5055</Port>
<!-- 配置SSL证书路径 -->
<SslCertificatePath>/etc/letsencrypt/live/photon.example.com/fullchain.pem</SslCertificatePath>
<SslCertificateKeyPath>/etc/letsencrypt/live/photon.example.com/privkey.pem</SslCertificateKeyPath>
<SslCertificatePassword></SslCertificatePassword>
</WebSocketSettings>
配置完后重启Photon服务器,现在你的Photon服务器就支持wss://连接了。
3.2.2 配置Photon服务器的跨域规则
接下来,我们要让Photon服务器允许你的WebGL项目域名的跨域请求。还是打开Photon的配置文件,找到CORS相关的配置,改成这样:
<HttpSettings>
<Enabled>True</Enabled>
<Port>9090</Port>
<!-- 配置允许跨域的域名,*代表允许所有域名(测试用,生产环境建议指定具体域名) -->
<CorsAllowedOrigins>*</CorsAllowedOrigins>
<CorsAllowedMethods>GET,POST,PUT,DELETE,OPTIONS</CorsAllowedMethods>
<CorsAllowedHeaders>Content-Type,Authorization,X-Requested-With</CorsAllowedHeaders>
</HttpSettings>
这里要注意,生产环境下不要用*,要把你的WebGL项目的域名填进去,比如https://your-game.com,这样更安全。
3.2.3 修改Unity端的连接代码
最后,我们要把Unity端的Photon连接地址改成wss://开头的,代码修改如下:
// 统一技术栈:Unity 2022.3 + Photon Realtime 4.1.10
using UnityEngine;
using Photon.Pun;
using Photon.Realtime;
public class PhotonWebSocketConnector : MonoBehaviourPunCallbacks
{
// 改成wss://开头的地址
public string photonServerAddress = "wss://photon.example.com:5055";
public string photonAppId = "YOUR_APP_ID";
void Start()
{
#if UNITY_WEBGL
// WebGL平台用wss://连接,协议用默认的WebSocket
PhotonNetwork.NetworkingClient.LoadBalancingPeer.Protocol = ConnectionProtocol.WebSocket;
#endif
if (!PhotonNetwork.IsConnected)
{
PhotonNetwork.ConnectUsingSettings();
PhotonNetwork.GameVersion = "1.0";
}
}
public override void OnConnectedToMaster()
{
Debug.Log("Photon WebSocket连接成功!");
PhotonNetwork.JoinRandomRoom();
}
public override void OnDisconnected(DisconnectCause cause)
{
Debug.Log($"Photon WebSocket连接失败,原因:{cause}");
}
}
3.3 调优方案的优缺点
这个方案的优点是实时性好,能达到和原生平台差不多的延迟,适合对实时性要求高的项目;流量消耗也比HTTP长轮询低,长时间联机更省流量。
缺点是配置比较复杂,需要给Photon服务器申请和配置SSL证书,还要处理跨域、端口等问题,对服务器配置不熟悉的开发者来说门槛比较高;另外,证书需要定期更新(Let's Encrypt证书有效期是90天),如果忘了更新,连接会再次失败。
四、两种方案的适用场景对比
我们来总结一下两种方案分别适合什么情况:
- HTTP长轮询方案:适合对实时性要求不高的项目,比如回合制游戏、实时聊天、多人互动课件等;适合个人开发者、小团队,不想花太多时间配置服务器的场景;适合需要兼容旧浏览器的场景。
- WebSocket调优方案:适合对实时性要求高的项目,比如MOBA游戏、射击游戏、实时协作绘图等;适合有一定服务器配置能力的团队;适合流量敏感的项目。
五、注意事项
不管用哪种方案,都有几个通用的注意事项:
- 测试环境尽量和生产环境一致:不要只在本地测试就上线,要把项目打包成WebGL后,放到公网服务器上测试,因为本地环境的限制和公网不一样;
- 版本一致性:Photon的客户端版本和服务器版本要一致,不然会出现连接成功但数据同步失败的问题;
- 错误日志要详细:连接失败时,要打印出具体的错误原因,比如是证书错误、跨域错误还是超时,方便排查问题;
- 避免自签名证书:如果用WebSocket方案,不要用自签名的证书,浏览器会直接拒绝连接;
- 跨域配置不要用*:生产环境下,一定要指定允许跨域的具体域名,不要用*,不然会有安全风险。
六、文章总结
Photon在WebGL平台的连接问题,本质是浏览器对WebSocket的安全限制导致的,我们可以根据项目的实际需求选择两种解决思路:如果追求简单、兼容,就用HTTP长轮询替代;如果追求实时性,就对WebSocket协议栈进行调优,满足浏览器的安全要求。
两种方案各有优劣,没有绝对的好坏,适合自己项目的就是最好的。希望这篇文章能帮大家解决Photon在WebGL上的连接问题,让大家的联机项目能顺利跑在浏览器上。
评论
围绕“使用Photon时WebGL平台因WebSocket限制导致连接失败的替代方案与协议栈调优”参与讨论