Skip to content

WebSocket

WebSocket 是一种基于 TCP 连接上进行全双工通信的协议,相对于 HTTP 这种非持久的协议来说,WebSocket 是一个持久化网络通信的协议;它不仅可以实现客户端请求服务器,同时可以允许服务端主动向客户端推送数据。在 WebSocket API 中,客户端和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

背景

  1. 在 Web 应用架构中,连接由 HTTP/1.0 和 HTTP/1.1 处理。HTTP 是客户端/服务器模式中 请求一响应 所用的协议,在这种模式中,客户端(一般是浏览器)向服务器提交 HTTP 请求,服务器响应请求的资源(例如HTML页面)。 HTTP 是无状态的,也就是说,它将每个请求当成唯一和独立的。无状态协议具有一些优势,例如,服务器不需要保存有关会话的信息,从而不需要存储数据。但是,这也意味着在每次 HTTP 请求和响应中都会发送关于请求的冗余信息,比如使用 Cookie 进行用户状态的验证;

  2. 随着客户端和服务器之间交互的增加,HTTP 协议在客户端和服务器之间通信所需要的信息量快速增加。 从根本上讲,HTTP 还是 半双工 的协议,也就是说,在同一时刻信息的流向只能单向的:客户端向服务器发送请求(单向),然后服务器响应请求(单向)半双工方式的通信效率是非常低的。

  3. 同时 HTTP 协议有一个缺陷:通信只能由客户端发起。 这种单向请求的特点,注定了如果服务器有状态变化,是无法主动通知客户端的。 为了能够及时的获取服务器的变化,我们尝试过各种各样的方式:

    1. 轮询: 每隔一段时候,就发出一个请求,了解服务器有没有新的信息。不精准,有延时,大量无效数据交换;
    2. 长轮询: 客户端向服务器请求信息,并在设定的时间段内保持连接。直到有服务器有新消息响应,或者连接超时,这种技术常常称作“挂起GET”或“搁置POST”。占用服务器资源,相对轮询并没有优势,没有标准化;

这些方法提供了近乎实时的通信,但是它们也涉及 HTTP 请求和响应首标,包含了许多附加和不必要的首标数据与延迟。此外,在每一种情况下,客户端都必须等待请求返回,才能发出后续的请求,而这显著地增加了延退。同时也极大的增加了服务器的压力;

介绍

Websocket 是一种自然的全双工、双向、单套接字连接。解决了 HTTP 协议中不适合于实时通信的不足。2008年被提出,2011 年成为国际标准。

Websocket 协议能够通过 Web 进行客户端和服务器之间的全双工通信,并支持二进制数据和文本字符串的传输。这个协议由开始的握手和之后的基本消息框架组成,是建立在 TCP 协议上的。相比于 HTTP 协议,Websocket 链接一旦建立,即可进行双向的实时通信;

特点

  1. 建立在 TCP 协议之上,服务器端的实现比较容易。
  2. 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
  3. 数据格式比较轻量,性能开销小,通信高效。
  4. 可以发送文本,也可以发送二进制数据。
  5. 没有同源限制,客户端可以与任意服务器通信。

通信原理

WebSocket 在握手阶段采用的是 HTTP 协议,Websocket 借用了 HTTP 的一部分协议来完成一次握手。(HTTP的三次握手,此处只完成一次)

客户端通过 http 带着信息请求服务器,但同时,携带了Upgrade:websocket和Connection:Upgrade(两根管子),服务器如果支持WebSocket协议(有两根管子的接口),使用Websocket协议返回可用信息,此后信息的传递,均使用这两个管子,除非有一方人为的将管子切断;若服务器不支持,客户端请求链接失败,返回错误信息;

案例

服务端

js
var Websocket = require('websocket').server
var http = require('http')

// 创建 HTTP 服务,作为第一次握手链接使用
var httpServer = http.createServer().listen(8080,function(){
  console.log('http://127.0.0.1:8080')
})

// 创建 websocket 服务实力
var wsServer = new Websocket({
  // 配置依赖的握手 http 服务器
  httpServer:httpServer,
  
  autoAcceptConnections:false
})

// 保存链接池
var conArr = []

// 监听 ws 请求事件
wsServer.on('request',function(request){
  // 获取链接示例
  var connection = request.accept()
  // 保存连接池
  conArr.push(connection)
  // 监听消息事件
  connection.on('message',function(msg){
    console.log(msg)
    // 循环连接池,推送广播消息至客户端
    for(let i = 0;i<conArr.length;i++){
      conArr[i].send(msg.utf8Data)
    }
  })
})

客户端

html

<body>
  <div id="msg"></div>
  <input type="text" id="text">
  <input type="button" value="发送" onclick="send()">

  <script>
    //调用websocket对象建立连接:
    //参数:ws/wss(加密)://ip:port (字符串)
    var websocket = new WebSocket('ws://127.0.0.1:8080')
    // console.log(websocket.readyState) // 0 
    // readyState 
    // 0 链接还没有建立(正在建立链接)
    // 1 链接建立成
    // 2 链接正在关闭
    // 3 链接已经关闭

    // 监听链接开启事件
    websocket.onopen = function () {
      console.log(websocket.readyState)
    }

    // 绑定按钮点击事件
    function send() {
      var text = document.getElementById('text').value
      // ws 消息发送
      websocket.send(text)
    }

    // 监听服务端消息推送事件
    websocket.onmessage = function (back) {
      console.log(back.data)
    }

    // 监听连接错误信息
    // websocket.onerror = function (evt, e) {
    //   console.log('Error occured: ' + evt.data);
    // };

    //监听连接关闭
    // websocket.onclose = function (evt) {
    //   console.log("Disconnected");
    // };


  </script>

</body>

Socket.IO

一个目前最为强大且好用的,基本屏蔽了 websocket 概念的 websocket 库;不用掌握 websocket 相关的知识,只需要按照 Socket.IO 中提供的 API 就能够很好的实现一个 websocket 通信

服务端

js
const { createServer } = require("http");
const { Server } = require("socket.io");

const httpServer = createServer();
const io = new Server(httpServer, {
  cors: {
    origin: "*",
    methods: ["GET", "POST"]
  }
});

io.on("connection", (socket) => {
  socket.on('sendMsg',(data)=>{
    io.emit('pushMsg',data)
  })
});

httpServer.listen(3000, function () {
  console.log('http://127.0.0.1:3000')
});

客户端

html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <script src="https://cdn.socket.io/4.2.0/socket.io.min.js"
    integrity="sha384-PiBR5S00EtOj2Lto9Uu81cmoyZqR57XcOna1oAuVuIEjzj0wpqDVfD0JA9eXlRsj"
    crossorigin="anonymous"></script>
</head>

<body>
  <input type="text" id="text">
  <input type="button" value="发送" onclick="send()">

  <script>
    var socket = io.connect('http://127.0.0.1:3000')
    function send() {
      var text = document.getElementById('text').value
      socket.emit('sendMsg', text)
    }

    socket.on('pushMsg', (data) => {
      console.log(data)
    })

  </script>

</body>

</html>

相关资料

http://www.ruanyifeng.com/blog/2017/05/websocket.html

https://zhuanlan.zhihu.com/p/23467317

https://socket.io/docs/v4/

https://github.com/theturtle32/WebSocket-Node

Released under the MIT License.