如何解决 WebSocket Server 返回数据不一致 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
如果想在 V2EX 获得更好的推广效果,欢迎了解 PRO 会员机制:
pro/about
Baishancloud

如何解决 WebSocket Server 返回数据不一致

  •  
  •   Baishancloud Feb 27, 2017 3477 views
    This topic created in 3345 days ago, the information mentioned may be changed or developed.

    作者简介:

    花名“卡库”,白山云科技系统开发工程师

    API 开发与管理老鲜肉,丰富的产品开发与运维经验,先后就职于搜狐、新浪等知名互联网公司,曾参与新浪云 SAE 平台 CC 防火墙项目,为数十万用户提供安全防护,保证 SAE 平台性能稳定; 2016 年入职白山,就此成为酒仙桥地区最大酒窝的系统开发工程师。


    针对实时 Web 应用(如:实时通信、股票基金应用、体育实况更新、多玩家游戏等场景),传统 Web 中为了实时获取 Server 端的数据,通常是 Client 端定期发送 HTTP 请求, Server 端进行响应并返回数据。由于 Client 定期向 Server 发送请求,当 Server 端没有数据更新时, Client 仍旧发送请求,这造成了带宽的浪费以及 Server 端 CPU 的占用。

    为解决上述问题,越来越多企业在思考如何解决长连接问题, WebSocket 是较为常用的方法之一。 WebSocket 通过第一个 HTTP request 建立 TCP 连接,后续数据交换都无需再发送 HTTP request ,创建了一个真正的长连接。同时 WebSoket 还是一个双通道的连接,可以实现在同一个 TCP 连接上收发信息。

    白山云聚合平台也融入了 WebSocket ,可以为用户提供 WebSocket 协议到 HTTP 协议的转换功能,让用户的 Client 以长连接 WebSocket 协议的方式连接到云聚合平台,云聚合只需一个 HTTP 连接即可连接到企业后端,大幅降低后端压力的同时,更免去了用户服务器端适配 WebSocket 协议的问题。我们在研发测试过程中遇到了一个有意思的问题,这或许是很多开发者都曾遇到过的:使用不同的 WebSocket 客户端和 WebSocket Server 通信, WebSocket Server 返回数据不一致。

    一、问题场景

    1.不同客户端访问

    ( 1 ) python 通过 WebSocket 客户端和 WebSocket Server ws://2abe356fc.bsclink.com/交互,输出正常;

    (python 客户端输出内容)

    ( 2 ) Chrome 浏览器加载 ws.html 页面之后,页面中的 js 调用浏览器自带的 WebSocket Client 与 WebSocket Server ws://2abe356fc.bsclink.com/交互,输出 ERROR ;

    ( Chrome 浏览器输出内容)

    ( 3 ) Safari 浏览器加载 ws.html 页面之后,页面中的 js 调用浏览器自带的 WebSocket Client 和 WebSocket Server ws://2abe356fc.bsclink.com/交互,输出正常;

    ( Safari 浏览器输出内容)


    2.浏览器请求流程图

    以下是浏览器通过 WebSocket 协议向服务器请求的流程:

    (浏览器请求流程图)


    二、问题分析

    只有 Chrome 与 Websocket Server 间的通信发生异常,判断 ERROR 很可能是由 Chrome 浏览器问题导致的,基于此来分析问题产生的具体原因。


    1.通过浏览器控制台查看报错相关信息

    如上图下方所示, WebSocket 协议 decode a text frame 在转化为 uft-8 编码时失败。

    由于 WebSocket Server 向 Client 返回数据时,使用 text frame 方式,于是我们开始排查 WebSocket Server 返回数据导致 decode 失败的原因。


    2.打印 WebSocket Server 日志,查看返回内容

    通过日志,观察到 longloop 传送给 WebSocket Server 的内容与 WebSocket Server 输出到 Client 的内容一致,均为乱码。基于此我们可以确定 WebSocket Server 不存在异常情况,于是我们需要确定 longloop 是否存在异常。


    3.通过 longloop 抓包查看 backend 返回内容

    可以通过 TCPDUMP 抓包来判定 longloop 是否存在问题。

    ( backend 返回到 longloop 的数据)

    ( longloop 返回到 WebSocket Server 的数据)

    通过对比以上两组数据,可以得出如下结论:

    经过 longloop 后,真实返回给 Client 的数据并未发生变化。

    ( 1 ) backend 的返回数据被 gzip 压缩;

    ( 2 ) 压缩的响应数据被发送至 WebSocket Server ;

    ( 3 ) 最终由 WebSocket Server 发送到 WebSocket 客户端。


    4.backend 返回的数据为什么被压缩了?

    首先, backend 端必须开启 gzip 压缩,并支持对此返回的数据类型的 gzip 压缩,才能返回压缩后的响应数据;

    其次,客户端要明确声明能接收 gzip 压缩的响应数据, backend 端才能够返回 gzip 压缩过的数据。

    经确认, backend server 上的配置开启了 gzip 压缩功能,并对 content-type 为 text/html 的数据支持 gzip 压缩。

    可以判断问题有可能出现在 client 环节:

    Client 没有要求返回压缩数据,但是 backend 端返回了压缩数据;

    通过不同浏览器访问,返回不同数据,可以判定不是 backend 端的问题。

    Client 主动要求 backend 端返回被压缩的数据;

    只有 Chrome 浏览器返回了 gzip 压缩数据,可以推断可能是因为 Chrome 请求 backend 端时,在 request header 中包含了可以接收 gzip 压缩数据的 header ,导致 backend 端返回了 gzip 压缩数据。


    5.抓包对比 Chrome 和 Safari 请求头信息

    Chrome 相关信息:

    1 ) Chrome 浏览器请求 ws.html 静态文件的请求头中带有 Accept-Encoding :

    2 ) Chrome 浏览器将 ws.html 加载到本地后, ws.html 文件中的 js WebSocket 客户端向 WebSocket Server 发送请求的请求头中带有 Accept-Encoding :

    3 ) Chrome 浏览器的请求发送到 longloop 之后, longloop 到 backend 的请求头中带有 Accept-Encoding :

    Safari 相关信息

    1 ) Safari 浏览器请求 ws.html 静态文件的请求头中带有 Accept-Encoding :

    2 ) Safari 浏览器将 ws.html 加载到本地后, ws.html 文件中的 js WebSocket 客户端向 WebSocket Server 发送请求的请求头中未带有 Accept-Encoding :

    3 ) Safari 浏览器的请求发送到 longloop 之后, longloop 到 backend 的请求头中未带有 Accept-Encoding :

    通过对比 Chrome 和 Safari 相关请求数据,我们可以判断出 WebSocket Server 返回数据不一致的原因如下:

    Chrome , Safari 浏览器发送请求时,为了提高网络传输效率、减少网络带宽占用,默认自带 gzip 压缩支持,两种浏览器加载 ws.html 时均无异常。但当 js 调用 Chrome 浏览器 WebSocket 客户端向 WebSocket Server 端发送请求时,在请求头 Accept-Encoding 中添加了对 gzip 的支持, backend 收到 HTTP 请求后,认为客户端能够对 gzip 压缩的响应数据进行解压缩,从而 backend 返回了 gzip 压缩过的响应数据,而 WebSocket 客户端接收到 gzip 压缩的数据后,不支持 gzip 数据解压缩,最终导致了 decode 出错。

    而 js 调用 Safari 浏览器 WebSocket 客户端向 WebSocket Server 端发送请求时,请求头未带有 Accept-Encoding , backend 收到 http 请求后,不会返回被 gzip 压缩的响应数据,从而 WebSocket 客户端正常解析访问正常。


    三、解决办法

    为解决上述问题,我们需要在 longloop 这一层进行判断:如果 user agent 为 Chrome 浏览器,则需要去掉 request header 中的 Accept-Encoding 这个 header ,明确告知服务器端不接受 gzip 压缩过的数据,这样服务器端就不会返回 gzip 压缩过的数据, Chrome 浏览器即可正常访问。

    3 replies    2017-03-02 18:56:49 +08:00
    ryd994
        1
    ryd994  
       Feb 27, 2017 via Android
    这样的 workaround 不好
    是 chrome 违反了 accept-encoding 声明了自己不支持的
    还是 Nginx 对不该 gzip 的数据 gzip 了
    无论是哪个,应该直接向上游提交 issue ,再 workaround ,在 issue 解决后去掉

    关于 gzip websocket ,似乎暂时没有标准: http://stackoverflow.com/questions/11646680/could-websocket-support-gzip-compression
    jingniao
        2
    jingniao  
       Feb 27, 2017
    没遇到这个问题,倒是在选型的时候遇到过某些库的动作有些奇怪
    Baishancloud
        3
    Baishancloud  
    OP
       Mar 2, 2017
    @ryd994
    恩,您说的很对。之前定解决办法的时候也想到了,源站的 nginx 由于是企业客户那边的,不好控制,故没有采用。 Chrome 的问题后续也会跟踪。考虑到客户测试,故直接 workaround 了。
    About     Help     Advertise     Blog     API     FAQ     Solana     3887 Online   Highest 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 51ms UTC 10:34 PVG 18:34 LAX 03:34 JFK 06:34
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86