• SocketIo的使用和基于SocketIO的聊天室


      Socket.IO 是一个库,可以在客户端和服务器之间实现 低延迟, 双向 和 基于事件的 通信。

    一、Socket.IO的特点

    以下是 Socket.IO 在普通 WebSockets 上提供的功能:

    1、HTTP 长轮询回退

    如果无法建立 WebSocket 连接,连接将回退到 HTTP 长轮询。
    这个特性是人们在十多年前创建项目时使用 Socket.IO 的原因(!),因为浏览器对 WebSockets 的支持仍处于起步阶段。
    即使现在大多数浏览器都支持 WebSockets(超过97%),它仍然是一个很棒的功能,因为我们仍然会收到来自用户的报告,这些用户无法建立 WebSocket 连接,因为他们使用了一些错误配置的代理。

    2、自动重新连接

    在某些特定情况下,服务器和客户端之间的 WebSocket 连接可能会中断,而双方都不知道链接的断开状态。
    这就是为什么 Socket.IO 包含一个心跳机制,它会定期检查连接的状态。
    当客户端最终断开连接时,它会以指数回退延迟自动重新连接,以免使服务器不堪重负。

    3、数据包缓冲

    当客户端断开连接时,数据包会自动缓冲,并在重新连接时发送。

    4、收到后的回调

    Socket.IO 提供了一种方便的方式来发送事件和接收响应

    5、广播

    在服务器端,您可以向所有连接的客户端或客户端的子集发送事件

    6、多路复用

    命名空间允许您在单个共享连接上拆分应用程序的逻辑。例如,如果您想创建一个只有授权用户才能加入的“管理员”频道

    更多信息请访问socketIO的介绍文档

    二、Socket.IO发送消息常见的方式

      socket.io用on函数给调用的时间注册调用函数,用emit函数来发送时间,以此来时间客户端和服务器两端的通信,常用的使用方式如下:

    1、只发送事件

    发送端代码:

    socket.emit('action');
    
    • 1

    表示发送了一个action命令,命令是字符串的,在另一端接收时,可以这么写:

    socket.on('action',function(){
    	...
    });
    
    • 1
    • 2
    • 3

    2、发送事件和一个数据

    发送端:

    socket.emit('action',data);
    
    • 1

    表示发送了一个action命令,还有data数据,在另一端接收时,可以这么写:

    socket.on('action',function(data){
    	...
    });
    
    • 1
    • 2
    • 3

    3、发送事件和多个数据

    发送端:

    socket.emit('action',arg1,arg2);
    
    • 1

    表示发送了一个action命令,还有两个数据,在另一端接收时,可以这么写:

    socket.on('action',function(arg1,arg2){
    	...
    });
    
    • 1
    • 2
    • 3

    如果是多个参数,就在后面加参数就行了

    4、发送事件和数据及回调函数

    在emit方法中包含回调函数,例如:

    socket.emit('action',data, function(arg1,arg2){
    	...
    } );
    
    • 1
    • 2
    • 3

    那么这里面有一个回调函数可以在另一端调用,另一端可以这么写:

    socket.on('action',function(data,fn){ 
        ...
        fn('a','b');
        ...
     });
    
    • 1
    • 2
    • 3
    • 4
    • 5

    三、emit发送消息的范围

    下面是emit不同方法发送消息的范围:

      // 只发给sender。 sending to the client
      socket.emit('hello', 'can you hear me?', 1, 2, 'abc');
    
      // 发给所有人,除了sender。 sending to all clients except sender
      socket.broadcast.emit('broadcast', 'hello friends!');
    
      // 发给game房间所有人,除了sender。 sending to all clients in 'game' room except sender
      socket.to('game').emit('nice game', "let's play a game");
    
      // 发给game1和/或game2所有人,除了sender。 sending to all clients in 'game1' and/or in 'game2' room, except sender
      socket.to('game1').to('game2').emit('nice game', "let's play a game (too)");
    
      // 发给game房间所有人,包含sender。 sending to all clients in 'game' room, including sender
      io.in('game').emit('big-announcement', 'the game will start soon');
    
      // 发给域名myNamespacs所有人,包含sender。 sending to all clients in namespace 'myNamespace', including sender
      io.of('myNamespace').emit('bigger-announcement', 'the tournament will start soon');
    
      // 发给域名myNamespace里room房间的所有人,包含sender。 sending to a specific room in a specific namespace, including sender
      io.of('myNamespace').to('room').emit('event', 'message');
    
      // 发给某一个人 sending to individual socketid (private message)
      io.to(`${socketId}`).emit('hey', 'I just met you');
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    四、聊天室实现

    1、项目结构

    在这里插入图片描述

    2、server.js

    server.js实现了一个web服务器,主要的功能有两个

    • 实现一个nodejs的服务器,让浏览器能够加载到聊天网页
    • 用socket.io实现服务端接受客户连接和发送消息的功能
      其代码如下:
    'use strict'
    
    // 配置日志
    const log4j = require('log4js');
    const logger = log4j.getLogger();
    
    // 配置http服务器
    const {createServer} = require('http');
    const express = require('express');
    const serveIndex = require('serve-index');
    
    const app = express();
    // 配置静态文件,位置不能乱
    app.use(serveIndex('./public'));
    app.use(express.static('./public'));
    
    // 创建http服务器
    const http_server = createServer(app);
    
    // 配置socket io
    const {Server} = require("socket.io");
    // 让socketIo监听https服务器
    
    const io = new Server(http_server);
    
    
    // 配置socket发送消息逻辑
    io.on('connection', (socket) => {
        logger.info('socket connection : ', socket);
        //socket.emit('joined',room,socket.id);只给当前用户发送
        //socket.to(room).emit('joined', room, socket.id);//除自己之外
        //io.in(room).emit('joined', room, socket.id)//房间内所有人
        //socket.broadcast.emit('joined', room, socket.id);//除自己,全部站点
        // 加入房间的处理逻辑
        socket.on('join', (room, userName) => {
            logger.info(`user join in. userName=${userName},room=${room}`);
            // socket加入room中
            socket.join(room);
            // 发送给当前用户用户加入成功
            socket.emit('joined', room, socket.id);
        });
    
        // 离开房间的处理逻辑
        socket.on('leave', (room, userName) => {
            logger.info(`user leave. userName=${userName},room=${room}`);
            socket.leave(room);
            socket.emit('leaved', room, socket.id);
        })
    
        // 发送消息逻辑
        socket.on('message', (room, data) => {
            // 给房间内的所有人发送消息(包括自己)
            io.in(room).emit('message', data);
            //不给自己发,只给别人发(前端需要适配自己发送的内容到消息显示框)
            //socket.to(room).emit('message', data);
        })
    
    });
    
    //启动服务器
    http_server.listen(80);
    
    
    • 1
    • 2
    • 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

    3、index.html

    index.html是聊天的网页,代码如下:

    <html>
    <head>
        <title>Chat roomtitle>
        <link rel="stylesheet" href="./css/main.css">
    head>
    
    <body>
    <table align="center">
        <tr>
            <td>
                <label>UserName:label>
                <input type="text" id="userName">
            td>
        tr>
        <tr>
            <td>
                <label>Room:label>
                <input type="text" id="room">
                <button id="connect">Connectbutton>
                <button id="leave">Leavebutton>
            td>
        tr>
        <tr>
            <td>
                <label>Content: label><br>
                <textarea disabled style="line-height: 1.5;" id="content" rows="10" cols="100">textarea>
            td>
        tr>
        <tr>
            <td>
                <label>Input: label><br>
                <textarea disabled id="input" rows="3" cols="100">textarea>
            td>
        tr>
        <tr>
            <td>
                <button disabled id="send">Sendbutton>
            td>
        tr>
    table>
    
    
    <script src="/socket.io/socket.io.js">script>
    <script src="./js/client.js">script>
    body>
    
    html>
    
    • 1
    • 2
    • 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

    4、main.css

    main.css是index.html的布局格式文件,代码如下:

    /*
     *  Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
     *
     *  Use of this source code is governed by a BSD-style license
     *  that can be found in the LICENSE file in the root of the source
     *  tree.
     */
    button {
      margin: 0 20px 25px 0;
      vertical-align: top;
      width: 134px;
    }
    
    div#getUserMedia {
      padding: 0 0 8px 0;
    }
    
    div.input {
      display: inline-block;
      margin: 0 4px 0 0;
      vertical-align: top;
      width: 310px;
    }
    
    div.input > div {
      margin: 0 0 20px 0;
      vertical-align: top;
    }
    
    div.output {
      background-color: #eee;
      display: inline-block;
      font-family: 'Inconsolata', 'Courier New', monospace;
      font-size: 0.9em;
      padding: 10px 10px 10px 25px;
      position: relative;
      top: 10px;
      white-space: pre;
      width: 270px;
    }
    
    section#statistics div {
      display: inline-block;
      font-family: 'Inconsolata', 'Courier New', monospace;
      vertical-align: top;
      width: 308px;
    }
    
    section#statistics div#senderStats {
      margin: 0 20px 0 0;
    }
    
    section#constraints > div {
      margin: 0 0 20px 0;
    }
    
    section#video > div {
      display: inline-block;
      margin: 0 20px 0 0;
      vertical-align: top;
      width: calc(50% - 22px);
    }
    
    section#video > div div {
      font-size: 0.9em;
      margin: 0 0 0.5em 0;
      width: 320px;
    }
    
    h2 {
      margin: 0 0 1em 0;
    }
    
    section#constraints label {
      display: inline-block;
      width: 156px;
    }
    
    section {
      margin: 0 0 20px 0;
      padding: 0 0 15px 0;
    }
    
    section#video {
      width: calc(100% + 20px);
    }
    
    video {
      --width: 90%;
      display: inline-block;
      width: var(--width);
      height: calc(var(--width) * 0.75);
      margin: 0 0 10px 0;
    }
    
    @media screen and (max-width: 720px) {
      button {
        font-weight: 500;
        height: 56px;
        line-height: 1.3em;
        width: 90px;
      }
    
      div#getUserMedia {
        padding: 0 0 40px 0;
      }
    
      section#statistics div {
        width: calc(50% - 14px);
      }
    
      video {
        display: inline-block;
        width: var(--width);
        height: 96px;
      }
    }
    
    • 1
    • 2
    • 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
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117

    5、client.js

    client.js文件是客户端的处理逻辑文件,包括发送和显示消息,连接服务器等

    'use strict'
    
    // 获取页面组建
    const userNameInput = document.querySelector('input#userName');
    const roomInput = document.querySelector('input#room');
    
    const connectBtn = document.querySelector('button#connect');
    const leaveBtn = document.querySelector('button#leave');
    
    const contentArea = document.querySelector('textarea#content');
    const inputArea = document.querySelector('textarea#input');
    
    const sendBtn = document.querySelector('button#send');
    
    var socket;
    // 连接逻辑
    connectBtn.onclick = () => {
        //连接
        socket = io();
    
        // 成功加入后的逻辑
        socket.on('joined', (room, id) => {
            console.log(`join in successful,room=${room},socketId=${id}`);
            connectBtn.disabled = true;
            leaveBtn.disabled = false;
            inputArea.disabled = false;
            sendBtn.disabled = false;
            roomInput.disabled = true;
            userNameInput.disabled = true;
        });
    
        //离开成功的逻辑
        socket.on('leaved', (room, id) => {
            console.log(`user leave ,room=${room},socketId=${id}`);
            connectBtn.disabled = false;
            leaveBtn.disabled = true;
            inputArea.disabled = true;
            sendBtn.disabled = true;
            roomInput.disabled = false;
            userNameInput.disabled = false;
            socket.disconnect();
        });
    
        // 断开连接
        socket.on('disconnect', (socket) => {
            connectBtn.disabled = false;
            leaveBtn.disabled = true;
            inputArea.disabled = true;
            sendBtn.disabled = true;
            roomInput.disabled = false;
            userNameInput.disabled = false;
        });
    
        // 接受到消息的逻辑
        socket.on('message', (data) => {
            //窗口总是显示最后的内容
            contentArea.scrollTop = contentArea.scrollHeight;
            contentArea.value = contentArea.value + data + '\r';
        });
    
        // 发送加入的信令
        socket.emit('join', roomInput.value, userNameInput.value);
    }
    
    //断开连接
    leaveBtn.onclick = () => {
        socket.emit('leave', roomInput.value, userNameInput.value);
    }
    
    //发送消息的逻辑
    sendBtn.onclick = () => {
        sendMessage();
    }
    
    // 回车发送消息的逻辑
    inputArea.onkeypress = (event) => {
        //回车发送消息
        if (event.keyCode !== 13) {
            return;
        }
        sendMessage();
        //阻止默认行为
        event.preventDefault();
    }
    
    function sendMessage() {
        let data = userNameInput.value + ' : ' + inputArea.value;
        socket.emit('message', roomInput.value, data);
        inputArea.value = '';
    }
    
    • 1
    • 2
    • 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
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90

    6、启动服务器

    找到项目所在的目录,安装项目需要的模块

    npm install express serve-index log4js socket.io
    
    • 1

    用以下命令启动服务器

    node server.js
    
    • 1

    7、功能演示

    打开两个浏览器的窗口,分别输入项目地址http://localhost/index.html(如果是别的ip,将localhost换成对应的ip地址),在浏览器上面输入用户名(不同),房间room(相同),点击connect按钮就可以发送消息。
    在这里插入图片描述
    另一个客户端:
    在这里插入图片描述


    后记
      个人总结,欢迎转载、评论、批评指正

  • 相关阅读:
    Redis存储结构之zskiplist
    【配置教程】撑起月6亿PV开源监控解决方案
    Linux命令(93)之su
    IP地址的分类,五分类编制CIDR以及子网的划分和子网掩码
    SQL:sql连接那些事儿
    STM32外部Flash移植FATFS笔记
    Java系列之:字符串的截取及分割 split() 和 substring()
    海外动态IP代理可以用来批量注册邮箱吗?
    议题征集中|KCD 2023 杭州站
    并发编程 ---为何要线程池化
  • 原文地址:https://blog.csdn.net/u012559967/article/details/134536101