前言
我們知道服務器是一種應答模式,也就是說服務器只能被動提供服務,而不會主動推送信息給客戶端。
傳統網站爲了實現類似在線聊天的功能都是不斷的給服務器發送信息詢問是否有新消息也就是所謂的輪詢
。
這種方式有很明顯的弊端:大量耗費服務器內存和寬帶資源,因爲不停的請求服務器,很多時候 並沒有新的數據更新,因此絕大部分請求都是無效請求。
WebSocket 是 HTML5 開始提供的一種在單個 TCP 連接上進行全雙工通訊的協議。
瀏覽器和服務器建立了webSocket連接後就可持續連接並雙向數據傳輸。
瀏覽器可發送數據給服務器,服務器有信息後也可主動推送信息給瀏覽器
效果
項目地址:github地址
預覽地址:預覽地址
請用不同的瀏覽器測試兩個賬號
聊天功能
- 用戶上/下線時提示聊天組所有的成員用戶已上/下線
- 聊天室可發送圖片和表情進行聊天
- 仿製qq的聊天時間格式
實現
前端涉及到的依賴有react、antd、redux
以及braft-editor富文本編輯器
後端涉及到的依賴有koa
和nodejs-websocket。
這裏我只會介紹大致的邏輯,詳細的實現細節可以參考我的源碼。
流程:
- 後臺首先搭建websocket服務器
- 前後端建立websocket連接
- 前後端各自監聽消息事件
- 開始聊天
我使用的是nodejs-websocket來搭建的websocket服務
var ws = require("nodejs-websocket")
// Scream server example: "hi" -> "HI!!!"
var server = ws.createServer(function (conn) {
console.log("New connection")
//監聽瀏覽器發送過來的信息
conn.on("text", function (str) {
console.log("Received "+str)
conn.sendText(str)
})
//websocket連接關閉的事件
conn.on("close", function (code, reason) {
console.log("Connection closed")
})
}).listen(8081)
上面幾行代碼就已經在8081端口實現了一個websocket服務(nodejs-websocket
更詳細的api請參考官網)
瀏覽器與服務器建立websocket連接
//實例化websocket對象
const ws = new WebSocket("ws://"+window.location.hostname+":8081")
//建立連接時觸發
ws.onopen = function(){
//發送數據到服務器
ws.send("第一次建立連接")
}
//監聽服務器信息
ws.onmessage = function(event){
console.log(event.data)
}
注意:不管是服務器還是瀏覽器我們發送的信息都是字符串類型的數據
。對於其他類型的數據請參考nodejs-websocket官網
這裏我將服務器和瀏覽器發送的消息設置爲兩種類型:聊天消息、上線消息。
這樣在瀏覽器和服務器才能對不同的消息類型做不同的處理。
const msgType = {
onlineInfo: 0, //關於在線列表
chatInfo: 1 //關於聊天內容
}
用戶上線
- 瀏覽器第一次和服務器建立websocket連接時將用戶信息發送給服務器
- 服務器在收到消息後將用戶信息添加到在線列表中去,並在連接中保存當前用戶信息
- 服務器將用戶上線消息廣播到所有與服務器成功建立連接的瀏覽器(消息類型爲0)
- 瀏覽器收到服務器推送的消息後更新在線列表
這裏我只做了一個聊天室,所以這裏廣播到所有客戶端,如果想要實現多個聊天室或私人聊天室,只需將聊天目標作爲信息一起傳給服務器,服務器根據聊天目標去過濾廣播。
用戶聊天
- 前端將用戶聊天內容(可以是圖片、文字、表情)發送給服務器
- 服務器收到消息後將聊天內容保存到數據庫並廣播給所有客戶端(消息類型爲1)
- 瀏覽器收到服務器推送的消息後更新聊天列表
用戶下線
- 服務器的在close事件中監聽websocket連接關閉
- 用戶關閉瀏覽器
- 服務器將下線用戶彈出在線列表並廣播給所有客戶端(消息類型爲0)
- 瀏覽器收到服務器推送的消息後更新在線列表
用戶信息更改
- 用戶信息更改時,發送新用戶信息到服務器
- 服務器收到消息後更新當前用戶信息(包括數據庫的更新)
- 瀏覽器發現用戶信息更新後,重新請求聊天列表和聊天記錄
這裏我就不放具體的實現代碼了,建議先了解websocket
和nodejs-websocket
後再參考我的源碼。
之所以用到redux
是因爲我想在用戶登錄成功後就建立websocket
連接,所以放到了redux中去了。
另外我還仿製了qq聊天的時間格式,當聊天消息間隔3分鐘時,在消息上方顯示時間,這個時間我利用了moment
進行轉換
//處理時間
handleTime = (time, small) => {
if (!time) {
return ''
}
const HHmm = moment(time).format('HH:mm')
//不在同一年,就算時間差一秒都要顯示完整時間
if (moment().format('YYYY') !== moment(time).format('YYYY')) {
return moment(time).format('YYYY-MM-DD HH:mm:ss')
}
//判斷時間是否在同一天
if (moment().format('YYYY-MM-DD') === moment(time).format('YYYY-MM-DD')) {
return HHmm
}
//判斷時間是否是昨天。不在同一天又相差不超過24小時就是昨天
if (moment().diff(time, 'days') === 0) {
return `昨天 ${HHmm}`
}
//判斷時間是否相隔一週
if (moment().diff(time, 'days') < 7) {
const weeks = ['日', '一', '二', '三', '四', '五', '六']
return `星期${weeks[moment(time).weekday()]} ${HHmm}`
}
if (small) {
return moment(time).format('MM-DD HH:mm')
} else {
return moment(time).format('M月D日 HH:mm')
}
}