Day15 [实作] 使用 Socket.io 建立聊天室

实作

  1. 聊天室 server 端,使用 SSL
  2. 聊天室 client 端,使用 SSL

为什麽要使用 Socket.io

  1. 使用webSocket
    1. 建立在 TCP 协议之上
    2. 资料格式比较轻量
    3. 可以发送文字,也可以发送二进位资料
  2. 有房间的概念
  3. 跨平台(浏览器/IOS/Android/…),跨语言

服务器端实作

  1. 建立专案

    cd Desktop/WebRTC/sample
    mkdir socketio-chartroom
    cd socketio-chartroom
    touch server.js
    npm init --yes
    npm install express socket.io
    

    https://ithelp.ithome.com.tw/upload/images/20210929/20130062iPa9G6k5HR.png

  2. 产生本地端开发用SSL凭证

    https://ithelp.ithome.com.tw/upload/images/20210929/20130062tfUNTSEBjM.png

    1. 安装 mkcert
    2. 开启终端机 cd 到专案下
    3. mkcert -install
    4. mkcert 0.0.0.0 localhost 127.0.0.1 ::1
    5. mv 0.0.0.0+3-key.pem key.pem & mv 0.0.0.0+3.pem cert.pem
  3. 在 server.js 加入以下程序码

    'use strict'
    
    const https = require('https')
    const fs = require('fs')
    const express = require('express')
    const socketIo = require('socket.io')
    
    const app = express()
    
    const options = {
      key: fs.readFileSync('key.pem'),
      cert: fs.readFileSync('cert.pem'),
    }
    
    const https_server = https.createServer(options, app)
    https_server.listen(4443, '0.0.0.0')
    
    const io = socketIo(https_server, {
      cors: true,
    })
    
    // 收到使用者连线
    io.sockets.on('connection', (socket) => {
      console.log(socket.id, '已连线')
    
      socket.on('message', (room, data) => {
        io.in(room).emit('message', room, data)
      })
    
      socket.on('join', (room) => {
        socket.join(room)
        socket.emit('joined', room, socket.id)
      })
    
      socket.on('leave', (room) => {
        socket.leave(room)
        socket.to(room).emit('bye', room, socket.id)
        socket.emit('leave', room, socket.id)
      })
    })
    

使用者端实作

  1. 在专案内新增一个 client 资料夹及以下结构

    client
    ├── index.html
    └── js
        └── client.js
    
  2. index.html 中加入

    <html>
      <head>
        <title>Chat Room</title>
      </head>
      <body>
        <table align="center">
          <h1 style="text-align: center">聊天室</h1>
          <tr>
            <td>
              <label for="username">UserName: </label>
              <input type="text" id="username" />
            </td>
          </tr>
          <tr>
            <td>
              <label for="room">room: </label><input type="text" id="room" />
              <button id="connect">Connect</button>
              <button id="leave" disabled>Leave</button>
            </td>
          </tr>
          <tr>
            <td>
              <label for="output">Content: </label><br />
              <textarea disabled style="line-height: 1.5" id="output" rows="10" cols="100"></textarea>
            </td>
          </tr>
          <tr>
            <td>
              <label for="input">Input: </label><br />
              <textarea disabled id="input" rows="3" cols="100"></textarea>
            </td>
          </tr>
          <tr>
            <td>
              <button id="send">Send</button>
            </td>
          </tr>
        </table>
    
        <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
        <script src="./js/client.js"></script>
      </body>
    </html>
    
  3. client.js 中加入

    'use strict'
    
    const userName = document.querySelector('input#username')
    const inputRoom = document.querySelector('input#room')
    const btnConnect = document.querySelector('button#connect')
    const btnLeave = document.querySelector('button#leave')
    const outputArea = document.querySelector('textarea#output')
    const inputArea = document.querySelector('textarea#input')
    const btnSend = document.querySelector('button#send')
    
    let socket
    let room
    
    btnConnect.onclick = () => {
      socket = io('wss://localhost:4443')
    
      socket.on('joined', (room, id) => {
        btnConnect.disabled = true
        btnLeave.disabled = false
        inputArea.disabled = false
        btnSend.disabled = false
      })
    
      socket.on('leave', (room, id) => {
        btnConnect.disabled = false
        btnLeave.disabled = true
        inputArea.disabled = true
        btnSend.disabled = true
    
        socket.disconnect()
      })
    
      socket.on('message', (room, data) => {
        console.log(room, data)
        outputArea.scrollTop = outputArea.scrollHeight
        outputArea.value = outputArea.value + data + '\n'
      })
    
      socket.on('disconnect', (reason) => {
        btnConnect.disabled = false
        btnLeave.disabled = true
        inputArea.disabled = true
        btnSend.disabled = true
      })
    
      room = inputRoom.value
      socket.emit('join', room)
    }
    
    btnSend.onclick = () => {
      let data = inputArea.value
      data = userName.value + ':' + data
      socket.emit('message', room, data)
      inputArea.value = ''
    }
    
    btnLeave.onclick = () => {
      room = inputRoom.value
      socket.emit('leave', room)
    }
    
    inputArea.onkeypress = (event) => {
      if (event.keyCode === 13) {
        let data = inputArea.value
        data = userName.value + ':' + data
        socket.emit('message', room, data)
        inputArea.value = ''
        event.preventDefault()
      }
    }
    

启动服务

  1. 启动 server

    开启终端机,cd 进入专案,执行 node server.js

    https://ithelp.ithome.com.tw/upload/images/20210929/20130062kIEx9td177.png

  2. 启动使用者端

    开启另一个终端机,cd 进入专案的 client 资料夹内,执行

    http-server -S -C ../cert.pem -K ../key.pem -o
    

    https://ithelp.ithome.com.tw/upload/images/20210929/20130062ggrPyDYAwY.png


<<:  第29天 - 文件审核系统(7)_审核端3

>>:  Day15 CSS二

手机节省行动数据的几个方法 4G 吃不饱

手机节省行动数据的几个方法 4G 吃不饱 没有学生方案 或者是低流量限速方案吃到饱方案 只能从关掉手...

D26 - 「来互相伤害啊!」:站在 Phaser 的肩膀上

鳕鱼:「再来要设计对战游戏,可以切换场景,人物可以在场地随意移动,发射武器互相攻击,人物会与墙壁、敌...

Day 22 - 运算过载,warning ! warning !

Outline Motivations(为什麽要做 operation overloading) a...

想要吃牛,必须先种草 -- input与print

注释有单行与多行 #这是单行注释 """ 这是多行注释 "...

3 所以要长怎样?

聊一下更新画面这回事 自己以前的经验,我在出一张牌的时候,都是从前端通知後端说出了牌,更新完服务器端...