webSocket 是一种比较新的协议, 它是伴随着 html5 规范而生的, 虽然还比较年轻, 但大多主流浏览器都已经支持. 它使用方面, 应用广泛, 已经渗透到前后端开发的各种场景中.
对 http 一问一答 中二式流程的不满, 催生了支持双向通信的 WebSocket 诞生. WebSocket 是个 不太干净 协议.
一, WebSocket 协议只能浏览器发起么?
不是. 目前此协议的受众的也不仅仅是 Web 开发者.
WebSocket 只是一种协议, 它和 http 协议一样, 使用 类似 okhttp 的组件, 可以在任何地方进行调用, 甚至可以借助 WebSocket 实现 RPC 框架.
二, WebSocket 和 HTTP 什么关系?
WebSocket 和 http 一样, 都是处于 OSI 模型中的最高层: 应用层 .
WebSocket 借助 http 协议进行握手, 握手成功后, 就会变身为 TCP 通道 , 从此与 http 不再相见.
使用 netstat 或者 ss, 能够看到对应的连接, 它与处于抽象层的 socket, 在外观上没有区别.
三, WebSocket 和长轮询有什么区别?
长轮询, 就是客户端发送一个请求, 服务端将一直在这个连接上等待 (当然有一个超长的超时时间), 直到有数据才返回, 它依然是一个一问一答的模式. 比如著名的 comted.
WebSocket 在握手成功后, 就是 全双工 的 TCP 通道, 数据可以主动从服务端发送到客户端, 处于链接两端的应用没有任何区别.
WebSocket 创建的连接和 Http 的长连接是不一样的. 由于 Http 长连接底层依然是 Http 协议, 所以它还是一问一答, 只是 Hold 住了一条命长点的连接而已.
长轮询和 Http 长连接是阻塞的 I/O, 但 WebSocket 可以是非阻塞的 (具体是多路复用).
四, 如何创建一个连接?
WebSocket 的连接创建是借助 Http 协议进行的. 这样设计主要是考虑兼容性, 在浏览器中就可以很方便的发起请求, 看起来比较具有迷惑性.
下图是一个典型的由浏览器发起的 ws 请求, 可以看到和 http 请求长的是非常相似的. 但是, 它只是请求阶段长得像而已:
请求的地址, 一般是: ws://\*\*\* , 或者是使用了 SSL/TLS 加密的安全协议 wss: , 用来标识是 WebSocket 请求.
1, 首先, 通过 Http 头里面的 Upgrade 域, 请求进行协议转换. 如果服务端支持的话, 就可以切换到 WebSocket 协议. 简单点讲: 连接已经在那了, 通过握手切换成 ws 协议, 就是切换了连接的一个状态而已.
1, Connection 域可以认为是与 Upgrade 域配对的头信息. 像 nginx 等代理服务器, 是要先处理 Connection, 然后再发起协议转换的.
Sec-WebSocket-Key 是随机的字符串, 服务器端会用这些数据来构造出一个 SHA-1 的信息摘要. 如此操作, 可以尽量避免普通 HTTP 请求被误认为 WebSocket 协议.
其他的, 像 Sec-WebSocket * 字样的头信息, 表明了客户端支持的子协议以及其他信息. 像 loT 中很流行的 mqtt, 就可以作为 WebSocket 的子协议.
使用 JavaScript, 可以很容易连接一个 WebSocket 服务端.
- <script>
- var ws = new WebSocket('ws://localhost:80');
- ws.onopen = function () {
- console.log('ws onopen');
- ws.send('from client: hello');
- };
- ws.onmessage = function (e) {
- console.log('ws onmessage');
- console.log('from server:' + e.data);
- };
- ...
- </script>
五, 如何处理数据?
WebSocket 是通过事件通知的方式运行的. 它包含四个事件和两个动作 (发送和关闭).
WebSocket 的事件
事件 | 钩子 | 备注 |
---|---|---|
open | onopen | 连接建立时触发 |
message | onmessage | 客户端接收服务端数据时触发 |
error | onerror | 通信发生错误时触发 |
close | onclose | 连接关闭时触发 |
数据可直接通过 Socket.send() 方法进行传输.
通过 Chrome 的 Inspect->Network->WS, 可以看到页面上的 WebSocket 连接. 如图 Opcode 为 2, 表明它是一个二进制帧.
WebSocket 有类似 tcp 协议的帧格式, 在此不做过多解释.
参考:( https://tools.ietf.org/html/rfc6455#section-5.1)
心跳对应的 ping,pong 操作, opcode 分别是 0x9,0xA. 收到心跳的一方需要自行更新心跳的更新时间. 同 使用 Netty, 我们到底在开发些什么? https://mp.weixin.qq.com/s/-HWW32E2r2KcbwphrsDPFA 介绍的类似, 在一些移动环境中, 需要更加智能的控制心跳.
六, 如何使用 Nginx 做负载均衡?
nginx 官网已经给出了例子. 主要是 Upgrade 和 Connection 头的设置.
- map $http_upgrade $connection_upgrade {
- default upgrade;
- '' close;
- }
- location /chat/ {
- proxy_pass http://backend;
- proxy_http_version 1.1;
- proxy_set_header Upgrade $http_upgrade;
- proxy_set_header Connection $connection_upgrade;
- }
需要注意的是, nginx 做负载均衡, 不需要配置 ip_hash 等参数, nginx 天然支持. 由于 ip_hash 仅使用 ip 地址的前三个数字做 hash, 还有可能造成服务端的不均衡.
七, java 服务端怎么实现?
可以实现 javax.WebSocket 下的包, 简单的实现 ws 服务端. 目前基本可以通过注解的方式去编写代码, 比如 ServerEndpoint .
推荐使用基于 netty 的 netty-socketio 进行服务端的编写. 由于使用的是 netty, 所以能够在多个层面进行切入, 获取一些统计数据, 执行一些控制指令. socketio 是一套解决方案, 它有多个语言的客户端, 并处理了市面上大多数的兼容问题.
八, WebSocket 能干些啥?
通知功能
保持一个长连接, 当服务端游新的消息, 能够实时的推送到使用方. 像知乎的点赞通知, 评论等, 都可以使用 WebSocket 通信.
某些使用 H5 的客户端, 为了简化开发, 也会使用 WebSocket 进行消息的通知, 由于它是实时推送的, 会有更好的用户体验.
数据收集
一些次优级别的数据, 比如行为日志, trace, 异常执栈收集等, 都可以开辟专门的 WebSocket 通道进行传输. 这能够增加信息的集中度, 并能及时的针对用户的行为进行合适的配置推送. 由于大多数浏览器内核都支持, 它将使客户端 APM 编程模型变得简单.
加密 && 认证
虽然使用 Fiddler,Charles 等能够抓到很多 WebSocket 包. 但如果同时开启 SSL, 传输加密后的二进制数据, 会大幅增加破解的成本, 会安全的多.
反向控制钩子
这个... 由于是双工长连接, 服务端完全可以推送一些钩子命令, 甚至直接是代码, 在客户端进行执行. 比如截个屏, 录个音, 种个小马. 用户只要通过了授权申请, 剩下的就随你发挥了.
支付宝偷偷调用你的相机给你拍照的梗, 我是相信的.
想当年, cometd 的出现, 惊为天人, 振奋了很久. 但技术日新月异, cometd 已经衰老, 而 Socket.io 得到了快速发展. WebSocket 经过一段时间的混沌期, 规范已经越来越完善, 使用也越来越方便, 不需要再处理那么多的兼容.
但它的本质, 还是新瓶装旧酒, 换汤不换药. WebSocket 的发展得益于 HTML5 规范的制定. 规范的意义, 就是约束厂商们天马行空的实现, 以及指明发展的方向.
这当然有典型的反例, 那就是 IE . 现在, 只有一群公认的 **, 还坚持在用.
来源: http://www.tuicool.com/articles/7baYjaf