什么是websocket?
websocket是一种网络传输协议,可在单个tcp连接上进行全双工通信,位于OSI模型的应用层。
特点:
- TCP链接,与- HTTP协议兼容
- 双向通信,主动推送(服务端向客户端)
- 无同源限制,协议标识符为ws(加密wss)
应用场景
- 聊天、消息、点赞
- 直播评论(弹幕)
- 游戏、协调编辑、基于位置的应用
开始你的第一个websocket应用
websocket常用前端库
- ws (实现原生协议,特点:通用、性能高,定制型强)
- socket.io (向下兼容协议,特点:适配性强,性能一般)
3分钟编写一个ws应用
- 新建server目录,通过npm init -y初始化server目录
- npm install ws安装ws库
- 新建index.js作为入口文件| 12
 3
 4
 5
 6
 
 | const WebSocket = require('ws')const wss = new WebSocket.Server({port:3000})
 
 wss.on('connection',function connection(ws){
 console.log('on client is connected')
 })
 
 |  
 
- 根目录下新建一个client目录,并添加index.html文件| 1
 | var ws =  new WebSocket('ws://127.0.0.1:3000')
 |  
 
- 通过node index.js启动服务端
- 访问客户端的index.html,我们可以看到服务端控制台输出有用户连接了
客户端中使用ws方法与服务端类似
常见API介绍
https://developer.mozilla.org/zh-CN/docs/Web/API/WebSocket
websocket常见状态
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 
 | ws.onopen = function(){console.log('open'+ws.readyState)
 ws.send('hello from client')
 }
 ws.onmessage = function(msg){
 console.log('message'+ws.readyState)
 console.log(msg)
 }
 
 ws.onclose = function(){
 console.log('close'+ ws.readyState)
 console.log('yiguanbi')
 }
 
 ws.onerror = function(){
 console.log('error'+ ws.readyState)
 }
 
 | 
实现一个ws广播
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | 
 ...
 wss.clients.forEach((client)=>{
 
 if(ws!==client&&client.readyState===WebSocket.OPEN){
 client.send(msg.toString())
 }
 })
 ...
 
 | 
统计进入聊天室的人数
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 
 | 
 sendMsg(){
 this.lists.push(this.name+":"+this.message)
 this.ws.send(JSON.stringify({
 event:'message',
 message:this.message,
 name:this.name
 }))
 this.message=''
 },
 
 onMessage(event){
 if(this.isshow) return
 var obj = JSON.parse(event.data)
 if(obj.event==='enter'){
 this.lists.push('欢迎'+obj.message+'加入聊天室')
 }else if(obj.event ==='out'){
 this.lists.push(obj.name+'离开了聊天室')
 }else {
 if(obj.name!==this.name){
 this.lists.push(obj.name+":"+obj.message)
 }
 }
 this.num = obj.num
 },
 
 
 
 
 let num = 0
 
 ws.on("message", function (msg) {
 const msgObj = JSON.parse(msg);
 if (msgObj.event === "enter") {
 ws.name = msgObj.message;
 num++;
 }
 
 
 wss.clients.forEach((client) => {
 if (client.readyState === WebSocket.OPEN) {
 
 msgObj.num = num;
 client.send(JSON.stringify(msgObj));
 }
 });
 });
 
 
 ws.on("close", function () {
 if(ws.name){
 num--;
 }
 let msgObj = {}
 
 wss.clients.forEach((client) => {
 if (client.readyState === WebSocket.OPEN) {
 
 msgObj.num = num;
 msgObj.name = ws.name;
 msgObj.event = 'out'
 client.send(JSON.stringify(msgObj));
 }
 });
 });
 
 | 
实现多聊天室
调整client代码,在enter的时候带上聊天室的id,广播的时候只对该聊天室进行广播
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 
 | enter(){
 if(this.name.trim()==='')  {
 alert('用户名不得为空')
 return
 }
 this.isShow=false
 this.ws.send(JSON.stringify({
 event:'enter',
 message:this.name,
 roomid:this.roomid
 }))
 }
 
 
 wss.clients.forEach((client) => {
 if (client.readyState === WebSocket.OPEN&& client.roomid ===ws.roomid) {
 msgObj.name = ws.name
 client.send(JSON.stringify(msgObj));
 }
 });
 
 | 
websocket鉴权
- 协议本身在握手阶段不提的
- 浏览器侧:url传参、message主动消息,session/cookie
- Nodejs侧:直接使用ws传Header
心跳检测&断线重连
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 
 | const timeInterval = 1000
 setInterval(()=>{
 wss.clients.forEach((ws)=>{
 if(!ws.isAlive){
 group[ws.roomid]--
 return ws.terminate()
 }
 
 
 ws.isAlive = false
 ws.send(JSON.stringify({
 event:'heartbeat',
 message:'ping'
 }))
 })
 },timeInterval)
 
 
 onMessage(event) {
 if (this.isShow) return;
 console.log("message" + this.ws.readyState);
 var obj = JSON.parse(event.data);
 switch (obj.event) {
 case "noAuth":
 
 
 break;
 case "enter":
 this.lists.push("欢迎" + obj.message + "加入聊天室");
 break;
 case "out":
 this.lists.push(obj.name + "离开了聊天室");
 break;
 case 'heartbeat':
 this.ws.send(JSON.stringify({
 event:'heartbeat',
 message:'pong'
 }))
 break;
 default:
 if (obj.name !== this.name) {
 this.lists.push(obj.name + ":" + obj.message);
 }
 }
 this.num = obj.num;
 }
 
 | 
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | 
 checkServer(){
 clearTimeout(this.handle)
 this.handle = setTimeout(() => {
 this.onClose()
 this.initWS()
 }, 1000+500);
 }
 
 
 onError() {
 console.log("error" + ws.readyState);
 
 setTimeout(() => {
 this.initWS()
 }, 1000);
 },
 
 | 
相关代码请移步github —–> websocketBase