一文读懂WebSocket

Websocket是一个长久化的网络通信协定,能够在单个 TCP 连贯上进行全双工通信,没有了Request和Response的概念,两者位置齐全平等,容许服务端被动向客户端发送数据,一旦建设连贯,客户端和服务端之间即可实时进行双向数据传输。

它的最大特点就是,服务器能够被动向客户端推送信息,客户端也能够被动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。

(1)建设在 TCP 协定之上,服务器端的实现比拟容易。
(2)没有同源限度,客户端能够与任意服务器通信。
(3)协定标识符是ws(如果加密,则为wss),服务器网址就是 URL。

客户端

因为websocket是HTML5开始提供的,因而客户端能够新建ws对象用于建设通信。

let ws = new WebSocket('ws://localhost:3000');ws.onopen = () => {    console.log('open connection');}ws.onmessage = (event) => {    //客户端接管到服务端数据的回调    console.log(event,'onmessage event');}ws.onclose = () => {    console.log('close connection')}

以上是websocket的三个事件,还有一个是error事件,显著是在通信产生谬误时触发的事件。

另外websocket还有两个罕用办法,在后续的例子中将会应用它们:

ws.send();        //应用连贯发送数据ws.close();        //敞开连贯

服务端

node中,应用最宽泛的是通过ws模块来创立websocket服务,应用前须要先装置这个模块:

const express = require('express');const SocketServer = require('ws').Server;const port = 3000;const server = express().listen(port, () => {    console.log(`listening to ${port}`);})const wss = new SocketServer({server});wss.on('connection', (ws) => {    console.log('Client connected.');    ws.on('message', (data) => {        console.log(data);        //服务端发送数据到客户端        ws.send(data);    })    ws.on('close', () => {        console.log('Close connected.')    })})

启动服务,运行:

node server.js

能看到服务启动:

listening to 3000

此时拜访客户端页面,在控制台能看到:

open connection

输出ws,而后回车显示ws对象信息:

WebSocket{    binaryType: "blob"    bufferedAmount: 0    extensions: ""    onclose: () => { console.log('close connection') }    onerror: null    onmessage: (event) => { console.log(event,'onmessage event'); }    onopen: () => { console.log('open connection'); }    protocol: ""    readyState: 1    url: "ws://localhost:3000/"}

这里提一下readyState,他是返回实例对象的以后状态,共有四种:

状态代码含意
CONNECTING0示意正在连接
OPEN1示意连贯胜利,能够通信
CLOSING2示意连贯正在敞开
CLOSED3示意连贯曾经敞开或关上连贯失败

这时在控制台手动调用send发送音讯:

ws.send('hello')

实例对象的send()办法用于向服务端发送数据,触发客户端ws.onmessage事件,同时服务端的ws.on('message')事件也被触发,服务端接管客户端发送过去的音讯。

客户端打印event回传信息:

在data属性中能够看到”hello“信息。

同时,服务端能够被动间断向客户端发送音讯:

wss.on('connection', (ws) => {    console.log('Client connected.');    const sendNowTime = setInterval(() => {         ws.send(String(new Date()))    },1000)});//服务端每秒向客户端发送实时音讯

客户端连贯后就会开始定时接管服务端发来的数据,直至手动敞开连贯或服务。

ws.close()

模仿多人聊天

上面提供一个残缺的例子用于模拟两人通过websocket进行实时聊天:

客户端代码

<div id="app">    <div class="box">        <ul>            <li v-for="(item,index) in news" :class="item.nickname == nickname ? 'me' : 'other'" :key="index">                ![](item.nickname == nickname ? )                <p>{{item.message}}</p>            </li>        </ul>        <div>            <input type="text" v-model="send">            <button @click="sendMsg">发送</button>        </div>    </div></div>
*{    margin: 0 ;    padding: 0;}ul,li{    list-style: none;}.box{    width: 500px;    margin: 0 auto;}.box ul li{    display: flex;        align-items: flex-start;    margin-bottom: 10px;}.box ul li img{    width: 40px;    height: 40px;    margin-right: 8px;}.box ul li p{    font-size: 14px;    line-height: 20px;    max-width: 300px;    background: #f1f1f1;    border-radius: 4px;    padding: 10px 20px;}.me{    justify-content: flex-start;    flex-direction: row;}.other{    flex-direction: row-reverse;    order:1}
let app = new Vue({    el: '#app',    data(){        return {            socketUrl: 'ws://localhost:8000?userName=',            nickname: '',            news: [],            send: '',            userList: []        }    },    mounted(){        this.initChatting();        let url = window.location.href;        this.nickname = url.split('=')[1]    },    methods: {        initChatting(){            if(window.WebSocket){                this.client = new WebSocket(this.socketUrl + this.nickname);                this.client.onopen = (e) => {                    if(e.type == 'open'){                        console.log('客户端连贯')                    }                }                //客户端接管到服务端数据的回调                this.client.onmessage = (e) => {                    let data = JSON.parse(e.data);                    if(data instanceof Array == true){                        this.userList = data;       //在线用户数变动                    }else{                        //聊天信息                        this.news.push(data);                    }                    console.log(this.news)                }                this.client.onclose = (e) => {                    this.client = null;                }                this.client.onerror = () => {                    if(!this.client){                        console.log("服务连贯失败")                    }                }            }else{                alert("浏览器版本过低,不反对websocket.")            }        },        sendMsg(){            let data = {                message: this.send,                uid: new Date().getTime(),                nickname: this.nickname,                date: new Date()            }            this.client.send(JSON.stringify(data))            this.send = "";        }    }})

服务端代码

const WebSocket = require('ws').Server;const moment = require('moment');const url = require('url');const querystring = require('querystring');let wss = new WebSocket({    url: 'localhost',    port: 8000})let id = 0;let onlineMemberList = [];let defaultUser = 'user';wss.on('connection', (ws,req) => {    console.log('connected.')    id++;    ws.id = id;    let arg = url.parse(req.url).query;    let nameObj = querystring.parse(arg);    let userName;    if(nameObj.username){        userName = decodeURIComponent(username);    }else{        userName = defaultUser + id    }        let userInfo = {        userName,        socketId: id,        date: moment().format('MMMM Do YYYY, hh:mm:ss a')    }        for(let i=0;i<onlineMemberList.length;i++){        if(userInfo.userName === onlineMemberList[i].userName){            onlineMemberList[i] = userInfo;            wss.clients.forEach(item => {                item.send(JSON.stringify(onlineMemberList))            })            return;        }    }    onlineMemberList.push(userInfo);    wss.clients.forEach(item => {        item.send(JSON.stringify(onlineMemberList));    })    ws.on('message',(data) => {        let newData = JSON.parse(data);        console.log(data,'data')        newData.serveDate = moment().format('MMMM Do YYYY, h:mm:ss a');        wss.clients.forEach(item => {            // 监听客户端发来的数据,间接将信息一成不变,全副返回回去            item.send(JSON.stringify(newData));        })    })    ws.on('close',(e) => {        onlineMemberList = onlineMemberList.filter(item => {            return item.socketId != ws.id;        })        wss.clients.forEach(item => {            item.send(JSON.stringify(onlineMemberList));        })    })    ws.on('error',(e) => {        console.log('客户端异样',e)    })})

实现成果

跨域发送接管信息

因为websocket能够建设客户端和服务端点对点的链接,因而不受跨域问题影响,也可用于解决客户端跨域问题:

let ws = new WebSocket("ws://localhost:8200"); //建设连贯ws.onopen = function () { //关上协定    console.log("连贯胜利");}ws.onmessage = function (mes) { //发送数据到服务端    console.log(mes);}// ws.addEventListener("message", function (e) {});document.querySelector(".btn").onclick = function () {    let input = document.querySelector(".input").value;    console.log("客户端发送给服务端的信息:" + input);    ws.send(input); //如果服务端敞开协定后,即执行ws.close()后,此时会报错: WebSocket is already in CLOSING or CLOSED state.};

看完这篇文章,心愿读者能对websocket能有一个初步的理解,在工作、练习中能够更快地上手利用。