websocket 基本使用

393 阅读6分钟

websocket的简单介绍

  • websocket是一种网络通信协议,一般用来进行实时通信会使用到(聊天室)

  • websocket 协议和http协议类似,http协议有一个缺陷,只能由客户端发送请求,服务端根据请求url和请求的参数返回对应的结果

  • websocket是双向通信的,只要websocket连接建立起来,可以有客户端给服务器发送数据,也可以有服务器主动发送给客户端数据。

  • *注:协议标识符是ws 如果是加密的话就是wss服务器网址就是URL

前端实现

  • 前端实现比较简单,有两种方式原生websocket构造函数 或者插件 vue-websocket-io库
var websocket = new WebSocket("ws://localhost:8001/");

前端代码
index.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>client</title>
</head>
<body>
  <h1>聊天室</h1>
  <input id="sendTxt"></input>
  <button id="sendBtn">发送</button>

  <script type="text/javascript">
    var websocket = new WebSocket("ws://localhost:8001/");
    function showMessage (txt,type) {
      var div = document.createElement("div");
      div.innerHTML = txt;
      
      if(type=="in"){
        div.style.color = "green";
      }else if(type=="out"){  
        div.style.color = "grey";
      }
      document.body.appendChild(div);
    }
    websocket.onopen = function () {  
      console.log('websocket open');
      
      document.getElementById("sendBtn").onclick = function () {
      var txt = document.getElementById("sendTxt").value;
      if(txt){
        websocket.send(txt);
        } 
      }
    }
    websocket.onclose = function () {
      console.log('websocket close');
    }
    websocket.onmessage = function (e) {
      console.log(e.data);
      var mes = JSON.parse(e.data);
      showMessage(mes.data,mes.type);
    }
    
  </script>
</body>
</html>

有几个钩子函数

websocket.onopen   	连接成功的回调
websocket.onclose   	连接断开的回调
websocket.onmessage	接收到后台传递的信息回调,可以拿到后台的参数
调用方法
websocket.send(‘传递给后台的参数’); 	可以传递给后台string ,二进制数据(blob对象或Arraybuffer对象)。

后端的实现

源代码
serve.js
var ws = require("nodejs-websocket") // 需要在npm中下载

var PORT = 8001

var clientCount = 0
var server = ws.createServer(function (conn) {
  console.log("New connection")
  clientCount++
  conn.nickname = '用户' + clientCount
  var mes = {}
  mes.type = "in"
  mes.data = "------" + conn.nickname + '已上线'+ "------"
  // console.log(conn)
  broadcast(JSON.stringify(mes))
  console.log('11111', server.connections)
  conn.on("text",function (str) {
    console.log("Received " + str)
    mes.type = "text"
    mes.data = conn.nickname + " : " + str
    broadcast(JSON.stringify(mes))
  })
  conn.on("close",function (code,reason) {
    console.log('Connection closed')
    mes.type = "out"
    mes.data = "------" + conn.nickname + '已下线'+ "------"
    broadcast(JSON.stringify(mes))
  })
  conn.on("error",function (err) {
    console.log('handle err')
    console.log(err)
  })
}).listen(PORT)

console.log('websocket server listening on port ' + PORT)

function broadcast (str) {
  server.connections.forEach(function (connection) {
    connection.sendText(str)
  })
}

代码可以直接copy运行的

node   serve.js
就可以查看页面效果
第六行的server 表示所有的用户连接用户  server.connections里面存放的是数组,每一个连接的socket用户对象
第六行的函数中的conn参数可以获取到当前连接到的用户对象

Event: 'close(code, reason)': 连接关闭时触发
Event: 'error(err)':发生错误时触发,如果握手无效,也会发出响应
Event: 'text(str)':收到文本时触发,str 时收到的文本字符串
Event: 'binary(inStream)':收到二进制内容时触发,inStream时一个ReadableStream

websocketIO插件

  • 要是在vue里面有vue-websocket-io插件
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>client</title>
  <script type="text/javascript" src="socket.io.js"></script>
</head>
<body>
  <h1>聊天室</h1>
  <input id="sendTxt"></input>
  <button id="sendBtn">发送</button>

  <script type="text/javascript">
    var socket = io("ws://localhost:8001/");
    function showMessage (txt,type) {
      var div = document.createElement("div");
      div.innerHTML = txt;
      
      if(type=="enter"){
        div.style.color = "green";
      }else if(type=="leave"){
        div.style.color = "grey";
      }
      document.body.appendChild(div);
    }
    
    console.log('websocket open');
    
    document.getElementById("sendBtn").onclick = function () {
    var txt = document.getElementById("sendTxt").value;
    if(txt){
      socket.emit("message",txt);//socket.emit发送给单个人,io.emit广播
      } 
    }
    
    socket.on('enter',function (data) {
      showMessage(data,'enter');
    })

    socket.on('message',function (data) {
      showMessage(data,'message');
    })

    socket.on('leave',function (data) {
      showMessage(data,'leave');
    })
    
    
  </script>
</body>
</html>
var socket = io("ws://localhost:8001/");
创建一个socket对象
socket可以监听enter事件,其实enter的事件名是与后台定义的
前端定义enter方法,后端触发前端定义的enter事件 连接成功触发
message,和leave也是前端定义的,后端触发 
message事件是接收后台的数据
leave事件后台断开的时候触发前端定义的leave事件,前端收到信息

socket.emit("message",txt); 触发后台定义的message事件给后台发送数据


socket-io后台
var app = require("http").createServer()
var io = require("socket.io")(app) // npm下载

var PORT = 8001
var clientCount = 0
app.listen(PORT, () => {
  console.log("聊天室开了")
})
io.on('connection',function (socket) {
  clientCount++
  socket.nickname = '用户' + clientCount

  io.emit('enter',"------" + socket.nickname + '已上线'+ "------")

  socket.on('message',function (str) {
    io.emit('message',socket.nickname + " : " + str)
  })
  socket.on('disconnect',function () {
    io.emit('leave11',"------" + socket.nickname + '已下线'+ "------")
  })
})
console.log('websocket server listening on port ' + PORT)

require("socket.io")(app) 将app里面注入socket
io 是后台所有的获取所有连接到socket的信息
io.on('connection',function (socket) {})
当用户连接的时候会触发connection事件 socket参数是当前连接信息
手动触发前端定义的enter事件,给前端发现连接成功的信息

socket.on('message',function (str) {})
后端定义事件message 前端想要给后台发送信息

socket.on('disconnect',function () {})
后端定义的当连接断开的时候触发,在触发前端定义接受的事件

scoket.io.js

心跳检测

  • 为什么会有心跳检测
    • 为了证明客户端和服务器还在连接中,如果遭到网络问题等情况,服务端没有触发onclose事件, 这样会产生多余的连接,并且服务端会继续发送消息给客户端,造成数据丢失,因此需要一种机制来检测客户端和服务端是否处于正常连接状态,心跳检测和重连截止就产生了。
  • 如何进行心跳检测和重连
  • 思路是:
  • 每隔一段指定的时间(计时器),向服务器发送一个数据,服务器收到数据后再发送给客户端,正常情况下客户端通过onmessage事件是能监听到服务器返回的数据的,说明请求正常。
  • 如果再这个指定时间内,客户端没有收到服务器端返回的响应消息,就判定连接断开了,使用websocket.close关闭连接。 这个关闭连接的动作可以通过onclose事件监听到,因此在 onclose 事件内,我们可以调用reconnect事件进行重连操作。
具体代码实现
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>client</title>
</head>
<body>
  <h1>聊天室</h1>
  <input id="sendTxt"></input>
  <button id="sendBtn">发送</button>

  <script type="text/javascript">
    function showMessage (txt,type) {
      var div = document.createElement("div");
      div.innerHTML = txt;
      if(type=="in"){
        div.style.color = "green";
      }else if(type=="out"){  
        div.style.color = "grey";
      }
      document.body.appendChild(div);
    }

    createWebSocket()
    /**
     * websocket启动
     */
     function createWebSocket() {
        try {
            if ('WebSocket' in window) {
                websocket = new WebSocket("ws://localhost:8001/");
            } else if ('MozWebSocket' in window) {
                websocket = new MozWebSocket("ws://localhost:8001/");
            } else {
                websocket = new SockJS("ws://localhost:8001/");
            }
            init();
        } catch (e) {
            console.log('catch' + e);
            reconnect();
        }

    }


    function init() {
        //连接成功建立的回调方法
        websocket.onopen = function (event) {
            console.log("WebSocket:已连接");
            //心跳检测重置
            document.getElementById("sendBtn").onclick = function () {
            var txt = document.getElementById("sendTxt").value;
            if(txt){
              websocket.send(txt);
              } 
            }
            heartCheck.reset().start();
        };

        //接收到消息的回调方法
        websocket.onmessage = function (event) {
            // showNotify(event.data);  正式要处理的逻辑
            console.log("WebSocket:收到一条消息", event.data);
            var mes = JSON.parse(event.data);
            showMessage(mes.data,mes.type);
            heartCheck.reset().start();
        };

        //连接发生错误的回调方法
        websocket.onerror = function (event) {
            console.log("WebSocket:发生错误");
            reconnect();
        };

        //连接关闭的回调方法
        websocket.onclose = function (event) {
            console.log("WebSocket:已关闭");
            heartCheck.reset();//心跳检测
            reconnect();
        };

        //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
        window.onbeforeunload = function () {
            websocket.close();
        };

        //关闭连接
        function closeWebSocket() {
            websocket.close();
        }

        //发送消息
        function send(message) {
            websocket.send(message);
        }
    }

    //避免重复连接
    var lockReconnect = false, timeID;

    /**
     * websocket重连
     */
     function reconnect() {
        if (lockReconnect) {
            return;
        }
        lockReconnect = true;
        timeID && clearTimeout(timeID);
        timeID = setTimeout(function () {
            console.log('重连中...');
            lockReconnect = false;
            createWebSocket();
        }, 4000);
    }

    /**
     * websocket心跳检测
     */
     var heartCheck = {
        timeout: 5000,
        timeoutObj: null,
        serverTimeoutObj: null,
        reset: function () {
            clearTimeout(this.timeoutObj);
            clearTimeout(this.serverTimeoutObj);
            return this;
        },
        start: function () {
            var self = this;
            this.timeoutObj && clearTimeout(this.timeoutObj);
            this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);
            this.timeoutObj = setTimeout(function () {
                //这里发送一个心跳,后端收到后,返回一个心跳消息,
                //onmessage拿到返回的心跳就说明连接正常
                websocket.send("HeartBeat");
                console.log('ping');
                self.serverTimeoutObj = setTimeout(function () { // 如果超过一定时间还没重置,说明后端主动断开了
                    console.log('关闭服务');
                    websocket.close();//如果onclose会执行reconnect,我们执行 websocket.close()就行了.如果直接执行 reconnect 会触发onclose导致重连两次
                }, self.timeout)
            }, this.timeout)
        }
    };
  </script>
</body>
</html>