webSocket實現多人在線聊天
主要思路如下:
1.使用vue構建簡單的聊天室界面
2.基於nodeJs 的webSocket開啟一個socket后台服務,前端使用H5的webSocket來創建一個socket對象來鏈接后端的socket服務
3.后端開啟一個socket服務后,可以監聽到客戶端的鏈接,以及客戶端發送過來的消息;也可以主動給客戶端發送消息,例如:當有新的客戶端連接的時候,服務端主動給所有連接的客戶發消息,讓所有人都知道有新的用戶加入聊天室
4.前端new一個webSocket實例,去連接socket服務,然后客戶端和服務端就可以實現雙向通訊了,連接到后端服務之后,我們就可以向服務端發送消息了,並且也可以監聽到服務端發送過來的消息。
5.雙向通訊建立起來之后,可以根據功能需求來進行相應的代碼邏輯
一、使用vue構建一個簡單的項目,並開發一個簡單的聊天頁面
聊天頁面代碼,前端的主要邏輯都在這里 chating/index.vue
<template> <div class="chating"> <div class="chating-wrap"> <div class="title">聊天頁面</div> <div class="chating-content"> <div class="chating-body"> <div class="chating-list"> <ul class="chating-records"> <div :key="index" v-for="(item, index) in chatingRecords"> <li class="other" v-show="item.nickName != myNickName"> <img alt="用戶頭像" src="../../assets/logo.png" /> <div class="record-text-wrap"> <div class="nick-name">{{item.nickName}}</div> <div class="record-text">{{item.message}}</div> </div> </li> <li class="my" v-show="item.nickName == myNickName"> <div class="record-text-wrap"> <!-- <div class="nick-name">迷離</div> --> <div class="record-text">{{item.message}}</div> </div> <img alt="用戶頭像" src="../../assets/logo.png" /> </li> </div> </ul> </div> <div class="chating-btns"> <input class="input-text" placeholder="請輸入聊天內容" type="text" v-model="text" /> <button @click="sendData" class="send">發送</button> </div> </div> <div class="chating-online-number"> <div class="online-num">在線用戶{{userList.length}}</div> <ul v-if="userList.length > 0"> <li :key="index" class="user" v-for="(item, index) in userList"> <img alt="用戶頭像" src="../../assets/logo.png" /> <span>{{item.userName}}</span> </li> </ul> <button @click="loginOutHandler">退出群聊</button> </div> </div> </div> <div class="login" v-if="showLogin"> <div class="opacity-wrap"> <div> 用戶名: <input class="user-name" v-model="userName" /> </div> <button @click="loginHandler" class="login-btn">登錄</button> </div> </div> </div> </template> <script> export default { data () { return { text: '', socketUrl: 'ws://localhost:8888?userName=', // socket服務地址 client: null, // webSocket實例 chatingRecords: [], // 聊天記錄 myNickName: '', // 是否是自己 userName: '', showLogin: false, userList: [] } }, created () { console.log('created') // this.initChaing() }, mounted () { console.log('mounted') }, methods: { /* 初始化聊天,連接socket */ initChaing () { let that = this if (window.WebSocket) { /* webSocket 連接服務器 */ this.client = new WebSocket(this.socketUrl + this.myNickName) /* 監聽客戶端連接 */ this.client.onopen = function (ev) { if (ev.type === 'open') { console.log('客戶端連接socket服務') } } /* 監聽服務端發送的消息 */ this.client.onmessage = function (ev) { let data = JSON.parse(ev.data) /* 用戶在線信息接收的是一個jsony數組 */ if (data instanceof Array === true) { that.userList = data // 在線用戶數量變化 } else { /* 聊天信息接收的是一個json對象 */ that.chatingRecords.push(data) // 在線用戶聊天 } } /* 監聽服務端關閉 */ this.client.onclose = function (ev) { console.log('socket服務已關閉') that.client = null // 客戶端或者是服務端斷開后,將webSocket實例清除 } /* 監聽服務端異常 */ this.client.onerror = function () { if (!that.client) { console.log('socket服務連接失敗') } that.loginOutHandler() } } else { alert('該瀏覽器不支持webSocket,請使用主流瀏覽器,如chrome') } }, loginHandler () { this.myNickName = this.userName this.showLogin = false /* 登錄成功后再連接服務,是為了連接服務的時候把用戶信息發過去 */ this.initChaing() }, loginOutHandler () { this.client.close() this.client = null // 客戶端或者是服務端斷開后,將webSocket實例清除 this.$router.push('/') }, sendData () { if (!this.myNickName) { alert('請登錄') this.showLogin = true return } let data = { nickName: this.myNickName, uid: new Date().getTime(), message: this.text, date: new Date() } if (this.client) { this.client.send(JSON.stringify(data)) this.text = '' } else { console.log('socket服務連接失敗,正在重新連接服務..') this.initChaing() } } }, beforeDestroy () { this.client.close() } } </script> <style lang=""> .login { width: 100vw; height: 100vh; position: fixed; top: 0; left: 0; background: rgba(0, 0, 0, 0.6); display: flex; justify-content: center; align-items: center; } .opacity-wrap { width: 500px; height: 300px; background: #fff; display: flex; justify-content: center; align-items: center; flex-direction: column; } .user-name { font-size: 16px; padding: 5px; text-indent: 10px; } .login-btn { font-size: 20px; background: cornflowerblue; color: 20px; margin-top: 30px; color: #fff; border: none; outline: none; padding: 10px 20px; border-radius: 10px; } ul { list-style: none; margin: 0; padding: 0; } .chating { max-width: 800px; border: 20px solid lightcyan; border-radius: 20px; margin: 0 auto 0; } .title { background: cornflowerblue; color: #fff; padding: 5px 0 5px; } .chating-content { width: 100%; display: flex; justify-content: space-between; } .chating-body { flex: 1; display: flex; flex-direction: column; justify-content: space-between; background: #f3f3f3; } .chating-list { flex: 1; border: 1px solid cornflowerblue; } .chating-records { padding: 10px; min-height: 300px; max-height: 600px; overflow-y: auto; } .chating-records li { margin-bottom: 20px; } .chating-records .other { display: flex; justify-content: start; align-items: flex-start; } .chating-records .my { display: flex; justify-content: flex-end; align-items: center; } .chating-records img { width: 36px; height: 36px; /* border-radius: 50%; */ margin-right: 15px; background: purple; } .chating-records .my img { margin-right: 0; margin-left: 15px; } .chating-records .other .record-text-wrap { display: flex; flex-direction: column; align-items: flex-start; } .chating-records .my .record-text-wrap { display: flex; flex-direction: column; align-items: flex-end; } .nick-name { font-size: 14px; margin-bottom: 5px; color: #666; } .record-text { max-width: 260px; text-align: left; font-size: 14px; padding: 5px; background: #fff; border-radius: 5px; } .chating-btns { background: burlywood; padding: 10px; display: flex; align-items: center; justify-content: center; } .input-text { font-size: 16px; border: none; outline: none; padding: 5px 0 5px 5px; } .send { font-size: 16px; border: none; outline: none; padding: 4px 15px; margin-left: 20px; } .online-num { font-size: 12px; padding-bottom: 15px; } .chating-online-number { padding: 15px; height: 100%; } .chating-online-number ul { list-style: none; margin: 0; padding: 0; min-width: 120px; max-height: 580px; overflow-y: auto; } .user { display: flex; justify-content: space-between; align-content: center; line-height: 20px; font-size: 12px; border-bottom: 1px solid aqua; padding: 10px; margin-bottom: 5px; } .user img { width: 20px; height: 20px; border-radius: 50%; margin-right: 5px; background: palevioletred; } </style>
二、使用nodeJs搭建一個webSocket服務
webSocket.js文件
/* 首先在根目錄下安裝nodeJs的ws模塊:npm install ws */ /* 引入nodejs的webSocket模塊 */ var WebSocket = require('ws').Server var moment = require('moment') /* 創建一個webSocket實例 */ var wss = new WebSocket({ url: 'localhost', // webSocket服務的ip port: 8888 // webSocket服務的端口 }) /* socketId */ var id = 0 var onlineMemberList = [] var defaultUser = 'user' /* 監聽客戶端連接 */ wss.on('connection', function (ws, req) { id++ ws.id = id // 給每個連接的客戶端綁定一個id let reqUser = req.url.split('?')[1] let name = reqUser && reqUser.split('=')[1] let userName if (name) { /* 因為傳過來的名字可能是中文的,這里需要解碼,否則前端接收到的是一堆編碼后的字符串 */ userName = decodeURIComponent(name) } else { userName = defaultUser + id } var userInfo = { userName: userName, socketId: id, date: moment().format('MMMM Do YYYY, h:mm:ss a') } /* 當用戶名一樣的時候,表示重新登錄 */ for (var i = 0; i < onlineMemberList.length; i++) { if (userInfo.userName === onlineMemberList[i].userName) { onlineMemberList[i] = userInfo wss.clients.forEach(itemWs => { itemWs.send(JSON.stringify(onlineMemberList)) }) return } } onlineMemberList.push(userInfo) wss.clients.forEach(itemWs => { itemWs.send(JSON.stringify(onlineMemberList)) }) /* 監聽客戶端發過來的信息 */ ws.on('message', function (data) { console.log(data) let newData = JSON.parse(data) newData.serveDate = moment().format('MMMM Do YYYY, h:mm:ss a') /* 給所有連接的客戶端發送數據 */ wss.clients.forEach(itemWs => { itemWs.send(JSON.stringify(newData)) }) /* 給最后一個連接的客戶端發送數據 */ // ws.send(JSON.stringify(newData)) }) /* 監聽客戶端關閉 */ ws.on('close', function (ev) { console.log('客戶端斷開連接') /* 監聽到用戶斷開連接后,將在線的重新廣播給所有有人 */ onlineMemberList = onlineMemberList.filter(item => { return item.socketId !== ws.id }) wss.clients.forEach(itemWs => { itemWs.send(JSON.stringify(onlineMemberList)) }) console.log(onlineMemberList, 'onlineMemberList') console.log(ws.id, 'ws.id') }) /* 監聽客戶端發生異常 */ ws.on('error', function (ve) { console.log('客戶端異常') }) }) console.log('webSocket服務已開啟,端口為:8888')
為了方便管理代碼,我把webSocket服務的代碼放到了vue項目根目錄下的socketServe文件夾下面,在啟動vue項目之前先要來到這個文件夾里面開啟socket服務,才能在聊天界面連接到服務器
github地址:https://github.com/yanhuomili/chatingGroup