0
点赞
收藏
分享

微信扫一扫

前端使用 WebSocket 的四大注意事项(线上踩坑,含泪分享)

携手创作,共同成长!

背景

我是公众号「线下聚会游戏」的作者,开发了一些​​联机桌游网页​​(斗地主、五子棋等),可以和朋友联机玩游戏,不用下载就能一起玩。

其中的联机功能,就是通过WebSocket实现的,中途也踩了一些坑,分享给大家。

ws.close()有个参数,最好填上

这个参数就是错误码,表明了关闭连接的原因:

前端使用 WebSocket 的四大注意事项(线上踩坑,含泪分享)_safari

WebSocket断开连接时,会发送一个错误码给另一方。如果是浏览器主动断开连接,浏览器发错误码给服务器。如果是服务器断开连接,服务器发错误码给浏览器。

所有错误码可参考 ​​MDN: CloseEvent Code​​。

在浏览器中,调用​​ws.close()​​函数关闭连接时,默认错误码是1005,含义是 no status code was provided even though one was expected。

这是容易犯错的,可能很多人认为它的默认值是1000(正常关闭)。结果服务器收到的却是1005。

解决

如果前端关闭是正常关闭,你可以使用​​ws.close(1000)​​。

如果前端关闭不是正常关闭,你需要自定义一个异常错误码,范围是4000-4999。

此外,如果你在开发一个框架,那么你可用的错误码范围是3000-3999。

如果接收的数据是二进制,一定要设置ws.binaryType

前端使用 WebSocket 的四大注意事项(线上踩坑,含泪分享)_WebSocket_02

​ws.binaryType​​​有2种值:​​blob​​​和​​arraybuffer​​。

​blob​​是它的默认值。

如果你收到了二进制数据:当​​ws.binaryType​​​为​​blob​​​时,event.data是Blob类型,你需要调用​​await event.data.arrayBuffer()​​获取ArrayBuffer类型的数据。

如果你收到了二进制数据,当​​ws.binaryType​​​为​​arraybuffer​​时,event.data是ArrayBuffer类型。

踩坑点

我的《联机桌游合集》刚上线时,有个使用iOS的朋友告诉我,她无法进入游戏,重试了多次也不行。

但是我已经用我手头的安卓、iPad、iPhone、Mac、Windows全都测试过一遍了。

经过排查,才发现是她的iOS14中Safari浏览器搞的鬼。虽然我没有设置​​ws.binaryType​​​为​​arraybuffer​​​, 但是因为Safari检测到是二进制数据,就直接把​​​event.data​​​转换为了ArrayBuffer类型,不是Blob类型,导致我调用​​await event.data.arrayBuffer()​​时出错了。

回顾当时的commit记录:

前端使用 WebSocket 的四大注意事项(线上踩坑,含泪分享)_前端_03

解决

如果你ws收到的数据都是二进制格式,在调用​​const ws = new WebSocket()​​​后,立马设置​​ws.binaryType = 'arraybuffer'​​。

但是如果你ws可能收到二进制数据,也可能收到文本数据,建议参考MDN官方案例,设置​​ws.binaryType​​​为​​arraybuffer​​,但是加个条件判断:

const ws = new WebSocket("ws://localhost:8080");
// Change binary type from "blob" to "arraybuffer"
ws.binaryType = "arraybuffer";
ws.onmessage = (event) => {
if(event.data instanceof ArrayBuffer) {
// binary frame
console.log(event.data);
} else {
// text frame
console.log(event.data);
}
});

关于消息合并与拆分

我在Mac环境下,使用Safari浏览器和Chrome浏览器的WebSocket,有2种不同的现象:

如果后端发送给前端的消息中,包含了​​\n​​​换行符。在Chrome中,会触发多次​​onmessage​​​事件,各个消息是被Chrome基于​​\n​​​分割开了,分割后的消息按顺序依次触发​​onmessage​​​来处理。在Safari中,只触发了一次​​onmessage​​事件,Safari没有帮我们分隔消息。

事实上,在WebSocket消息中,​​\n​​​换行符本身就是区分消息的特殊符号。如果需要短时间内连续发送多条消息给客户端,一种常见的优化手段就是把这些消息一次性发送过去,用​​\n​​分割。

Chrome做的很好,帮我们分割好了。但是像Safari这种浏览器没有帮我们分割,为了兼容性,我们也需要处理下。

解决

如果后端有「批量发送」的机制,就在​​onmessage​​​事件中,把消息按​​\n​​分割后,再依次处理。如果后端没有实现「批量发送」的机制,则可以忽略。

ws.onmessage = (event) => {
event.data.split('\n').forEach((message) => {
// 处理各个message

关于多个ws实例并发

这里的坑不是特别大,但如果你要做压力测试,那就可能会遇到坑。你需要知道:

Chrome有个特点:如果你同时建立多个WebSocket连接,只会一个一个建立。等前一个ws建立连接成功,后一个ws开始建立连接。

如果你想测试后台服务同时被多个客户端连接,有无并发问题时,不要用同一个Chrome Tab来测。可以开多个Tab和多个浏览器,或者用Safari测试。

因为在Safari上:如果你同时建立多个WebSocket连接,是同时发送ws连接请求的(当然注意ws同时连接数有上限,做压测时,一个Tab没必要一次性连太多,是没用的)。

我在写文章​​《多房间的聊天室(六)为什么要加锁?不加锁行不行啊?》​​时,发现了这个问题。

写在最后

我是HullQin,公众号线下聚会游戏的作者(欢迎关注公众号,联系我,交个朋友),转发本文前需获得作者HullQin授权。我独立开发了​​《联机桌游合集》​​​,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋等游戏,不收费无广告。还独立开发了​​《合成大西瓜重制版》​​​。还开发了​​《Dice Crush》​​​参加Game Jam 2022。喜欢可以关注我噢~我有空了会分享做游戏的相关技术,会在这2个专栏里分享:​​《教你做小游戏》​​​、​​《极致用户体验》​​。

举报

相关推荐

0 条评论