實戰WebSocket聊天室:從開發到部署上線

本文僅對一些關鍵性的知識點進行解釋,具體請Fork源碼學習。css

Demo頁面若是沒啥人的話能夠本身新建幾個頁面複製地址進入,每一個頁面都是一個獨立的訪客,兼容PC和移動端訪問。html

Demo 演示前端

Github源碼node

博客原文git

前端實現

前端技術棧

  • Parcel:構建工具,零配置的打包構建工具
  • socket.io:跨平臺的WebSocket通訊庫,具備先後端一致的API,能夠觸發和響應自定義的事件。

前端界面

界面佈局這個沒有啥說的,直接按本身喜愛寫就好了。也能夠直接參照本項目DEMO和源碼。github

socket.io 簡介

socket.io 有先後端一致的API,因此在前端和Node端使用區別不大。docker

socket.io 最主要的API是觸發和響應自定義的事件,除了connect,message,disconnect這些事件的名字不能使用以外,你能夠觸發任何自定義的事件名稱。下面列出本項目中所使用到的一些自定義事件。npm

socket.io 官網json

# 客戶端觸發自定義事件:test
socket.emit('test', data)

# 服務端響應事件
socket.on('test', data => {})
複製代碼

前端觸發事件後端

  • 登陸聊天室
  • 發送消息

前端響應事件

  • 登陸狀態
  • 系統通知
  • 消息發送

源碼解析

鏈接服務器

import io from './assets/js/socket.io'
let socket = io('ws://47.91.235.153:3000')
// 鏈接服務器
socket.on('connect', function () {
  console.log('成功鏈接服務器')
})
複製代碼

登陸聊天室

進入頁面彈窗要求輸入用戶名,後端驗證用戶名不重複後便可關閉登陸彈窗進入聊天室。

# 發送登陸事件
function userLogin () {
  let loginName = document.getElementById('js-loginName').value
  if (loginName === '') {
    alert('你必須輸入用戶名')
  } else {
    // 發送登陸事件
    socket.emit('login', {
      name: loginName
    })
  }
}
oLoginBtn.addEventListener('click', userLogin)

# 響應登陸狀態
socket.on('login', function (data) {
  if (data.status === 'ok') {
    loginStatus = true
    oLogin.style.visibility = 'hidden'
  } else {
    alert(data.text)
  }
})
複製代碼

系統通知

系統通知只能由服務端發送,主要返回用戶進入、離開房間的通知,並返回當前在線用戶。

socket.on('sys', function (data) {
  // 在線人數
  oCount.innerHTML = data.count
  // 加入消息列表
  oMessageBox.innerHTML += `<li class="sys">
    <div class="name">系統通知</div>
    <div class="message">${data.text}</div>
  </li>`
	// 遍歷顯示在線用戶
  let sUser = ''
  data.users.forEach(el => {
    sUser += `<li>${el}</li>`
  });
  oUserBox.innerHTML = sUser
})
複製代碼

消息發送

function sendMessage () {
  // 獲取輸入框
  let oText = document.getElementById('js-text')
  // 當前輸入的內容
  let sText = oText.value
  // 爲空不提交
  if (sText === '') {
    return false
  }
  // 觸發消息發送事件
  socket.emit('message', {
    name: nickName,
    text: sText
  })
  // 消息列表追加本人發送的消息
  oMessageBox.innerHTML += `<li class="my">
    <div class="name">${nickName}</div>
    <div class="message">${sText}</div>
  </li>`
  // 重置內容爲空
  oText.value = ''
  // 消息列表滾動到最底部
  oMessageBox.scrollTop = oMessageBox.scrollHeight
}
oEnter.addEventListener('click', sendMessage)
複製代碼

接收羣聊消息

接收後臺發送的廣播消息,不包含本人發送的消息。

socket.on('message', function (data) {
  // 消息列表追加消息
  oMessageBox.innerHTML += `<li>
    <div class="name">${data.name}</div>
    <div class="message">${data.text}</div>
  </li>`
  // 消息列表滾動到底部
  oMessageBox.scrollTop = oMessageBox.scrollHeight
})
複製代碼

使用 Parcel 開發和打包

Parcel的使用很是簡單,不須要任何配置便可運行和打包應用程序

Parcel 中文官網

# 安裝
npm install -g parcel-bundler

# 開發:http://localhost:1234/ 訪問
parcel index.html

# 編譯
parcel build index.html
複製代碼

Node 服務端實現

Node 技術棧

  • http
  • socket.io

Node端 socket.io

擁有和前端同樣的API,這裏很少作解釋

io.on('connection', function (socket) {
	// 響應當前鏈接用戶的事件
	socket.on('test', data => {})
	// 給當前鏈接的用戶發送事件
	socket.emit('test', data)
	// 廣播給全部人
	io.emit('test', data)
	// 廣播給除當前用戶外全部人
	socket.broadcast.emit('test', data)
})
複製代碼

源碼解析

啓用WebSocket

var app = require('http').createServer()
var io = require('socket.io')(app)
// WebSocket 鏈接
io.on('connection', function (socket) {
	// 全部的事件觸發響應都寫在這裏
})
// 啓用3000端口
app.listen(3000, function () {
  console.log('WebSocket 啓用端口 on *: 3000')
})
複製代碼

用戶登陸聊天室

由於只是個練習小項目,也沒有真正的用戶中心啥的。只用了一個數組users來存儲當前在線用戶。

socket.on('login', function (data) {
	// 檢查 users 中是否有重名用戶
	if (users.indexOf(data.name) >= 0) {
		console.log(data.name + ' 已有重名用戶,請從新輸入暱稱。')
		// 發送登陸失敗事件
		socket.emit('login', {
	   		status: 'err',
	    	text: '已有重名用戶,請從新輸入暱稱。'
		})
	} else {
		// 添加一個用戶
		users.push(data.name)
		// 設置當前用戶的 nickName
		socket.nickName = data.name
		console.log(data.name + ' 進入了房間')
		console.log('當前用戶', users)
		// 發送進入房間的系統通知
		io.emit('sys', {
			text: socket.nickName + ' 進入了房間',
			count: users.length,
			users: users
		})
		// 發送登陸成功的通知
		socket.emit('login', {
			status: 'ok'
		})
	}
})
複製代碼

消息推送

接收用戶發送的信息後廣播給除發送用戶外的全部人

socket.on('message', function (data) {
	socket.broadcast.emit('message', data)
})
複製代碼

用戶斷開鏈接

socket.on('disconnect', function () {
	let index = users.indexOf(socket.nickName)
	if (index >= 0) users.splice(index, 1)
	// 用戶離開房間發送系統通知
	io.emit('sys', {
		text: socket.nickName + ' 離開了房間',
		count: users.length,
		users, users
	})
	console.log(socket.nickName + '離開了房間')
	console.log('當前用戶', users)
})
複製代碼

部署上線

項目雖小卻也是先後端分離的,因此做爲練習將項目的代碼分開部署到了不一樣的服務器。

NodeJS 服務端部署

後端代碼是部署到了阿里雲的香港服務器的,系統 CentOS 7。使用 Docker 運行了一個node環境的容器。

docker 中文手冊

阿里雲的服務器安全性很高,但也所以有超多的坑,主要注意如下幾點。

  • 服務器默認是關閉全部對外端口的,須要在控制檯中添加對應的安全組策略。
  • 本地ssh和FTP操做頻繁可能被拉入黑名單,須要在雲盾·DDoS高防IP中設置本地IP到白名單中
  • CentOS 7 默認防火牆從iptables換成了 FirewallD

一、在服務器中安裝好 ftpDocker,安裝方法谷歌有不少 二、使用 Docker 安裝 Node 鏡像

$ Docker pull node
複製代碼

三、運行容器並掛載本地目錄(容器中的全部數據都是緩存,因此對一些須要常常變更修改的文件直接掛載到本地目錄)

$ docker run -it -p 3000:3000 \
$ --mount type=bind,source=/home/www/chat,target=/home/www/chat \
$ node:latest \
$ /bin/bash
複製代碼

四、上傳代碼並進入容器運行

將代碼文件上傳到服務器 /home/www/chat中,而後進入容器。

  • index.js
  • package.json
$ npm install
$ node index.js
複製代碼

若是從容器退出了須要從新進入容器

# 容器ID能夠用 docker ps -a 查看
$ docker exec -it f9dd88d7f
# 進入容器中的項目目錄
$ cd /home/www/chat
# 安裝依賴
$ npm install
# 運行項目
$ node index.js
複製代碼

Web 前端部署

前端的代碼部署相對後端來講簡單不少,由於前端代碼只有一些HTML、CSS和JS等靜態的文件,隨便找個靜態服務器放就能夠。

對於此類項目這裏強烈推薦使用阿里雲的對象存儲OSS,超級便宜基礎版一年只須要9塊錢。

  1. 新建 Bucket 存儲塊
  2. 在域名管理中綁定域名
  3. 在基礎設置中設置靜態網站託管
  4. 打包上傳編譯後的前端文件

打包前端代碼

$ parcel build index.html
複製代碼

打包成功後將/dist目錄上傳到剛剛在阿里雲中創建的OSS存儲塊中,修改index.html目錄中的資源引用爲根目錄,並將index.html移動到根目錄,而後經過綁定的域名訪問便可

相關文章
相關標籤/搜索