赞
踩
WebSocket
?概念
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的网络传输协议。
WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
特点
TCP连接,于HTTP协议兼容
双向通信,主动推送(服务器端向客户端)
无同源限制,协议标识符是ws(加密是wss)
通信方式:
单工通信
半双工通信
全双工通信
对比分析
Http:
无法监听连续变化
效率低下
浪费资源
Websocket
长连接形式,节省服务器资源和带宽,可以更好的进行实时通信
问题分析
问:长连接是否消耗服务器资源?
答:不会,Websocket设计便是为了解决服务器资源的问题,相对于Http只是建立连接,并未在逻辑上进行任何处理或者是查询数据库,所以不会造成系统资源的浪费,同时网络资源上,由于进行通信所以不会占用网络资源,也就是所说的带宽,保持长连接的状态同样不会造成网络资源的浪费,在没有发送消息的时候,整个信道处于空的状态,并不占用带宽。
相关内容
Http:超文本传输协议,是互联网上应用最为广泛的一种网络协议,是一个客户端和服
务器端请求和应答的标准(TCP),用于从 WWW 服务器传输超文本到本地浏览器的传
输协议,它可以使浏览器更加高效,使网络传输减少
ajax轮询:
方式1:设定一个定时器,无论有无结果返回,时间一到就会继续发起请求,这种轮询耗费资源,也不一定能得到想要的数据,这样的轮询是不推荐的。
方式2:
轮询就是在第一次请求的时候,如果返回数据了那么就在成功的回调里面再次发起这个请求,就像递归一样,调用本方法。
如果时间太久,失败了,同样的再次调用这个请求,也就是本函数。当然,长轮询也需要后台配合,没有数据改变的时候就不用返回,或者约定好逻辑。
ws常用前端库
ws(实现原生协议,特点:通用、性能高、定制性强)
socket.io(向下兼容,特点:适配性强、性能一般)
学习地址
第一个websocket应用
前置:应用分客户端(client---浏览器端)和服务端(server---node作为服务端)
服务端(node)
空目录 npm init -y npm install ws
服务端 index.js const WebSocket = require('ws') const wss = new WebSocket.Server({ port: 3000 }) wss.on('connection', function connection (ws) { console.log('一个客户以连接'); })
客户端 index.html
客户端
var ws = new WebSocket('ws://localhost:3000') ws.onopen = function () { console.log(ws.readyState); ws.send('client:hello') }
注意事项:
当服务器接收到来自客户端消息的时候,出现编码问题,显示为GBK编码格式,例如:<Buffer 63 6c 69 65 6e 74 20 3a 20 68 65 6c 6c 6f>
,这里安装依赖进行解码
npm i iconv-lite
解码 console.log(msg); 解码前:<Buffer 63 6c 69 65 6e 74 20 3a 20 68 65 6c 6c 6f> console.log(iconv.decode(msg, 'gbk')); 解码后:client : hello
心跳检测
判断客户端和服务端的连接是否牢靠、是否正常,二者可以互相判断,是一个双向的过程,服务端定时的向客户端发送消息,并且客户端及时的做出回应,保证连接正常 ,确保聊天室的在线人数实时封更新
服务端
给当前用户一个初始化变量,维护当前用户是否登录,判断是否开始心跳检测,同时初始化一个是否处于聊天室的变量-作为在线状态(给定初始值0,登录后改为1,发送心跳检测定义为404,接收到用户的反馈后修改为2,表示当前用户连接状态良好)
开启定时器,判断用户当前状态为false时主动断开用户连接,更新数据
接收用户反馈,更新当前用户状态
客户端
定义初始化函数方便重连
定义维护需要变量
定义定时器,用户检测服务端是否再规定时间内发起心跳检测
捕捉到异常,开启重连
代码
服务端(index.js)
- const WebSocket = require('ws');
- const iconv = require('iconv-lite'); //解析来自用户的消息
- const wss = new WebSocket.Server({
- port: 3000
- })
- let group = {}
- const timeInterval = 1000 //发送心跳请求间隔
- wss.on('connection', function (ws) {
- console.log("一个客户已连接");
- ws.isActive = 0 //初始连接状态
- ws.isLogin = false
- ws.on('message', function (msg) {
- const msgObj = JSON.parse(iconv.decode(msg, 'gbk'))
- if (msgObj.event == 'enter') {
- ws.name = msgObj.name
- ws.roomId = msgObj.roomId
- console.log(msgObj);
- if (typeof group[ws.roomId] === 'undefined') {
- group[ws.roomId] = 1
- } else {
- group[ws.roomId]++
- }
- ws.isActive = 1
- ws.isLogin = true
- }
- if (msgObj.event == 'heartbeat' && msgObj.message === 'pong') {
- ws.isActive = 1
- return
- }
- //实现广播
- wss.clients.forEach((client) => {
- if (client.readyState === WebSocket.OPEN && client.roomId === ws.roomId) {
- msgObj.name = ws.name
- msgObj.num = group[ws.roomId]
-
- client.send(JSON.stringify(msgObj))
- }
- })
- })
- ws.on('close', function () {
- if (ws.name) {
- group[ws.roomId]--
- }
- let msgObj = {}
- wss.clients.forEach((client) => {
- if (client.readyState === WebSocket.OPEN && client.roomId === ws.roomId) {
- msgObj.name = ws.name
- msgObj.num = group[ws.roomId]
- msgObj.event = 'exit'
- client.send(JSON.stringify(msgObj))
- }
- })
- })
- })
- setInterval(() => {
- wss.clients.forEach((ws) => {
- // 主动发送心跳检测请求
- // 当客户端返回消息之后,主动设置flag为在线
- if (ws.isLogin == true) {
- // console.log(ws.isActive);
- if (ws.isActive == '404') {
- group[ws.roomId]--
- ws.isLogin = false
- return ws.terminate()
- }
- // console.log('---');
- ws.isActive = '404'
- ws.send(JSON.stringify({
- event: 'heartbeat',
- message: 'ping',
- num: group[ws.roomId]
- }))
- }
- })
- }, timeInterval)

客户端 (index.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>
- <style>
- input {
- outline-style: none;
- border: 1px solid #ccc;
- border-radius: 3px;
- padding: 6px;
- width: 300px;
- font-size: 14px;
- font-family: "Microsoft soft";
- }
-
- input:focus {
- border-color: #66afe9;
- outline: 0;
- -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6);
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6)
- }
-
- #login {
- display: block;
- }
-
- #chatRoom {
- display: none
- }
- </style>
- </head>
-
- <body>
- <div id='login'>
- <h1>进入聊天室</h1>
- <span>用户名:</span><input type="text" class='msg'>
- <span>房间号:</span><input type="text" class='room'>
- <button class='btn' id="loginBtn">登录</button>
- </div>
- <div id="chatRoom">
- <h1>聊天室</h1>
- <p>在线人数:<span id="nums">0</span></p>
- <input type="text" class='msg'>
- <button class='submitBtn' >发送消息</button>
- <button onclick="clsoeFun()">关闭连接</button>
- <ul id="content">
- </ul>
- </div>
- <script>
- var MyName, MyRoom //数据存储,重连时使用
- var isLogin = false //判断是否为登录状态
- var isReload = false //当监听到连接断开时,更新为true连接成功后恢复false
- var content = document.getElementById('content')
- var nums = document.getElementById('nums')
- var handle //作为定时器监听服务器状态,服务器断开开始尝试重连
- webScoketInit() //初始化
- function webScoketInit () {
- var ws = new WebSocket('ws://localhost:3000')
- ws.onopen = function () {
- console.log(ws.readyState);
-
- if (isReload) {
- // 如果重新连接服务器,将原本数据重新传入
- let obj = {
- event: 'enter',
- name:MyName,
- roomId:MyRoom,
- }
- ws.send(JSON.stringify(obj))
- isReload = false
- }
- }
- ws.onmessage = (event) => {
- if (!isLogin) return;
- let obj = JSON.parse(event.data)
- showFun(obj)
- }
- ws.onclose = function () {
- console.log("close" + ws.readyState);
- }
- ws.onerror = function () {
- console.log('error' + ws.readyState);
- // 连接失败后一秒进行短线重连
- setTimeout(function () {
- isReload = true
- webScoketInit()
- }, 1000)
- }
- function showFun (obj) {
- let str = ''
- switch (obj.event) {
- case 'enter':
- str = `欢迎${obj.name}进入聊天室`
- nums.innerHTML = obj.num
- break
- case 'exit':
- str = `${obj.name}离开进入聊天室`
- nums.innerHTML = obj.num
- break
- case 'auth':
- return
- case 'heartbeat':
- checkServer() //timeInterval+ t
- ws.send(JSON.stringify({
- event: 'heartbeat',
- message: 'pong'
- }))
- return
- default:
- str = `${obj.name}:${obj.message}`
-
- }
- content.innerHTML += `
- <li>${str}</li>
- `
- }
- let submitBtn = document.querySelector(".submitBtn")
- console.log(submitBtn);
- submitBtn.addEventListener("click",submitFun)
- function submitFun () {
- let input = document.getElementsByClassName('msg')[1]
- let value = input.value
- let obj = {
- event: 'message',
- message: value
- }
- ws.send(JSON.stringify(obj))
- value.value = ''
- }
- let loginBtn = document.getElementById('loginBtn')
- loginBtn.addEventListener('click', login)
- function login () {
- let inputName = document.getElementsByClassName('msg')[0]
- let inputRoomId = document.getElementsByClassName('room')[0]
- let roomId = inputRoomId.value
- let name = inputName.value
- MyName = name
- MyRoom = roomId
- if (name.trim() == '') {
- alert('请输入名称')
- } else {
- let obj = {
- event: 'enter',
- name,
- roomId
- }
- ws.send(JSON.stringify(obj))
- inputName.value = ''
- inputRoomId.value = ''
- document.getElementById('login').style.display = 'none'
- document.getElementById('chatRoom').style.display = 'block'
- isLogin = true
- }
- }
- function clsoeFun () {
- ws.close()
- }
- /*
- 接收到服务器发送的心跳检测,
- 回复的同时开始开启检测(考虑延时t)如果服务端未再规定时间内再次发送请求,
- 理解为服务端异常,触发重连
- */
- function checkServer () {
- clearTimeout(handle)
- handle = setTimeout(function () {
- isReload = true
- webScoketInit()
- }, 1000 + 500)
- }
- }
- </script>
- </body>
-
- </html>

socket.io
特点:
易用性:socket.io封装了服务端和客户端,使用起来非常简单方便。
内置心跳检测:短线重连
广播自动过滤当前发消息用户
跨平台:socket.io支持跨平台,这就意味着你有了更多的选择,可以在自己喜欢的平台下开发实时应用。
自适应:它会自动根据浏览器从WebSocket、AJAX长轮询、Iframe流等等各种方式中选择最佳的方式来实现网络实时应用,非常方便和人性化,而且支持的浏览器最低达IE5.5。
服务端
空目录 npm init -y npm i socket-io npm i express
- //server.js
- const app = require('express')();
- const http = require('http').createServer(app);
- const io = require('socket.io')(http)
-
- app.get('/', function (req, res) {
- res.sendFile(__dirname + '/index.html') //创建默认打开文件
- })
-
- io.on('connection', function (socket) {
- console.log('a socket is connected');
- socket.on('chatEvent', function (msg) { //监听客户端注册的charEvent事件,接收发送过来的消息
- console.log(msg);
- // socket.send('server 收到')
- //广播,向聊天室其他成员发送消息,本人并不会接收,同时也无需接收
- socket.broadcast.emit('SreverMsg', msg) //服务器注册ServerMsg事件,向所有用户广播,广播消息,实现群聊功能,自动过滤当前用户
- //单独向当前用户发送消息
- socket.send(msg)
- })
- })
- http.listen(3000, function () {
- console.log("3000端口已开启");
- })

客户端
- //index.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>
- <style>
- input {
- outline-style: none;
- border: 1px solid #ccc;
- border-radius: 3px;
- padding: 6px;
- width: 300px;
- font-size: 14px;
- font-family: "Microsoft soft";
- }
-
- input:focus {
- border-color: #66afe9;
- outline: 0;
- -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6);
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6)
- }
- </style>
- </head>
- <input type="text" id='msg'>
- <button id='btn'>发送消息</button>
- <body>
- <!-- <script src="/socket.io/socket.io.js"></script> -->
- <script src="https://cdn.bootcdn.net/ajax/libs/socket.io/4.5.2/socket.io.js"></script>
- <script>
- var socket = io() //初始化
- document.getElementById('btn').addEventListener('click', function (e) {
- var value = document.getElementById('msg').value
- socket.emit('chatEvent', value) //注册发送消息事件,向服务器发送消息
- document.getElementById('msg').value = ''
- })
- //监听服务器单独发送的消息
- socket.on('message', function (msg) {
- console.log(msg);
- })
- //监听服务器注册的广播事件,接收消息
- socket.on('SreverMsg', function (msg) {
- console.log(msg);
- })
- </script>
- </body>
- </html>

socket.js引入方式
<script src="/socket.io/socket.io.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/socket.io/4.5.2/socket.io.js"></script>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。