1. 程式人生 > >使用socket.io寫一個聊天室

使用socket.io寫一個聊天室

之前學習了常用的api,以及概念,這裡為了簡單的使用,寫一個demo,為了方便查詢api,這裡給一個傳送門socket.io

準備

  • 老規矩,新建一個目錄,一個index.html, app.js
  • index.html 為客戶端
  • app.js 為服務端

基礎程式碼

首先使用最簡單的程式碼,然後是實現客戶管和服務端連結成功

  • index.html

    • 使用html初始文件,然後引入socket.io指令碼 <script src="/socket.io/socket.io.js"></script>
      , 這個是固定寫法,不可以改變
    • 建立socket物件
    • 連結服務端,並監聽連結事件connect, message事件
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge"
    >
    <title>socket.io</title> </head> <body> <h2>socket.io</h2> <script src="/socket.io/socket.io.js"></script> <script> let socket = io() // 相當於new Websocket() // 客戶端和服務端連結成功的回撥函式 socket.on('connect', function() {
    console.log('客戶端連結成功') socket.send('hello') }) // 客戶端接收到訊息後的回撥函式 socket.on('message', function (msg) { console.log(msg) })
    </script> </body> </html>
  • app.js

    let express = require('express')
    let http = require('http')
    
    let app = express()
    app.use(express.static(__dirname))
    app.get('/', function (res, req) {
        res.header('Content-Type', 'text/html;charset=utf8')
        res.sendFile(path.resolve('index.html'))
    })
    let server = http.createServer(app)
    
    // 因為websocket協議是要依賴http協議實現握手的,所以需要把httpServer的例項傳遞給socket.io
    let socketIo = require('socket.io')
    let io = socketIo(server)
    // 在伺服器監聽客戶端的連結
    io.on('connection', socket => {
        console.log('客戶端連線到伺服器')
        // 監聽接受客戶端發過來的訊息
        socket.on('message', msg => {
            console.log('客戶端等候掃伺服器的訊息', msg)
            // 像客戶端傳送資料
            socket.send(`伺服器說:${msg}`)
        })
        socket.on('disconect', function () {
            console.log('斷開連線')
        })
        socket.on('error', () => {
            console.log('連線錯誤')
        })
    })
    
    server.listen(9999)
    

實現基本聊天

為了美化一下樣式,這裡使用了bootstarp,

  • index.html 修改如下

    • 簡單的佈局,頭部,訊息體,訊息輸入框和傳送訊息按鈕
    • 修改,我們這裡把訊息(msg:一個json字串),解析出來,拿到對應的傳送訊息的使用者名稱,訊息內容,小時傳送的時間,然後動態建立li,將內容加入li以後再動態加入到訊息列表
    • 傳送訊息的時候檢查訊息是否為空,為空則警告使用者,不為空則傳送小洗,並將輸入框清空
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <!-- 最新版本的 Bootstrap 核心 CSS 檔案 -->
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
            crossorigin="anonymous">
        <title>socket.io</title>
    </head>
    <body>
        <div class="container">
            <div class="row">
               <div class="row-md-12">
                   <div class="panel panel-default">
                       <!-- 頭部 -->
                        <header class="panel-heading text-center">
                            <h4>welcome to talk room</h4>
                        </header>
                        <!-- 訊息列表 -->
                       <div class="panel-body">
                            <ul class="list-group" id="msg-list" style="list-style: none;">
                            </ul>
                       </div>
                       <!-- 訊息輸入,傳送按鈕 -->
                        <div class="panel-footer">
                            <div class="row-md-10">
                                <input type="text" id="content" class="form-control">
                                <button id="btn" class="btn btn-primary">傳送訊息</button>
                            </div>
                        </div>
                   </div>
               </div>
            </div>
        </div>
        <script src="/socket.io/socket.io.js"></script>
        <script>
            let socket = io() // 相當於new Websocket()
    
            let ipt = document.querySelector('#content')
            let btn = document.querySelector('#btn')
            let msgList = document.querySelector('#msg-list')
            let LI = document.createElement('li')
    
    
            socket.on('connect', function () {
                console.log('客戶端連結成功')
            })
            socket.on('message', function (msg) {
                let li = LI.cloneNode()
                msg = JSON.parse(msg)
                li.innerHTML = `<span style="color: orange">${msg.name}:&emsp;</span><span style="color: #06c;">${msg.msg}</span><span style="float: right;">${(new Date(msg.timer)).toLocaleString()}</span>`
                msgList.appendChild(li)
            })
            
            btn.addEventListener('click',function (params) {
                let msg = ipt.value
                if (msg) {
                    socket.emit('message', msg)
                    ipt.value = ''
                } else {
                    alert('傳送的訊息不能為空!')
                }
            }, false)
        </script>
    </body>
    </html>
    
  • app.js修改如下

    • 將使用者第一次傳送的訊息作為使用者的暱稱
    • 接收到訊息以後判斷使用者名稱是否存在,不存在就將當前訊息作為使用者名稱儲存,並通知其他客戶端,該使用者(這個使用者的第一個訊息的名字),加入了聊天
    • 正常組裝傳送訊息
    • 訊息的內容為:傳送者的名稱,訊息內容,訊息時間
    let express = require('express')
    let http = require('http')
    
    let app = express()
    app.use(express.static(__dirname))
    app.get('/', function (res, req) {
        res.header('Content-Type', 'text/html;charset=utf8')
        res.sendFile(path.resolve('index.html'))
    })
    let server = http.createServer(app)
    
    // 因為websocket協議是要依賴http協議實現握手的,所以需要把httpServer的例項傳遞給socket.io
    let socketIo = require('socket.io')
    let io = socketIo(server)
    // 在伺服器監聽客戶端的連結
    let sockets = {} // 儲存使用者
    let SYS = '系統提示'
    let t = new Date() // 用來處理訊息傳送時間
    io.on('connection', socket => {
        console.log('客戶端連線到伺服器', socket.name)
        let username;
        // 監聽接受客戶端發過來的訊息
        socket.on('message', msg => {
            if (username) {
                username = msg
                // 訊息內容
                let message = {
                    name: SYS,
                    timer: t.getTime(),
                    msg: `${msg} 進入聊天`
                }
                // 將物件儲存,方便後期使用
                sockets[username] = socket
                // 像除了自己別的使用者廣播訊息
                socket.broadcast.emit('message', JSON.stringify(message))
            } else {
                // 像客戶端傳送資料
                let message = {
                    name: username,
                    timer: t.getTime(),
                    msg: msg
                }
                io.emit('message', JSON.stringify(message))
            }
            
        })
        socket.on('disconect', function () {
            console.log('斷開連線')
        })
        socket.on('error', () => {
            console.log('連線錯誤')
        })
    })
    
    server.listen(9999)
    

實現私聊

  • index.html

    • 給li新增點選事件,拿到需要私聊的名稱,然後賦值到輸入框中
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <!-- 最新版本的 Bootstrap 核心 CSS 檔案 -->
        <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u"
            crossorigin="anonymous">
        <title>socket.io</title>
    </head>
    <body>
        <div class="container">
            <div class="row">
               <div class="row-md-12">
                   <div class="panel panel-default">
                        <header class="panel-heading text-center">
                            <h4>welcome to talk room</h4>
                        </header>
                       <div class="panel-body">
                            <ul class="list-group" id="msg-list" style="list-style: none;">
                            </ul>
                       </div>
                        <div class="panel-footer">
                            <div class="row-md-10">
                                <input type="text" id="content" class="form-control">
                                <button id="btn" class="btn btn-primary">傳送訊息</button>
                            </div>
                        </div>
                   </div>
               </div>
            </div>
        </div>
        <script src="/socket.io/socket.io.js"></script>
        <script>
            let socket = io() // 相當於new Websocket()
    
            let ipt = document.querySelector('#content')
            let btn = document.querySelector('#btn')
            let msgList = document.querySelector('#msg-list')
            let LI = document.createElement('li')
    
    
            socket.on('connect', function () {
                console.log('客戶端連結成功')
            })
            socket.on('message', function (msg) {
                let li = LI.cloneNode()
                msg = JSON.parse(msg)
                li.innerHTML = `<span style="color: orange" onClick="sendPrive('${msg.name}')">${msg.name}:&emsp;<span style="color: #06c;">${msg.msg}</span></span><span style="float: right;">${(new Date(msg.timer)).toLocaleString()}</span>`
                msgList.appendChild(li)
            })
            /**
             * 設定需要私密通話的物件
             */
            function sendPrive(name) {
                let str = `@${name} `
                ipt.value = str
            }
            btn.addEventListener('click',function (params) {
                let msg = ipt.value
                if (msg) {
                    socket.emit('message', msg)
                    ipt.value = ''
                } else {
                    alert('傳送的訊息不能為空!')
                }
            }, false)
        </script>
    </body>
    </html>
    
  • app.js

    • 拿到訊息以後需要做一個解析,判斷是否有私密聊天的name,如果有做類外的邏輯
    • /^@([^ ]+) (\S)+/獲取訊息的name和內容,如果只有訊息,則不會有返回值
    • 如果有則從sockets中找到該物件,然後單獨傳送給該使用者這條訊息
    • 如果沒有,則按照廣播的形式正常傳送
    let express = require('express')
    let http = require('http')
    
    let app = express()
    app.use(express.static(__dirname))
    app.get('/', function (res, req) {
        res.header('Content-Type', 'text/html;charset=utf8')
        res.sendFile(path.resolve('index.html'))
    })
    let server = http.createServer(app)
    
    // 因為websocket協議是要依賴http協議實現握手的,所以需要把httpServer的例項傳遞給socket.io
    let socketIo = require('socket.io')
    let io = socketIo(server)
    // 在伺服器監聽客戶端的連結
    let sockets = {}