【Node.js】WebSockets

概述

WebSockets是一种在浏览器和服务器之间建立持久连接的协议,它允许服务器主动推送数据给客户端,并且在客户端和服务器之间实现双向通信。

  1. 建立连接:客户端通过在JavaScript代码中使用WebSocket对象来建立WebSockets连接。例如:var socket = new WebSocket('ws://example.com/socket');

  2. 协议:WebSockets使用标准的WebSocket协议来进行通信。相对于HTTP协议,WebSocket协议提供了更高效率和更低的延迟。ws和wss协议正对应了http和https。

  3. 服务器支持:为了使用WebSockets,服务器必须支持WebSocket协议。

  4. 事件:WebSocket对象提供了一些事件,用于处理连接的不同阶段和接收到的数据。常见的事件有onopen(连接建立)、onmessage(接收到消息)、onclose(连接关闭)等。

  5. 数据传输:WebSockets可以传输文本和二进制数据。文本数据是以UTF-8编码发送和接收的,而二进制数据使用ArrayBuffer对象。

  6. 安全性:与HTTP协议通过TLS/SSL协议进行加密一样,WebSockets也可以通过wss协议来提供安全的通信。wss://表示通过TLS/SSL加密的WebSockets连接。

  7. 跨域策略:WebSockets默认不受同源策略的限制,可以跨域使用,但仍然会受到服务器的安全策略限制。

可以在浏览器和服务器之间建立稳定的连接,实际应用如聊天应用、实时数据监控等。

基本使用

使用WebSockets进行连接和通信:

首先,在客户端使用WebSocket对象来建立连接:

var socket = new WebSocket('ws://example.com/socket');

// 监听连接建立事件
socket.onopen = function() {
  console.log('WebSocket连接已建立');
  
  // 向服务器发送消息
  socket.send('Hello Server!');
};

// 监听接收到消息事件
socket.onmessage = function(event) {
  var message = event.data;
  console.log('接收到服务器消息:' + message);
};

// 监听连接关闭事件
socket.onclose = function() {
  console.log('WebSocket连接已关闭');
};

在上面的代码中,我们创建了一个WebSocket对象并指定连接的URL。然后我们监听了三个事件:onopen表示连接建立时触发的事件,onmessage表示接收到消息时触发的事件,onclose表示连接关闭时触发的事件。

在连接建立后,我们向服务器发送了一条消息Hello Server!。当服务器向客户端发送消息时,onmessage事件将被触发,并且我们可以在回调函数中处理接收到的消息。

接下来,让我们看一下服务器端的示例代码(以Node.js和Express框架为例):

const express = require('express');
const WebSocket = require('ws');

const app = express();

// 创建WebSocket服务器
const server = require('http').createServer(app);
const wss = new WebSocket.Server({ server });

wss.on('connection', function(socket) {
  console.log('WebSocket连接已建立');

  // 监听客户端发送的消息
  socket.on('message', function(message) {
    console.log('接收到客户端消息:' + message);

    // 向客户端发送消息
    socket.send('Hello Client!');
  });

  // 监听连接关闭事件
  socket.on('close', function() {
    console.log('WebSocket连接已关闭');
  });
});

server.listen(3000, function() {
  console.log('服务器已启动,监听端口3000');
});

在服务器端的代码中,我们使用了ws库创建了一个WebSocket服务器,并监听了connection事件。当有客户端连接到服务器时,connection事件将被触发,并在该回调函数中处理连接过程。

在服务器接收到客户端的消息时,我们输出消息到控制台,并向客户端发送一条回复消息。当连接关闭时,close事件将被触发。

最后,我们在Express应用中创建了一个HTTP服务器,将WebSocket服务器绑定到服务器上,然后启动服务器监听端口3000。

通信小实例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<script>
    const ws = new WebSocket('ws://localhost:3000')
    ws.addEventListener('open',()=> {
        console.log('已连接服务器')
        ws.send('我是前端的数据')
    })
    ws.addEventListener('message', ({data})=> {
        console.log(data)
    })
</script>
</body>
</html>
const WebSocket = require('ws')
const socket = new WebSocket.Server({port: 3000});

socket.on('connection',ws=> {
    console.log('我进来了')
    ws.on('message',(data)=> {
        console.log('前端数据:' + data + "。后端返回数据:"+ '我是后端的数据')
    })
    ws.on('close',()=> {
        console.log('溜了溜了')
    })
})

image.png

Socket.io

Socket.io 具有跨平台,实时性,WebSocket 不可连接时自动降级到其他传播机制(如 http 长轮询)等优点。

官方文档:(服务器初始化 | Socket.IO)

npm install socket.io 安装。

聊天室案例

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            padding: 0;
            margin: 0;
        }

        html,
        body,
        .room {
            height: 100%;
            width: 100%;
        }

        .room {
            display: flex;
        }

        .left {
            width: 300px;
            border-right: 0.5px solid #2b2d30;
            background: #333;
        }

        .right {
            background: #1c1c1c;
            flex: 1;
            display: flex;
            flex-direction: column;
        }

        .header {
            background: rgb(60, 63, 65);
            color: white;
            padding: 10px;
            box-sizing: border-box;
            font-size: 20px;
        }

        .main {
            flex: 1;
            padding: 10px;
            box-sizing: border-box;
            font-size: 20px;
            overflow: auto;
        }

        .main-chat {
            color: #bcbec4;
        }

        .footer {
            min-height: 200px;
            border-top: 1px solid #4e5157;
        }

        .footer .ipt {
            width: 100%;
            height: 100%;
            color: #bcbec4;
            outline: none;
            font-size: 20px;
            padding: 10px;
            box-sizing: border-box;
        }

        .groupList {
            height: 100%;
            overflow: auto;
        }

        .groupList-items {
            height: 50px;
            width: 100%;
            background: rgb(67, 69, 74);
            border-bottom: 1px solid #2b2d30;
            display: flex;
            align-items: center;
            justify-content: center;
            color: white;
        }
    </style>
</head>
<div class="room">
    <div class="left">
        <div class="groupList">

        </div>
    </div>
    <div class="right">
        <header class="header">聊天室</header>
        <main class="main">

        </main>
        <footer class="footer">
            <div class="ipt" contenteditable></div>
        </footer>
    </div>
</div>

<body>
<script type="module">
    import {io} from "https://cdn.socket.io/4.7.4/socket.io.esm.min.js";

    let name = prompt('请输入你的名字')
    let room = prompt('请输入你要进入的房间')
    const sendMessage = (message) => {
        const div = document.createElement('div');
        div.className = 'main-chat';
        div.innerText = `${message.user}:${message.text}`;
        main.appendChild(div)
    }
    const group = document.querySelector('.groupList');  // 左侧列表的父级
    const main = document.querySelector('.main');  // 消息内容的父级
    const ipt = document.querySelector('.footer .ipt')
    // 希望自己也能收到信息
    const addChart = (msg)=> {
        let item = document.createElement('div')
        item.className = 'main-chat'
        item.innerHTML = `${msg.name}: ${msg.message}`
        main.appendChild(item)
    }
    const socket = io('ws://localhost:3000')  // ws 的地址
    socket.on('connect', () => {
        // 监听回车事件
        document.addEventListener('keydown',e=> {
            if (e.key === 'Enter') {
                const message = ipt.innerText
                socket.emit('message', {name, message, room})
                addChart({name, message})
                ipt.innerText = ''
            }
        })
        // 加入房间
        socket.emit('join', {name, room})
        //监听左侧列表
        socket.on('groupMap', (data) => {
            group.innerHTML = ``
            Object.keys(data).forEach(key=> {
                const item = document.createElement('div')
                item.className = 'groupList-items'
                item.innerHTML = `房间号:${key} 房间人数:${data[key].length}`
                group.appendChild(item)
            })
        })
        //监听发送的消息
        socket.on('message', (data) => {
            addChart(data)
        })
    })
</script>
</body>

</html>
import {createServer} from "http";
import {Server} from "socket.io";

const server = createServer();
const io = new Server(server, {
    cors: true // 允许跨域
});
/**
 * [{name, room, id}, {name, room, id}]
 * @type {{}}
 */
const groupMap = {}

io.on("connection", (socket) => {
    // 前端传过来 name 和 room
    socket.on('join', ({name, room}) => {
        socket.join(room) // 创建一个房间
        if (groupMap[room]) {
            groupMap[room].push({name, room, id: socket.id}) // 已有就 push
        } else {
            groupMap[room] = [{name, room, id: socket.id}]  // 没有就创建
        }
        socket.emit('groupMap', groupMap)
        // 所有人都可以看到
        socket.broadcast.emit('groupMap', groupMap)
        // 管理员发送信息
        socket.broadcast.to(room).emit('message', {
            name: '管理员',
            message: `欢迎${name}进入聊天室`
        })
    })
    // 向前端发消息
    socket.on('message', ({name, message, room}) => {
        // 虽然给别人发送了消息,但是自己收不到
        socket.broadcast.to(room).emit('message', {name, message})
    })
});

server.listen(3000);

面试题

  1. webSocket协议是什么,能简述一下吗?

    websocket协议是 HTML5 带来的新协议,相对于http,它是一个持久连接的协议,它利用http协议完成握手,然后通过TCP连接通道发送消息,使用websocket 协议可以实现服务器主动推送消息。

    首先,客户端若要发起websocket连接,首先必须向服务器发送http 请求以完成握手,请求行中的path需要使用ws:开头的地址,请求头中要分别加入
    upgrade.connection、Sec-WebSocket-Key、Sec-WebSocket-Version 标记。

    然后,服务器收到请求后,发现这是一个websocket协议的握手请求,于是响应行中包含Switching Protocols,同时响应头中包含upgrade、connection、.Sec-WebSocket-Accept 标记。

    当客户端收到响应后即可完成握手,随后使用建立的TCP连接直接发送和接收消息。

  2. weblSocket与传统的http有什么优势?

    当页面中需要观察实时数据的变化(比如聊天、k 线图)时,过去我们往往使用两种方式完成:

    第一种是短轮询,即客户端每隔一段时间就向服务器发送消息,询问有没有新的数据。

    第二种是长轮询,发起一次请求询问服务器,服务器可以将该请求挂起,等到有新消息时再进行响应。响应后,客户端立即又发起一次请求,重复整个流程。

    无论是哪一种方式,都暴露了http协议的弱点,即响应必须在请求之后发生,服务器是被动的,无法主动推送消息。而让客户端不断的发起请求又白白的占用了资源。

    websocket的出现就是为了解决这个问题,它利用http协议完成握手之后,就可
    以与服务器建立持久的连接,服务器可以在任何需要的时候,主动推送消息给客户端,这样占用的资源最少,同时实时性也最高。

  3. 前端如何实现即时通讯?

    1. 短轮询。即客户端每隔一段时间就向服务器发送消息,询问有没有新的数据
    2. 长轮询,发起一次请求询问服务器,服务器可以将该请求挂起,等到有新消息时再进行响应。响应后,客户端立即又发起一次请求,重复整个流程。
    3. websocket,握手完毕后会建立持久性的连接通道,随后服务器可以在任何时候推送新消息给客户端
  • 17
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小秀_heo

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值