• Node聊天室和socket.io原理与功能总结


    导语:前几天做了一个简易的聊天室,实现了聊天功能,聊天内容可以发送文本,图片,音频,视频,表情包等内容,支持一对一聊天,群组聊天。现在就之前的聊天室功能做一个简单的梳理和总结。

    目录

    • 原理简述
    • 功能开发
    • 效果体验

    原理简述

    这次使用了socket.io这个工具包进行通信。

    webscoket

    html5中有websocket的功能,参考这篇文章《html知识总结之WebSocket》了解更多基础知识。

    WebSocket是一种在单个 TCP 连接上进行全双工通信的协议, 能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。

    课外科普:

    通信分类分为并行通信,串行通信,同步/异步,单工/双工,半双工/全双工。

    • 并行通信指的是数据的各位同时在多根数据线上发送或接收。控制简单,传输速度快;由于传输线较多,适用于短距离通信。
    • 串行通信是指数据的各位在同一根数据线上逐位发送和接收。控制复杂,传输速度慢;只需要一根数据线,适用于远距离通信。
      • 根据对数据流的分界、定时以及同步方案方法不同,可分为和同步串行通信方式和异步通信方式。
      • 根据串行数据的传输方向,我们可以将通信分为单工,半双工,双工。
        • 单工:是指数据传输仅能沿一个方向,不能实现反向传输。
        • 半双工:是指数据传输可以沿两个方向,但需要分时进行传输。
        • 全双工:是指数据可以同时进行双向传输。

    socket.io

    socket.io是基于websocket协议的一套成熟的解决方案。优点是性能好,支持多平台;缺点是传输的数据并不完全遵循websocket协议, 这就要求客户端和服务端都必须使用socket.io的解决方案。

    区别

    • http和webscoket都是基于tcp;
    • http建立的是短连接;
    • websocket建立的是长连接;

    功能开发

    现在就这个功能进行分析并且开发前端和后端内容,先来打通后端部分,为前端连接socket服务作准备。

    下面这个展示的是最基础的聊天室,包括以下几个功能:

    • 多人聊天
    • 显示用户名和人数
    • 回车发送
    • 到顶部

    后端方面

    这里就是如何在node中建立socket服务器。

    安装依赖包

    npm install -g express-generator
    express --view=ejs chat
    cd chat
    npm install
    npm install socket.io
    

    配置socket.io

    打开bin/www文件,在var server = http.createServer(app);下面一行写入以下内容。

    下面内容就分开介绍各个内容,不做全部代码粘贴。

    • 引入ws服务
    const ws = require('socket.io');
    const io = ws(server, {
      path: '/chat',
      transports: [
        'polling',
        'websocket'
      ]
    })
    

    常用方法

    • 连接和断开连接
    io.on('connection', socket => {
    
        console.log('a user connected!');
    
        //disconnect
        socket.on('disconnect', () => {
            console.log('a user disconnected!');
        })
    }
    
    • 加入和离开房间
    // join room
    socket.join(roomId);
    
    // leave room
    socket.leave(roomId);
    
    • 接受消息
    socket.on('event name', data => {
        // data
    }
    
    • 发送消息
    socket.emit('event name', {
        // some data
    });
    
    • 向其他人广播
    socket.broadcast.emit('event name', {
        // some data
    });
    
    • 向某个房间发送消息
    io.to(roomId).emit('event name', {
        // some data
    })
    

    简易程序

    let roomInfo = {};
    
    io.on('connection', socket => {
      let roomId = socket.handshake.query.roomId;
    
      // user login
      socket.on('login', data => {
        
        socket.join(roomId);
    
        if (!(roomId in roomInfo)) {
          roomInfo[roomId] = [];
        }
    
        let names = [];
        let users = roomInfo[roomId];
        if (users.length) {
          for (let i = 0; i < users.length; i++) {
            names.push(users[i].name);
          }
          if (!(names.includes(data.user))) {
            users.push({
              id: socket.id,
              name: data.name,
              avatar: data.avatar
            })
          }
        } else {
          roomInfo[roomId].push({
            id: socket.id,
            name: data.name,
            avatar: data.avatar
          });
        }
    
        console.log('roomInfo: ', roomInfo);
    
        io.to(roomId).emit('system', {
          name: data.name,
          users: roomInfo[roomId]
        })
    
      })
    
      // client msg
      socket.on('message', data => {
        io.to(roomId).emit('chat', data);
      })
    
      // leave room
      socket.on('leave', data => {
        let users = roomInfo[roomId];
        if (users && users.length) {
          for (let i = 0; i < users.length; i++) {
            const user = users[i];
            if (data.name == user.name) {
              users.splice(i, 1);
            }
            
          }
        }
    
        socket.leave(roomId);
    
        io.to(roomId).emit('logout', {
          name: data.name,
          users: roomInfo[roomId]
        })
    
        console.log('roomInfo: ', roomInfo);
    
      })
    
      socket.on('disconnect', () => {
        console.log('a user disconnect!');
      })
    
    });
    

    前端方面

    • 引入socket.io.js文件
    <script src="./js/socket.io.js"></script>
    
    • html部分

    登录界面:

    <div class="chat">
      <h2>XQ聊天室</h2>
      <form class="chat-form">
          <p>
              <label for="name">昵称:</label>
              <input type="text" id="name" name="name" placeholder="请输入用户名" required>
          </p>
          <p>
              <label for="avatar">头像:</label>
              <select name="avatar" id="avatar" required>
                  <option value="avatar1">头像1</option>
                  <option value="avatar2">头像2</option>
                  <option value="avatar3">头像3</option>
              </select>
          </p>
          <p>
              <label for="roomId">房间:</label>
              <select name="roomId" id="roomId" required>
                  <option value="1">房间1</option>
                  <option value="2">房间2</option>
                  <option value="3">房间3</option>
              </select>
          </p>
          <p>
              <input type="submit" value="进入房间">
          </p>
      </form>
    </div>
    

    房间界面:

    <div class="room">
        <div class="room-header">
            <h3>XQ聊天室(<span class="count">0</span>)</h3>
            <button class="logout">退出</button>
        </div>
        <div class="room-nav">
            <small>在线人数:</small>
            <span id="room-users">暂无成员</span>
        </div>
        <ul class="room-content">
        </ul>
        <div class="room-footer">
            <input class="room-ipt" type="text" placeholder="随便写点儿吧">
            <input class="room-btn" type="submit" value="发送">
        </div>
    </div>
    
    • css部分
    body {
        margin: 0;
        padding: 0;
        background: #f9f9f9;
    }
    
    h1,h2,h3,h4,h5,h6,p {
        margin: 0;
    }
    
    ul,li {
        margin: 0;
        padding: 0;
        list-style: none;
    }
    
    .chat {
        box-sizing: border-box;
        margin: 50px auto;
        padding: 20px;
         300px;
        height: auto;
        background: #fff;
    }
    
    .chat.active {
        display: none;
    }
    
    .chat h2 {
        margin-bottom: 10px;
        font-size: 18px;
        line-height: 1.5;
        text-align: center;
    }
    
    .chat-form p {
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 5px 0;
        font-size: 15px;
        line-height: 35px;
    }
    
    .chat-form p label {
         50px;
    }
    
    .chat-form p input,
    .chat-form p select {
        flex: 1;
        box-sizing: border-box;
        padding: 0 10px;
        height: 30px;
        border: 1px solid #ccc;
        outline: none;
        background: none;
    }
    
    .chat-form p input:focus,
    .chat-form p select:focus {
        box-shadow: 0 0 5px #ccc;
    }
    
    .room {
        display: none;
         100%;
        height: 100vh;
        overflow: hidden;
    }
    
    .room.active {
        display: flex;
        flex-direction: column;
    }
    
    .room-header {
        position: relative;
        display: flex;
        justify-content: space-between;
        align-items: center;
        box-sizing: border-box;
        padding: 0 15px;
        height: 60px;
        background: #111;
        color: #fff;
    }
    
    .room-header h3 {
        font-size: 18px;
        text-align: left;
    }
    
    .room-header button {
         50px;
        height: 50px;
        background: none;
        color: #fff;
        outline: none;
        border: none;
        text-align: right;
    }
    
    .room-nav {
        box-sizing: border-box;
        padding: 20px 15px;
        line-height: 30px;
        font-size: 14px;
    }
    
    .room-nav small,
    .room-nav span {
        font-size: 14px;
    }
    
    .room-nav span {
        color: rgb(6, 90, 146);
    }
    
    .room-content {
        flex: 1;
        padding: 15px 0;
        background: #fff;
        border-top: 1px solid #ccc;
        border-bottom: 1px solid #ccc;
        background: #eee;
        overflow-x: hidden;
        overflow-y: auto;
    }
    
    .room-content li {
        display: flex;
        justify-content: flex-start;
        align-items: flex-start;
        box-sizing: border-box;
        padding: 15px 10px;
        margin-bottom: 10px;
         100%;
    }
    
    .room-content li .room-user {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
    }
    
    .room-content li img {
        display: inline-block;
         40px;
        height: 40px;
    }
    
    .room-content li span {
        font-size: 14px;
    }
    
    .room-des {
        position: relative;
        margin-top: 5px;
        margin-left: 10px;
        box-sizing: border-box;
        padding: 3px 5px;
        font-size: 14px;
        line-height: 30px;
        background: #ccc;
        border-radius: 5px;
    }
    
    .room-des::before,
    .room-des::after {
        content: '';
        position: absolute;
        top: 10px;
         0;
        height: 0;
        border: 5px solid transparent;
    }
    
    .room-des::before {
        display: inline-block;
        left: -10px;
        border-right: 5px solid #ccc;
    }
    
    .room-des::after {
        display: none;
        right: -10px;
        border-left: 5px solid #fff;
    }
    
    .room-me {
        flex-direction: row-reverse;
    }
    
    .room-me .room-des {
        margin-left: 0;
        margin-right: 10px;
        background: #fff;
    }
    
    .room-me .room-des::before {
        display: none;
    }
    
    .room-me .room-des::after {
        display: inline-block;
    }
    
    .room-content .system {
        justify-content: center;
        align-items: center;
        padding: 0;
        height: 35px;
        line-height: 35px;
    }
    
    .system p {
        box-sizing: border-box;
        padding: 0 5px;
        font-size: 14px;
        text-align: center;
        border-radius: 5px;
        background: #ccc;
    }
    
    .room-footer {
        display: flex;
        justify-content: space-between;
        align-items: center;
        height: 50px;
        background: #fff;
        border-top: 1px solid #ccc;
    }
    
    .room-footer .room-ipt {
        margin-top: 1px;
        box-sizing: border-box;
        padding: 10px;
         80%;
        height: 48px;
        background: none;
        border: 1px solid transparent;
        outline: none;
    }
    
    .room-footer .room-ipt:focus {
        border: 1px solid #ccc;
        box-shadow: 0 0 5px #ccc;
    }
    
    .room-footer .room-btn {
         19%;
        height: 100%;
        background: rgb(2, 54, 112);
        border: 1px solid #ccc;
        outline: none;
        font-size: 15px;
        color: #fff;
    }
    
    • js部分

    登录界面的js

    let chat = document.querySelector('.chat');
    let chatForm = document.querySelector('.chat-form');
    let user = document.querySelector('#name');
    let avatar = document.querySelector('#avatar');
    let roomId = document.querySelector('#roomId');
    
    // io
    let socket = io.connect('/', {
        path: '/chat'
    });
    
    // login
    chatForm.onsubmit = function(){
        let userInfo = {
            name: user.value,
            avatar: `/img/${avatar.value}.webp`,
            roomId: roomId.value
        }
        localStorage.setItem('userInfo', JSON.stringify(userInfo));
        checkLogin();
        return false;
    };
    
    checkLogin();
    
    function checkLogin () {
    
        let userInfo = localStorage.getItem('userInfo');
        userInfo = JSON.parse(userInfo);
    
        if (userInfo && userInfo.name) {
            chat.classList.add('active');
            room.classList.add('active');
            socket.emit('login', userInfo);
    
        } else {
            chat.classList.remove('active');
            room.classList.remove('active');
        }
    
    }
    

    房间部分的js

    let room = document.querySelector('.room');
    let logout = document.querySelector('.logout');
    let count = document.querySelector('.count');
    let roomUsers = document.querySelector('#room-users');
    let roomContent = document.querySelector('.room-content');
    let roomIpt = document.querySelector('.room-ipt');
    let roomBtn = document.querySelector('.room-btn');
    
    // 退出登录
    logout.addEventListener('click', function(){
        let userInfo = localStorage.getItem('userInfo');
        userInfo = JSON.parse(userInfo);
        socket.emit('leave', userInfo);
        alert('退出成功!');
        localStorage.removeItem('userInfo');
        checkLogin();
    })
    
    roomIpt.addEventListener('keyup', sendMsg, false);
    roomBtn.addEventListener('click', sendMsg, false);
    
    // 发送消息 
    function sendMsg (e) {
        if (e.type === 'click' || e.code === 'Enter') {
            let val = roomIpt.value;
            if (val == '') {
                alert('聊天内容不能为空!');
                return false;
            }
            let userInfo = localStorage.getItem('userInfo');
            userInfo = JSON.parse(userInfo);
            userInfo.msg = val;
            roomIpt.value = '';
            socket.emit('message', userInfo);
            goBot();
        }
    }
    
    goBot();
    
    // 到底部
    function goBot () {
        roomContent.scrollTop = roomContent.scrollHeight;
    }
    
    // 系统消息提示
    function welcome (user = 'mark', type = 1) {
        roomContent.innerHTML += `
            <li class="system">
                <p>系统消息:<strong>${user}</strong>${type == 1 ? '来到' : '离开'}本房间!</p>
            </li>
        `;
        goBot();
    }
    
    // 系统消息
    socket.on('system', data => {
        let strs = '';
        welcome(data.name);
        count.innerText = data.users.length;
        for (const item of data.users) {
            strs += item.name + ',';
        }
        roomUsers.innerText = '';
        roomUsers.innerText += strs;
    })
    
    
    // 退出提醒
    socket.on('logout', data => {
        let strs = '';
        welcome(data.name, 2);
        count.innerText = data.users.length;
        for (const item of data.users) {
            strs += item.name + ',';
        }
        roomUsers.innerText = '';
        roomUsers.innerText += strs;
    })
    
    // 接受消息
    socket.on('chat', data => {
        let userInfo = localStorage.getItem('userInfo');
        userInfo = JSON.parse(userInfo);
        let isUser = data.name == userInfo.name;
        roomContent.innerHTML += `
            <li ${isUser ? 'class="room-me"' : ''}>
                <div class="room-user">
                    <img class="room-avatar" src="/chatroom/${(isUser ? userInfo.avatar : data.avatar ) || '/img/avatar1.webp'}" alt="">
                    <span class="room-name">${isUser ? userInfo.name : data.name }</span>
                </div>
                <p class="room-des">${data.msg}</p>
            </li>
        `;
        goBot();
    })
    

    效果体验

    终于做好了,接下来来体验一下网上冲浪--XQ聊天室的美好生活吧!

    • 进入房间

    首先,输入你自己的昵称,选择好头像和房间,点击进入房间按钮对话。
    在这里插入图片描述

    • 发送消息

    然后,输入消息内容,点击发送按钮,或者按Enter回车也可以。
    在这里插入图片描述

    可以打开一个隐私无痕窗口或者新的游览器,打开网址,输入另一个测试账号进行

    这是jerry登录后的界面
    在这里插入图片描述

    这是mark看到的jerry发来的消息
    在这里插入图片描述

    • 退出登录

    如果聊天结束,可以点击右上方退出聊天室。
    在这里插入图片描述

    这是jerry退出登录后, mark看到的界面
    在这里插入图片描述

    再来看一下后端打印出的用户信息。

    • 这是mark登录以后记录的信息
      在这里插入图片描述

    • 这是jerry登录以后记录的信息
      在这里插入图片描述

    • 这是jerry退出以后记录的信息
      在这里插入图片描述

    以上就是一个简易的聊天室和node中websocket的知识总结。

  • 相关阅读:
    day03 bs4解析库
    day02—selenium库
    day01爬虫三部曲
    IIC SPI UART通信方式的区别
    五大类程序设计模式
    套接字编程基础
    主机字节序和网络字节序转换
    位运算
    ARM体系结构的特点
    static关键字的作用
  • 原文地址:https://www.cnblogs.com/guanqiweb/p/14992307.html
Copyright © 2020-2023  润新知