你好,websocket

来了 2019-11-04

你好,WebSocket

定义

WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议(注意是协议)。

WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

为什么使用WebSocket

原本的通过HTTP协议的客户端与服务端的通信,只能由客户端发起。HTTP 协议做不到服务器主动向客户端推送信息。而且一次客户端与服务端的通信完成之后,客户端与服务端的链接就会断开,但是WebSocket是长链接,当一次连接建立以后,如果不是其中一端主动断开连接,两端的连接就会一直连接着。

WebSocket是为了解决类似聊天室的场景,原本HTTP协议这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。我们只能使用"轮询":每隔一段时候,就发出一个询问,了解服务器有没有新的信息。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。因此,工程师们一直在思考,有没有更好的方法。WebSocket 就是这样发明的。

一:组成和特点

长连接:一个连接上可以连续发送多个数据包,在连接期间,如果没有数据包发送,需要双方发链路检查包。

TCP/IP:TCP/IP属于传输层,主要解决数据在网络中的传输问题,只管传输数据。但是那样对传输的数据没有一个规范的封装、解析等处理,使得传输的数据就很难识别,所以才有了应用层协议对数据的封装、解析等,如HTTP协议。

HTTP:HTTP是应用层协议,封装解析传输的数据。
从HTTP1.1开始其实就默认开启了长连接,也就是请求header中看到的Connection:Keep-alive。但是这个长连接只是说保持了(服务器可以告诉客户端保持时间Keep-Alive:timeout=200;max=20;)这个TCP通道,直接Request - Response,而不需要再创建一个连接通道,做到了一个性能优化。但是HTTP通讯本身还是Request - Response。

socket:与HTTP不一样,socket不是协议,它是在程序层面上对传输层协议(可以主要理解为TCP/IP)的接口封装。
我们知道传输层的协议,是解决数据在网络中传输的,那么socket就是传输通道两端的接口。所以对于前端而言,socket也可以简单的理解为对TCP/IP的抽象协议。

WebSocket:
WebSocket是包装成了一个应用层协议作为socket,从而能够让客户端和远程服务端通过web建立全双工通信。websocket提供ws和wss两种URL方案。

WebSocket 实例
WebSocket 协议本质上是一个基于 TCP 的协议。

为了建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求和通常的 HTTP 请求不同,包含了一些附加头信息,其中附加头信息"Upgrade: WebSocket"表明这是一个申请协议升级的 HTTP 请求,服务器端解析这些附加的头信息然后产生应答信息返回给客户端,客户端和服务器端的 WebSocket 连接就建立起来了,双方就可以通过这个连接通道自由的传递信息,并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接。

你好,websocket你好,websocket

二:WebSocket使用

创建WebSocket对象

var ws = new WebSocket(url, [protocol] );

以上代码中的第一个参数 url, 指定连接的 URL。第二个参数 protocol 是可选的,指定了可接受的子协议。

1:属性

ws.readyState:

0 - 表示连接尚未建立。

1 - 表示连接已建立,可以进行通信。

2 - 表示连接正在进行关闭。

3 - 表示连接已经关闭或者连接不能打开。

ws.bufferedAmount 实例对象的bufferedAmount属性,表示还有多少字节的二进制数据没有发送出去。它可以用来判断发送是否结束。

var data = new ArrayBuffer(10000000);
socket.send(data);

if (socket.bufferedAmount === 0) {
  // 发送完毕
} else {
  // 发送还没结束
}
2:事件

open: ws.onopen 连接建立时触发

ws.onopen = function () {
  ws.send('Hello Server!');
}

如果要指定多个回调函数,可以使用addEventListener方法。

ws.addEventListener('open', function (event) {
  ws.send('Hello Server!');
});

message:ws.onmessage 客户端接收服务端数据时触发

注意,服务器数据可能是文本,也可能是二进制数据(blob对象或Arraybuffer对象)。

error: ws.onerror 通信发生错误时触发

close: ws.onclose 连接关闭时触发

3:方法

ws.send() 向服务器发送数据

ws.close() 关闭连接

三:websocket的坑

1:WebSocket心跳及重连机制

在使用websocket的过程中,有时候会遇到网络断开的情况,但是在网络断开的时候服务器端并没有触发onclose的事件。这样会有:服务器会继续向客户端发送多余的链接,并且这些数据还会丢失。所以就需要一种机制来检测客户端和服务端是否处于正常的链接状态。因此就有了websocket的心跳了。还有心跳,说明还活着,没有心跳说明已经挂掉了。

实现心跳检测的思路是:每隔一段固定的时间,向服务器端发送一个ping数据,如果在正常的情况下,服务器会返回一个pong给客户端,如果客户端通过
onmessage事件能监听到的话,说明请求正常

实现心跳检测的思路是:每隔一段固定的时间,向服务器端发送一个ping数据,如果在正常的情况下,服务器会返回一个pong给客户端,如果客户端通过
onmessage事件能监听到的话,说明请求正常

心跳连接:
 //添加心跳防止断开连接
  let time=setInterval(() => {     
    ws.send('heartbeat')
  }, 9*60*1000);        //每9分钟 发送一次 防止断开

2:websocket和http长连接的区别

我们都知道现在我们普遍使用的HTTP1.1,也是有长连接的,也就是keep-alive。所以在学习的时候我们一定会有疑问:不都是长连接吗?有啥区别啊?

先说 comet 和 WebSocket 表现的区别:
comet 发送 http 请求后服务端如果没有返回则连接是一直连着的,等服务端有东西要“推送”给浏览器时,相当于给之前发送的这个 http 请求回了一个 http 响应。然后这个保持的时间比较长的 http 连接就断了。然后浏览器再次发送一个 http 请求,服务器端再 hold 住不返回,等待有东西需要“推送”给浏览器时,再给这个 http 请求一个响应,然后断开连接。循环往复。一旦浏览器不给服务器发送 http 请求,那么服务器是不能主动给浏览器推送消息的,因为根本没有连着的连接给你推。

WebSocket 则不同,它握手后建立的连接是不会断的(除了意外情况和程序主动掐断)。不需要浏览器在每次收到服务器推送的消息后再发起请求。而且服务器端可以随时给浏览器推送消息,不需要等浏览器发 http 请求,因为 WebSocket 的连接一直在没断。

为什么会有这样的区别?

这是协议层面的区别。http 协议规定了 http 连接是一个一来(request)一回(response)的过程。一个请求获得一个响应后必须断掉。而且只有先有请求才会有响应。拿 http1.1 keep-alive 来说,即使底层 tcp 连接没有断,服务端无缘无故给浏览器发一个 http 响应,浏览器是不收的,他找不到收的人啊,因为这个响应没有对应的请求。你看 ajax 必须先发请求才会有一个 onsuccess 回调来响应这个请求。这个 onsuccess 的回调会在你 ajax 不发送的情况下被调用到吗?

而 WebSocket 协议不同,他通过握手之后规定说你连接给我保持着,别断咯。所以浏览器服务器在这种情况下可以相互的发送消息。浏览器端 new 一个 WebSocket 之后注册 onmessage 回调,那么这个 onmessage 可以被反复调用,只要服务器端有消息过来。而不会说是 new 一个 WebSocket onmessage 只会被调用一次,下次还得再 new 一个 websocket。

上面说到 http 连接,tcp 连接,websockt 连接到底啥区别。其实这是新人最容易搞不懂的地方。接下来我就要胡诌了,为啥说胡诌,因为我只是看了个皮毛,然后按我自己的理解说下区别。网络5层分层(自下而上):

物理层

数据链路层

网络层

传输层

应用层

http,websocket都是应用层协议,他们规定的是数据怎么封装,而他们传输的通道是下层提供的。就是说无论是 http 请求,还是 WebSocket 请求,他们用的连接都是传输层提供的,即 tcp 连接(传输层还有 udp 连接)。只是说 http1.0 协议规定,你一个请求获得一个响应后,你要把连接关掉。所以你用 http 协议发送的请求是无法做到一直连着的(如果服务器一直不返回也可以保持相当一段时间,但是也会有超时而被断掉)。而 WebSocket 协议规定说等握手完成后我们的连接不能断哈。虽然 WebSocket 握手用的是 http 请求,但是请求头和响应头里面都有特殊字段,当浏览器或者服务端收到后会做相应的协议转换。所以 http 请求被 hold 住不返回的长连接和 WebSocket 的连接是有本质区别的。

四:node.js+express+websocket实现服务端推送

使用socket.io

socket.io文档地址:https://socket.io/docs/

不懂得地方可以看文档

开始:
npm install socket.io socket.io-client --save
1:客户端
let socket = io("ws://localhost:3001"); // 建立链接
    socket.emit('msg', {message:"你好服务器,我是客户端"}); //向服务器发送消息

    socket.on('msg',function(data){ // 监听服务端的消息“msg”
        console.log(data);
    });
2:服务端代码
let express = require('express');
let router = express.Router();
let app = require('express')();
let server = require('http').createServer(app);
let io = require('socket.io')(server);

io.on('connection', function(socket){ // socket相关监听都要放在这个回调里
    console.log('a user connected');
    
    // 连接断开时触发的回调
    socket.on("disconnect", function() {
        console.log("a user go out");
    });
     
    socket.on("msg", function(obj) {
      // 打印客户端传过来的消息
      let { message } = obj
      console.log(message);
      
        //延迟3s返回信息给客户端
        setTimeout(function(){
            io.emit("msg", '你好客户端,我是服务器');
        },3000);
    });
});
//开启端口监听socket
server.listen(3001);
 
router.get('/', function(req, res, next) {
    res.render('im/imRoom');
});
module.exports = router;

运行结果:

服务端:
a user connected
你好服务器,我是客户端

服务端:
你好客户端,我是服务器(3秒后)

本人菜鸟,以上就是我对websocket的一些学习,很多不足以后会持续进行补充。

相关推荐