websocket入門和實現聊天室

1.先提一個思考?

在傳統http思惟當中,有(瀏覽器1,服務器,瀏覽器2三個角色),如何實現瀏覽器1發送消息,而後,瀏覽器2接收看到瀏覽器1發送的消息,反之同樣。html

http能不能實現這種聊天的效果?前端

答案固然是能的,可是比較麻煩。node

由於 http是基於 請求 -------- 響應 這種模型的jquery

服務器沒有辦法「主動」給瀏覽器發送消息的git

因此只能在瀏覽器2當中,加入ajax輪詢,不斷的請求服務器,以獲取到最新的消息,來展現到客戶端github

2.websocket介紹

websocket是一種網絡協議,容許客戶端和服務端全雙工的進行網絡通信,服務器能夠給客戶端發消息,客戶端也能夠給服務器發消息。web

至關於瀏覽器和客戶端之間創建的是一種長連接,不會斷開,在這個管道內可以互相的,不斷的進行信息交互 (而傳統http是:瀏覽器請求數據,服務端響應數據後,連接斷開)ajax

3.websocket的使用

在HTML5中,瀏覽器已經實現了websocket的API,直接使用便可。WebSocket-MDNexpress

3.1 建立websocket鏈接

// 參數1: url:鏈接的websocket屬性
// 參數2: protocol,可選的,指定鏈接的協議
// var socket = new WebSocket('ws://echo.websocket.org') =>官方提供的地址
var Socket = new WebSocket(url, [protocol] );

3.2 websocket的一些事件

事件 事件處理程序 描述
open Socket.onopen 鏈接創建觸發
message Socket.onmessage 客戶端接收服務端數據時觸發
error Socket.onerror 通訊發生錯誤時觸發
close Socket.onclose 鏈接關閉時觸發

3.3 websocket方法

方法 描述
Socket.send() 使用鏈接發送數據
Socket.close() 關閉鏈接

4.使用node.js開發websocket服務

先簡單瞭解一下,有關於nodejs-websocket有哪些api,和怎麼使用json

前端頁:

<style>
      div {
        width: 200px;
        height: 200px;
        border: 1px solid #000;
      }
    </style>

  <!-- 用於收集輸入內容 -->
    <input type="text" placeholder="請輸入須要發送的內容" />
    <!-- 用於發送websocket請求 -->
    <button>websocket測試</button>
    <!-- 用於顯示websock服務器的響應 -->
    <div class="show"></div>

    <script>
      var input = document.querySelector('input')
      var button = document.querySelector('button')
      var div = document.querySelector('div')
      // 1. 建立websocket對象, 這個地址是官方提供的地址
      // var socket = new WebSocket('ws://echo.websocket.org')
      var socket = new WebSocket('ws://localhost:3000')

      // 2. 給websocket註冊事件
      socket.addEventListener('open', function() {
        // 與服務端創建鏈接的時候觸發
        div.innerText = '恭喜你,與服務端創建鏈接了'
      })
      // 如何給服務器發送消息
      button.addEventListener('click', function() {
        socket.send(input.value)
        input.value = ''
      })

      // 若是接收服務器的數據
      socket.addEventListener('message', function(e) {
        console.log('接收到服務器的數據了', e)
        // 將接收到服務器的數據展現出來
        div.innerText = e.data
      })

      socket.addEventListener('close', () => {
        div.innerHTML = '與服務器斷開鏈接'
      })
    </script>

服務端(app.js): 使用前需下載nodejs-websocket包

//1.導入nodejs-websocket包
const ws = require('nodejs-websocket')
const PORT = 3000

//2.建立一個服務

//2.1 如何處理用戶的請求 
//每次只要有用戶鏈接,函數就會被執行,會給當前鏈接的用戶建立 一個connect對象
var server = ws.createServer(connect =>{
    console.log('有用戶鏈接上來了')
    //每當接收到用戶傳遞過來的數據,這個text事件就會被觸發
    connect.on('text',data=>{
        console.log('接收到了用戶的數據:'+data)
        //返回客戶發送的消息
        connect.send('用戶發送的消息是:'+ data)
    })


    //只要websocket鏈接斷開了,close事件就會觸發
    connect.on('close',()=>{
        console.log('鏈接斷開了')
    })

    //在處理用戶斷開鏈接後,除了處理colse事件,還必需處理error事件,否則會報錯
    connect.on('error',()=>{
        console.log('用戶鏈接異常')
    })
})
server.listen(PORT,()=>{
    console.log('服務啓動成功,監聽了端口:'+PORT)
})

node app.js 便可啓動服務

5.websocket開發簡易聊天室

前端頁:

<!-- 用於收集輸入內容 -->
    <input type="text" placeholder="請輸入須要發送的內容" />
    <!-- 用於發送websocket請求 -->
    <button>websocket測試</button>
    <!-- 用於顯示websock服務器的響應 -->
    <div class="show"></div>

    <script>
      var input = document.querySelector('input')
      var button = document.querySelector('button')
      var div = document.querySelector('div')
      // 1. 建立websocket對象, 這個地址是官方提供的地址
      // var socket = new WebSocket('ws://echo.websocket.org')
      var socket = new WebSocket('ws://localhost:3000')

      // 2. 給websocket註冊事件
      socket.addEventListener('open', function() {
        // 與服務端創建鏈接的時候觸發
        div.innerText = '恭喜你,與服務端創建鏈接了'
      })
      // 如何給服務器發送消息
      button.addEventListener('click', function() {
        socket.send(input.value)
        input.value = ''
      })

      // 若是接收服務器的數據
      socket.addEventListener('message', function(e) {
        //將服務端返回的json字符串,轉成對象
        var data = JSON.parse(e.data)
        //建立一個div(此時只是建立,並無插入到頁面當中)
        var dv = document.createElement('div')
        //在牀的div中插入數據
        dv.innerHTML = data.msg + '----' + data.date
        //不一樣的消息類型改變消息的樣式
        if (data.type === 0) {
          dv.style.color = 'green'
        }
        if (data.type === 1) {
          dv.style.color = 'red'
        }
        if (data.type === 2) {
          dv.style.color = 'gray'
        }
        //最後將帶有數據的盒子插入到頁面當中
        div.appendChild(dv)
      })

      socket.addEventListener('close', () => {
        div.innerHTML = '與服務器斷開鏈接'
      })
    </script>

服務端(app.js):

const ws = require('nodejs-websocket')

//端口
const PORT = 3000
//返回客戶端是哪一種信息的標記
const TYPE_MSG = 0  
const TYPE_ENTER = 1
const TYPE_LEAVE = 2

//用戶的數量,用戶進入+1,用戶離開-1
let userCount = 0
const server = ws.createServer(connect => {
  console.log('有新用戶鏈接了')
  // 每次有新用戶鏈接,須要給全部用戶發送一條新增用戶的消息
  userCount++
  //給每一個connect對象添加一個name名稱
  connect.userName = 'user' + userCount
  // 給全部的用戶進行廣播,客戶端可根據不一樣的type值渲染不一樣樣式的消息
  broadcast({
    type: TYPE_ENTER,
    msg: connect.userName + '進入了聊天室',
    date: new Date().toLocaleTimeString()
  })

  connect.on('text', msg => {
    // 若是接收到用戶的數據, 須要發送給全部的用戶
    broadcast({
      type: TYPE_MSG,
      msg: msg,
      date: new Date().toLocaleTimeString()
    })
  })
  connect.on('close', () => {
    console.log('用戶斷開鏈接')
    userCount--
    // 給全部的用戶發送一條用戶離開的消息
    broadcast({
      type: TYPE_LEAVE,
      msg: `${connect.userName}離開了聊天室`,
      date: new Date().toLocaleTimeString()
    })
  })
  connect.on('error', () => {
    console.log('鏈接失敗')
  })
})

//廣播消息的方法  connections:存有所有用戶,至關於connect數組集合
function broadcast(msg) {
  server.connections.forEach(conn => {
    //原生的websocekt的sendText()不容許返回對象數據,只能返回字符串
    conn.sendText(JSON.stringify(msg))
  })
}

server.listen(PORT, () => {
  console.log('服務器啓動成功了', PORT)
})

6.原生websocket的缺點

一次給全部用戶廣播消息的功能,都須要經過一個存有全部用戶的數組屬性來本身封裝,支持的事件少,返回給客戶端的數據也只能是字符串格式,提供的api少

可是:這其實也不能說是websocket的缺點,由於它自己就是提供基礎能力而出現的,並非爲了解決咱們業務代碼的便利而出現,因此,一般在websocket的特性的時候,咱們經常會用框架來更簡單,高效的實現咱們所須要的功能 -> socket.io

7.socket.io的基本用法

前端頁面(當前頁面是建立在與app.js同級目錄下):

<!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>Document</title>
  </head>
  <body>
    哈哈
    <script src="/socket.io/socket.io.js"></script>
    <script>
      // 鏈接socket服務
      // 參數:服務器地址
      var socket = io('http://localhost:3000')

      // 接受服務器返回的數據
      // socket.on('send', data => {
      //   console.log(data)
      // })
      socket.emit('hehe', { name: 'zs', age: 18 })

      socket.on('send', function(data) {
        console.log(data)
      })
    </script>
  </body>
</html>

app.js:

const http  = require('http');
const fs = require('fs');
const app = http.createServer();

app.listen(3000,()=>{
    console.log('服務器啓動成功')
});

//將建立的node服務傳入到socket.io方法中
const io = require('socket.io')(app);

function handler (req, res) {
  fs.readFile(__dirname + '/index.html',
  (err, data) => {
    if (err) {
      res.writeHead(500);
      return res.end('Error loading index.html');
    }

    res.writeHead(200);
    res.end(data);
  });
}
//監聽用戶鏈接的事件
io.on('connection', (socket) => {
  console.log('有用戶進入了')
  //socket.emit 方法表示給到瀏覽器發送數據
  //參數1:事件的名字(自定義)
  socket.emit('send', { hello: 'world' });
  socket.on('hehe', (data) => {
    console.log(data);
  });
});

總結:socket.io在先後端通用,socket表示用戶鏈接,socket.emit 表示觸發某個事件,socket.on表示監聽某個事件,在使用socket.io的時候,不論是前端仍是後端,都是這樣接收和發送事件來進行數據的傳輸

8.實現一個多功能的聊天室

github項目代碼地址:

1.使用了socket.io express  => app.js (參照socket.io官方demo)
2.在index.html 中引入socket.io 等包
3.使用express處理靜態資源,而且重定向訪問 / 根目錄時 => 靜態資源文件夾(public)下的index.html

node app.js便可啓動項目

9.聊天室的先後端業務代碼總結

代碼地址:https://github.com/Y-Yin/js-project-demo

9.1 簡單登陸

===前端===
1.選擇頭像=>
$('#login_avatar li').on('click', function() {
  $(this)
    .addClass('now')
    .siblings()
    .removeClass('now')
})
//點擊添加now類,讓被點擊的頭像出現選擇框



2.點擊登陸按鈕,發送頭像和用戶名給服務器=>
$('#loginBtn').on('click', function(){
  // 獲取用戶名
  var username = $('#username').val().trim()
  if (!username) {
    alert('請輸入用戶名')
    return
  }
  // 獲取選擇的頭像
  var avatar = $('#login_avatar li.now img').attr('src')
  // 須要告訴socket io服務,登陸,而且傳入信息
  socket.emit('login', {
    username: username,
    avatar: avatar
  })
})

===後端===
對用戶重複登錄的處理  =>
    // 記錄全部已經登陸過的用戶
	const users = []
	//尋找users數組中有沒有重複用戶名
    let user = users.find(item => item.username === data.username)
    if (user) {
      // 表示用戶存在, 登陸失敗. 服務器須要給當前用戶響應,告訴登陸失敗
      socket.emit('loginError', { msg: '登陸失敗' })
      // console.log('登陸失敗')
    } else {
      // 表示用戶不存在, 登陸成功
      users.push(data)
      // 告訴用戶,登陸成功
      socket.emit('loginSuccess', data)
      // console.log('登陸成功')

9.2顯示我的用戶信息

===前端===
//監聽登陸成功後,服務器有把我的信息返回了
socket.on('loginSuccess', data => {
  // 須要顯示聊天窗口
  // 隱藏登陸窗口
  $('.login_box').fadeOut()
  $('.container').fadeIn()
  // 設置我的信息
  console.log(data)
  $('.avatar_url').attr('src', data.avatar)
  $('.user-list .username').text(data.username)

  username = data.username
  avatar = data.avatar
})

9.3顯示加入羣聊的消息

===後端===
//使用socket.io的內置api給每一個用戶發送消息,把頭像和名稱發送給每個人
io.emit('addUser', data)
//data傳給前端的不只有消息數據,還有消息類型type:0,1,2... 前端根據數據的類型,渲染出不一樣的消息樣式
//=> 好比先經過消息類型建立好元素,而且加入樣式,再將樣式插入到頁面當中

9.4用戶列表和聊天人數

//後端判斷當登陸成功以後,繼續emit一個事件,將全部用戶數據都扔給前端

9.5離開聊天室

//使用內置,用戶離開觸發的事件api

//=>
1.把當前y用戶的信息從users中刪除調用
2.告訴全部人,有人離開了聊天室
3.告訴全部人,userlist發生更新了

//=>
當登陸成功的時候,就將當前用戶的信息存儲起來
socket.username = data.username
socket.avatar = data.avatar
當用戶離開的時候,判斷離開的用戶是全局用戶中的哪個
let idx = users.findIndex(item => item.username === socket.username)
根據下標刪除這個用戶
users.splice(idx,1)
告訴全部人,有人離開的聊天室

9.6消息老是在最底部開始顯示

//使用到了一個方法 :element.scrollIntoView() -> 原生dom方法,不是jq方法

//找到盒子當中的最後一個dom對象,跳轉到那個高度
//children(':last')找最後一個子元素
$('.box-bd').children(':last').get(0).scrollIntoView(false)

9.7發送圖片

前端:
<a> <label> <input ...></label></a>標籤包住一個隱藏的input type="file" 標籤,這樣,咱們在點擊a標籤的時候也就是點擊了被隱藏的input選擇文件標籤

$('input_file').on('change',function(){
    //拿到上傳的文件
    var file = this.files[0]
    //須要把文件發送到服務器,藉助於h5新增的fileReader
    var fr = new FileReader()
    //讀取這個文件
    fr.readAsDataURL(file)
    //讀取成功
    fr.onload = function(){
        //fr.result爲圖片讀取成功後的結果
        console.log(fr.result)
        socket.emit('send',{
            username:name,
            avater:avater,
            img:fr.result
        })
    }
})

後端
接收到用戶上傳的圖片數據後,直接廣播給全部用戶,經過事件將數據發出

前端:
全部用戶接收到圖片消息,將圖片數據append進聊天盒子
$('...').append('  <img src="${data.img}"> ')

bug :發送圖片消息,聊天框老是不在底部開始顯示消息

緣由:由於圖片還沒加載完成,就調用了scrollIntoView方法

解決:監聽最後一張圖片的加載,在調用scrollIntoView

9.8 jquery-emoji 表情包的使用

具體查看 jquery-emoji 插件文檔
相關文章
相關標籤/搜索