Socket编程

web Socket介绍

  • http请求:
    客户端发送请求,服务端才会响应请求。
    在这里插入图片描述

  • web Socket请求
    在这里插入图片描述
    WebSocket并不是全新的协议,而是利用了HTTP协议来建立连接。

webSocket的应用场景:
● 弹幕
● 媒体聊天
● 协同编辑
● 基于位置的应用
● 体育实况更新
● 股票基金报价实时更新

创建WebSocket连接

WebSocket连接必须由浏览器发起,因为请求协议是一个标准的HTTP请求,格式如下:

GET ws://1oca1host:3000/ws/chat HTTP/1.1
Host: localhost
upgrade: websocket
Connection: Upgrade
origin: http://localhost:3000
Sec-WebSocket-Key:client-random-string
Sec-Websocket-Version: 13

该请求和普通的HTTP请求有几点不同:

  1. GET请求的地址不是类似/path/,而是以ws://开头的地址;
  2. 请求头upgrade: websocketConnection: upgrade 表示这个连接将要被转换为WebSocket连接;
  3. Sec-websocket-Key 是用于标识这个连接,并非用于加密数据;
  4. Sec-websocket-version指定了WebSocket的协议版本。

随后,服务器如果接受该请求,就会返回如下响应:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-Websocket-Accept:server-random-string

该响应代码101表示本次连接的HTTP协议即将被更改,更改后的协议就是Upgrade: websocket 指定的WebSocket协议。

这样,一个WebSocket连接就建立成功,浏览器和服务器就可以随时主动发送消息给对方。消息有两种,一种是文本, 一种是二进制数据。通常,我们可以发送JSON格式的文本,这样,在浏览器处理起来就十分容易。

Node实现WebSocket模块

ws模块

下载ws模块:npm i ws

简单实现聊天功能

前端创建webSocket协议
<!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>
</head>
<body>
    Chat
    <h1>聊天室</h1>
    <script>
        // WebSocket()是基于浏览器的内置函数
        // 建立连接
        var ws = new WebSocket("ws://localhost:8080")

        ws.onopen=()=>{
            console.log("连接成功")
        }
        ws.onmessage=(msgObj)=>{
            console.log(msgObj)
        }
        ws.onerror=()=>{
            console.log("error")
        }
    </script>
</body>
</html>
node创建webSocket服务器
const express = require("express")
const app = express()


// http响应
app.use(express.static("./public"))
app.get("/",(req, res)=> {
    res.send({ok:1})
})

app.listen(3000)

// webSocket响应
const WebSocket = require("ws")
const WebSocketServer = WebSocket.WebSocketServer
const wss = new WebSocketServer({ port: 8080 });

// ws表示当前连接的客户端,wss表示当前创建的服务器
wss.on('connection', function connection(ws) {

    // 将数据从服务端发送到客户端
    ws.send('欢迎来到聊天室');
    // ws.send不会直接显示在页面上,在data数据中

    // 接收从客户端传过来的消息
    ws.on('message', function message(data) {
         console.log('received: %s', data);
         
         // 转发给其他客户端,遍历客户端给每个人发消息,并且排除自己
        wss.clients.forEach(function each(client) {
            // 判断当前客户端是否处于连接状态
            if (client!==ws&&client.readyState === WebSocket.OPEN) {
                client.send(data,{binary:false});
            }
        });

     });
});

实现效果:
(一个网页相当于一个用户)

QQ录屏20220711165339

实现登录鉴权和身份识别

chat.ejs

<!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>
  </head>
  <body>
    Chat
    <h1>聊天室</h1>
    <input type="text" id="text" /><button id="send">send</button>
    <hr>
    <div id="chats"></div>
    <hr>
    私聊
    <div id="s_chats"></div>
    <hr>
    <h3>用户列表</h3>
    <select id="select"></select>

    <h3></h3>
    <script>
      const WebSocketType = {
        Error: 0,
        GroupList: 1,
        GroupChat: 2,
        SingleChat: 3,
      };
      // 建立socket链接,带着token
      var chats = document.getElementById("chats");
      var s_chats = document.getElementById("s_chats");
      var select = document.getElementById("select");
      var text = document.getElementById("text");
      var send = document.getElementById("send");

      // WebSocket()是基于浏览器的内置函数
      // 建立连接
      var ws = new WebSocket(
        `ws://localhost:8080?token=${localStorage.getItem("token")}`
      );

      // 接收从服务端传过来的数据
      ws.onopen = () => {
        console.log("连接成功");
      };
      ws.onmessage = (msgObj) => {
        // console.log(msgObj.data);
        msgObj = JSON.parse(msgObj.data);

        switch (msgObj.type) {
          case WebSocketType.Error:
            localStorage.removeItem("token");
            location.href = "/login";
            break;
          case WebSocketType.GroupList:
            const onlineList = JSON.parse(msgObj.data);
            select.innerHTML = "";
            select.innerHTML =
              `<option value="all">all</option>` +
              onlineList.map(
                  (item) => `
                    <option value="${item.username}">
                        ${item.username}
                    </option>`
                )
                .join("");

            //  console.log(JSON.parse(msgObj.data))
            break;

          case WebSocketType.GroupChat:
            console.log(msgObj);
            console.log(msgObj.user?.username);
            var h_html = document.createElement("h3")
            h_html.innerHTML = `${msgObj.user?.username??""}${msgObj.data}`;
            chats.appendChild(h_html)
            break;
        case WebSocketType.SingleChat:
            console.log(msgObj);
            console.log(msgObj.user?.username);
            var h_html = document.createElement("h3")
            h_html.innerHTML = `${msgObj.user?.username??""}${msgObj.data}`;
            s_chats.appendChild(h_html)
            break;
        }
      };
      ws.onerror = () => {
        console.log("error");
      };

      send.onclick = () => {
        if (select.value === "all") {
          // console.log("群发")
          ws.send(createMessage(WebSocketType.GroupChat, text.value));
        } else {
          // console.log("私聊")
          ws.send(createMessage(WebSocketType.SingleChat, text.value,select.value));
        }
      };

      function createMessage(type, data,to) {
        return JSON.stringify({
          type,
          data,
          to
        });
      }
    </script>
  </body>
</html>

webSocket.js


const JWT = require("../utils/jwt")
// webSocket响应
const WebSocket = require("ws")
const WebSocketServer = WebSocket.WebSocketServer
const wss = new WebSocketServer({ port: 8080 });

// ws表示当前连接的客户端,wss表示当前创建的服务器
wss.on('connection', function connection(ws, req) {
    // 获取请求,检查token
    const myURL = new URL(req.url, "http://127.0.0.1:3000")
    // console.log(myURL.searchParams.get("token"))
    // 校验token
    const payload = JWT.verify(myURL.searchParams.get("token"))
    if (payload) {
        // 将数据从服务端发送到客户端
        ws.send(createMessage(WebSocketType.GroupChat, null, '欢迎来到聊天室'));
        ws.user = payload//将用户信息添加到ws中

        // 只要有用户登录,就会告知所有用户
        sendAll()
        
    } else {
         ws.send(createMessage(WebSocketType.Error,null,'未授权'));
    }


    // 接收从客户端传过来的消息
    ws.on('message', function message(data) {

        const msgObj = JSON.parse(data)
        switch (msgObj.type) {
            case WebSocketType.GroupList:
                // 在线用户
                // console.log(Array.from(wss.clients).map(item=>item.user))
                ws.send(createMessage(WebSocketType.GroupList,null,JSON.stringify(Array.from(wss.clients).map(item=>item.user))))
                break;
            case WebSocketType.GroupChat:
                // console.log(msgObj.data)
                // 群发消息,显示所有用户
                wss.clients.forEach(function each(client) {
                    if (client.readyState == WebSocket.OPEN) {
                        client.send(createMessage(WebSocketType.GroupChat, ws.user, msgObj.data), {binary: false})
                    }      
                 })
                break;
            case WebSocketType.Error:
                break;
            case WebSocketType.SingleChat:
                wss.clients.forEach(function each(client) {
                    if (client.user.username===msgObj.to && client.readyState == WebSocket.OPEN) {
                        client.send(createMessage(WebSocketType.SingleChat, ws.user, msgObj.data), {binary: false})
                    }      
                 })
                break;
        }

    });
    
    // 等有人离开时,也重新更新用户列表
    ws.on("close", () => {
        wss.clients.delete(ws.user)
        sendAll()
        // console.log(ws.user)
    })
    

});

const WebSocketType = {
    Error: 0,
    GroupList: 1,
    GroupChat: 2,
    SingleChat:3
}
function createMessage(type, user, data) {
    return JSON.stringify({
        type,
        user,
        data
    })
   
}

function sendAll() {
    // 群发消息,显示所有用户
    wss.clients.forEach(function each(client) {
        if (client.readyState == WebSocket.OPEN) {
           client.send(createMessage(WebSocketType.GroupList,null,JSON.stringify(Array.from(wss.clients).map(item=>item.user))))
       }
      
   })
}

实现效果:

QQ录屏20220711225558

整个代码流程:

  • 前端登录:发出scoket长连接 ⇒ 服务端wss.on接收链接 ⇒ 用户鉴权 ⇒ 成功就返回登陆成功的信息
  • 前端群聊: 前端发出群聊信息 ⇒ 服务端接收群聊信息 ⇒ 转发给每个用户
  • 前端私聊:前端发出私聊信息 ⇒ 服务端接受私聊信息 ⇒ 转发给指定用户

代码获取:
链接:https://pan.baidu.com/s/1NQxKeiSyWwvOC8WNUOUaog?pwd=xtfx
提取码:xtfx

小结

群发流程:
在这里插入图片描述
私聊流程:
在这里插入图片描述

使用ws编写的代码,前端通过 ws.onmessage=()=>{},后端通过 ws.on(“message”,()=>{})类似的方法进行前后端信息的传递。
方法固定,比较死板

socket.io模块

比ws模块更加灵活,兼容性也更好。

  • 安装:npm i socket.io
    同时用户端有对应的js版本的socket.io
  • 前后端通过相同的事件名进行通信
  • 前后端都是通过emit(触发)和on(监听)
  • 不需要写switch判断聊天类型了,在emit和on的第一个参数直接指明类型。

eg:
实现聊天室功能:

  • websocket.js
const { Socket } = require("socket.io");
const JWT = require("../utils/jwt")

// 服务端的io模块
function start(server) {
    const io = require("socket.io")(server);
    io.on("connection", (socket) => {
        // token
        // console.log("11111",socket.handshake.query.token)
        // 判断token是否过期
        const payload = JWT.verify(socket.handshake.query.token)
        if (payload) {
            socket.user = payload
            // 发送欢迎
            socket.emit(WebSocketType.GroupChat,createMessage(null,"欢迎来到聊天室"))

            // 发送所有用户列表
            sendAll(io)
        } else {
            socket.emit(WebSocketType.Error,createMessage(null,"token过期"))
        }

        socket.on(WebSocketType.GroupList, () => {
            // // io.sockets.sockets代表所有客户端
            // io.sockets.sockets是map结构,可以转化为数组结构(键的下标是0,值的下标是1)
            console.log(Array.from(io.sockets.sockets).map(item =>item[1].user))

        })

        socket.on(WebSocketType.GroupChat, (msg) => {
            
            //  群发消息用io.sockets
            io.sockets.emit(WebSocketType.GroupChat, createMessage(socket.user, msg.data))
            // 除了自己不发,给其他人发
            // socket.broadcast.emit(WebSocketType.GroupChat, createMessage(socket.user, msg.data))
              
        })

        socket.on(WebSocketType.SingleChat, (msg) => {
            // io.sockets.sockets代表所有客户端
            Array.from(io.sockets.sockets).forEach(item => {
                if (item[1].user.username === msg.to) {
                    item[1].emit(WebSocketType.SingleChat,createMessage(socket.user,msg.data))
                }
            })    
        })

        // 离线后重新发送用户列表(固定写法)
        socket.on("disconnect", () => {
            sendAll(io)
        })
    })
}


const WebSocketType = {
    Error: 0,
    GroupList: 1,
    GroupChat: 2,
    SingleChat:3
}
function createMessage(user, data) {
    return {
        user,
        data
 }
   
}

function sendAll(io) {
    // 群发消息,显示所有用户
    io.sockets.emit(WebSocketType.GroupList,createMessage(null,Array.from(io.sockets.sockets).map(item =>item[1].user).filter(item=>item)))
}

module.exports = start
  • chat.ejs
<!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>
    <!-- // 引入socket.io(js版本) -->
    <script type='text/javascript' src='javascripts/socketio.js'></script>
    
  </head>
  <body>
    Chat
    <h1>聊天室</h1>
    <h1>当前用户:
        <b id="user"></b>
    </h1>
    <input type="text" id="text" /><button id="send">send</button>
    <select id="select"></select>
    <hr>
    <div id="chats"></div>
    <hr>
    私聊
    <div id="s_chats"></div>
    <hr>

    <h3></h3>
    <script>
      const WebSocketType = {
        Error: 0,
        GroupList: 1,
        GroupChat: 2,
        SingleChat: 3,
      };

      // 建立socket链接,带着token
      var chats = document.getElementById("chats");
      var s_chats = document.getElementById("s_chats");
      var select = document.getElementById("select");
      var text = document.getElementById("text");
      var send = document.getElementById("send");

      var user = document.getElementById("user")
      user.innerHTML = localStorage.getItem("username")


      // 引入socket.io(js端)
      // 不传参时,默认传递3000端口,socketio端口默认和http的端口一致都是3000
      // 这里进行修改
      const socket = io(`ws://localhost:3000?token=${localStorage.getItem("token")}`)

      
    //   前后端通过相同的事件名进行通信
     socket.on(WebSocketType.GroupChat,(msg)=>{
      var h_html = document.createElement("h3")
      h_html.innerHTML = `${msg.user?.username??"广播"}${msg.data}`;
      chats.appendChild(h_html)
     })

     socket.on(WebSocketType.SingleChat,(msg)=>{
      console.log(msg)
      var h_html = document.createElement("h3")
      h_html.innerHTML = `${msg.user?.username??"广播"}${msg.data}`;
      s_chats.appendChild(h_html)
     })

     socket.on(WebSocketType.Error,(msg)=>{
      localStorage.removeItem("token")
      location.href="/login"
     })

     socket.on(WebSocketType.GroupList,(msg)=>{
      console.log(msg)
      const onlineList = msg.data;
      select.innerHTML = "";
      select.innerHTML =
        `<option value="all">all</option>` +
        onlineList.map(
            (item) => `
              <option value="${item.username}">
                  ${item.username}
              </option>`
          )
          .join("");
     })
     

      send.onclick = () => {
        if (select.value === "all") {
          // console.log("群发")
          socket.emit(WebSocketType.GroupChat,createMessage(text.value))
         
        } else {
          // console.log("私聊")
          socket.emit(WebSocketType.SingleChat,createMessage(text.value,select.value))
        }
      };

      function createMessage(data,to) {
        // 使用socket可以直接传对象,无需转换成字符串
        return {
          data,
          to
        };
      }
    </script>
  </body>
</html>

完整代码获取:
https://github.com/wangliyang-max/qiandaun/tree/master/Node/socket_%E6%9D%83%E9%99%90/servermvc%20copy%204

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值