深入浅出FE(十二)浅析websocket

目录

 

一、为什么要有websocket?

二、关于websocket

三、websocket的请求响应过程

四、为什么WebSocket连接可以实现全双工通信而HTTP连接不行呢?

五、WebSocket 的用法

六、服务端的实现

七、websocket的踩坑


一、为什么要有websocket?

websocket的出现是为了弥补http协议服务端无法向客户端主动推送消息。所以以前实现这种场景都是通过用轮询或者Comet。轮询是指浏览器通过JavaScript启动一个定时器,然后以固定的间隔给服务器发请求,询问服务器有没有新消息。这个机制的缺点一是实时性不够,二是频繁的请求会给服务器带来极大的压力。

Comet本质上也是轮询,但是在没有消息的情况下,服务器先拖一段时间,等到有消息了再回复。这个机制暂时地解决了实时性问题,但是它带来了新的问题:以多线程模式运行的服务器会让大部分线程大部分时间都处于挂起状态,极大地浪费服务器资源。另外,一个HTTP连接在长时间没有数据传输的情况下,链路上的任何一个网关都可能关闭这个连接,而网关是我们不可控的,这就要求Comet连接必须定期发一些ping数据表示连接“正常工作”。

二、关于websocket

WebSocket 协议在2008年诞生,2011年成为国际标准。所有浏览器都已经支持。

它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的全双工通信。

三、websocket的请求响应过程

首先,WebSocket连接必须由浏览器发起,因为请求协议是一个标准的HTTP请求,格式如下:

GET ws://localhost:3000/ws/chat HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Origin: http://localhost:3000
Sec-WebSocket-Key: client-random-string
Sec-WebSocket-Version: 13

该请求和普通的HTTP请求有几点不同:

  1. GET请求的地址不是类似/path/,而是以ws://开头的地址;
  2. 请求头Upgrade: websocketConnection: Upgrade表示这个连接将要被转换为WebSocket连接;
  3. Sec-WebSocket-Key是用于标识这个连接,并非用于加密数据;
  4. Sec-WebSocket-Version指定了WebSocket的协议版本。

随后,服务器如果接受该请求,就会返回如下响应:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: server-random-string

该响应代码101表示本次连接的HTTP协议即将被更改,更改后的协议就是Upgrade: websocket指定的WebSocket协议。

版本号和子协议规定了双方能理解的数据格式,以及是否支持压缩等等。如果仅使用WebSocket的API,就不需要关心这些。

现在,一个WebSocket连接就建立成功,浏览器和服务器就可以随时主动发送消息给对方。消息有两种,一种是文本,一种是二进制数据。通常,我们可以发送JSON格式的文本,这样,在浏览器处理起来就十分容易。

四、为什么WebSocket连接可以实现全双工通信而HTTP连接不行呢?

实际上HTTP协议是建立在TCP协议之上的,TCP协议本身就实现了全双工通信,但是HTTP协议的请求-应答机制限制了全双工通信。WebSocket连接建立以后,其实只是简单规定了一下:接下来,咱们通信就不使用HTTP协议了,直接互相发数据吧。

安全的WebSocket连接机制和HTTPS类似。首先,浏览器用wss://xxx创建WebSocket连接时,会先通过HTTPS创建安全的连接,然后,该HTTPS连接升级为WebSocket连接,底层通信走的仍然是安全的SSL/TLS协议。

五、WebSocket 的用法

var ws = new WebSocket("wss://echo.websocket.org");

ws.onopen = function(evt) { 
  console.log("Connection open ..."); 
  ws.send("Hello WebSockets!");
};

ws.onmessage = function(evt) {
  console.log( "Received Message: " + evt.data);
  ws.close();
};

ws.onclose = function(evt) {
  console.log("Connection closed.");
};      

websocket  API 详见MDN:https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket

六、服务端的实现

WebSocket 服务器的实现,可以查看维基百科的列表

常用的 Node 实现有以下三种。

具体的用法请查看它们的文档。

七、websocket的踩坑

1、IP访问和域名访问在浏览器中存储的是两个sessionid

类似二级域名,一级域名,ip访问都会建立不同的session,比如我们在建立连接的时候地址是一个域名地址,但是如果发请求是ip地址,会造成sessionid验证不通过。一个简单取巧的方法:就是前端界面使用js获取浏览器地址中的链接,判断是使用了那种访问方式,然后对应得去建立websocke连接。

2、浏览器不支持websocket请求

因为soketjs在浏览器不支持websoket请求是会自动切换为http请求轮训方式。结果就挂了。

3、项目部署到远程服务器,使用https协议,websocket使用wss://前缀连接,报400

修改Nginx以支持websocket

location / {
    index  index.html index.htm;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_pass   http://127.0.0.1:8382/;
    #注意这里一定要加下面这两行,否则问题还是没解决
    proxy_set_header Upgrade $http_upgrade; # allow websockets
    proxy_set_header Connection "upgrade";   
}
location /socketServer {
    proxy_pass http://127.0.0.1:8382/platform/socketServer;
    proxy_set_header Host $host; # pass the host header
    proxy_set_header Upgrade $http_upgrade; # allow websockets
    proxy_set_header Connection "upgrade";            
    proxy_set_header X-Real-IP $remote_addr; # Preserve client IP
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_http_version 1.1;
}

4、连接会自动断开连接

解决方法:心跳检测。

实现心跳要双向,前端发一个数据给服务器,服务器也要返回一个数据给前端,不然nginx转发代理的时候,认为客服端还连着,如果一直没有接收到服务端的数据,会认为服务端的连接已失效,然后就会断开。

一种解决方式是前端发送“ping”给服务器,服务器收到并返回“pong”字符串。

【注意】心跳要每隔一段时间发送,前端的setTimeOut可以实现

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章