本文,我們通過Egret和Node.js實現一個在線聊天室的demo。主要包括,聊天,改用戶名,查看其他用戶在線狀態的功能。大致流程為,用戶訪問網頁,即進入聊天狀態,成為新游客,通過底部的輸入框,可以輸入自己想說的話,點擊發布,信息呈現給所有在聊天的人的頁面。用戶可以實時修改自己的昵稱,用戶離線上線都會實時廣播給其他用戶。
下圖為最終制作完成的聊天面板

WebSocket服務器可以用其他語言編寫,本文采用的方法建立在Node.js上 。
在Node.js中我們使用ws第三方模塊來實現服務器業務邏輯的快速搭建,還需使用uuid模塊生成隨機id,你需要使用npm包管理器來安裝ws、uuid模塊。使用以下命令:
npm install ws -g npm install uuid -g
安裝完成之后,使用終端工具進入服務器目錄,開始編寫代碼:
//引入ws模塊 var WebSocket = require('ws'); //創建websocket服務,端口port為:**** var WebSocketServer = WebSocket.Server, wss = new WebSocketServer({port: 8180}); //引入uuid模塊 var uuid = require('node-uuid'); //定義一個空數組,存放客戶端的信息 var clients = []; //定義發送消息方法wsSend //參數為 type:類型 //client_uuid:隨機生成的客戶端id //nickname:昵稱 //message:消息 //clientcount:客戶端個數 function wsSend(type, client_uuid, nickname, message,clientcount) { //遍歷客戶端 for(var i=0; i<clients.length; i++) { //聲明客戶端 var clientSocket = clients[i].ws; if(clientSocket.readyState === WebSocket.OPEN) { //客戶端發送處理過的信息 clientSocket.send(JSON.stringify({ "type": type, "id": client_uuid, "nickname": nickname, "message": message, "clientcount":clientcount, })); } } } //聲明客戶端index默認為1 var clientIndex = 1; //服務端連接 wss.on('connection', function(ws) { //客戶端client_uuid隨機生成 var client_uuid = uuid.v4(); //昵稱為游客+客戶端index var nickname = "游客"+clientIndex; //client++ clientIndex+=1; //將新連接的客戶端push到clients數組中 clients.push({"id": client_uuid, "ws": ws, "nickname": nickname}); //控制台打印連接的client_uuid console.log('client [%s] connected', client_uuid); //聲明連接信息為 昵稱+來了 // var connect_message = nickname + " 來了"; var connect_message = " 來了"; //服務器廣播信息 ***來了 wsSend("notification", client_uuid, nickname, connect_message,clients.length); //當用戶發送消息時 ws.on('message', function(message) { // 用戶輸入"/nick"的話為重命名消息 if(message.indexOf('/nick') === 0) { var nickname_array = message.split(' '); if(nickname_array.length >= 2) { var old_nickname = nickname; nickname = nickname_array[1]; var nickname_message = "用戶 " + old_nickname + " 改名為: " + nickname; wsSend("nick_update", client_uuid, nickname, nickname_message,clients.length); } }//發送消息 else { wsSend("message", client_uuid, nickname, message,clients.length); } }); //關閉socket連接時 var closeSocket = function(customMessage) { //遍歷客戶端 for(var i=0; i<clients.length; i++) { //如果客戶端存在 if(clients[i].id == client_uuid) { // 聲明離開信息 var disconnect_message; if(customMessage) { disconnect_message = customMessage; } else { disconnect_message = nickname + " 走了"; } //客戶端數組中刪掉 clients.splice(i, 1); //服務廣播消息 wsSend("notification", client_uuid, nickname, disconnect_message,clients.length); } } } ws.on('close', function() { closeSocket(); }); process.on('SIGINT', function() { console.log("Closing things"); closeSocket('Server has disconnected'); process.exit(); }); });
服務器端主要是接收信息,判斷是聊天信息還是重命名信息,然后發送廣播。同時,當用戶連接上服務器端或者關閉連接時,服務器也會發送廣播通知其他用戶。
我們封裝了wsSend函數用來處理消息的廣播。對每個連接的用戶,我們默認給他分配為游客。為了實現廣播,我們用clients數組來保存連接的用戶。
將編寫好的文件保存為server.js,在終端工具中,使用node server.js來啟動你剛剛編寫的服務器。如果終端沒有報錯,證明你的代碼已經正常運行。
在實際項目中,服務器邏輯遠遠比此示例復雜得多。服務器端完成后,再來編寫客戶端代碼。
界面非常簡單,我們通過兩張圖片來實現界面效果,首先創建我們的聊天界面,此項目中為了方便我們使用EUI進行快速開發。如下圖:

首先創建一個Image來放置我們的背景圖。
創建三個Label對象,一個作為title:“多人在線聊天室”,一個作為提示:“當前在線人數”,還有一個id為lb_online的作為在線人數顯示文本。
創建一個EditableText對象id為input_msg作為消息發送輸入框,用戶可以在此輸入消息進行發送。
創建一個Button對象id為btn_ok,點擊按鈕可以執行發送消息動作。
創建界面的操作和WebSocket對象創建動作在同時進行,在init方法中創建WebSocket對象,並執行服務器連接操作,代碼如下:
public ws; private init() { /**WebSocket連接 */ this.ws = new WebSocket('ws://127.0.01:8180'); this.ws.onopen = function (e) { console.log('Connection to server opened'); } }
由於服務器開放了8180端口,我們也需要使用8180端口進行連接。當連接成功,可執行onopen方法。
服務器連接成功了,在控制台打印 'Connection to server opened'。
onmessage方法中讀取服務器傳遞過來的數據,並通過appendLog方法將數據顯示在對應的文本里,
使用newLabel方法並將一條新消息插入到消息框中。
private init() { /**WebSocket連接 */ this.ws = new WebSocket('ws://127.0.01:8180'); this.ws.onopen = function (e) { console.log('Connection to server opened'); } /**昵稱 */ var nickname; var self = this; this.ws.onmessage = function (e) { var data = JSON.parse(e.data); nickname = data.nickname; appendLog(data.type, data.nickname, data.message, data.clientcount); console.log("ID: [%s] = %s", data.id, data.message); //插入消息 self.group_msg.addChild(self.newLabel(data.nickname, data.message)) } function appendLog(type, nickname, message, clientcount) { console.log(clientcount) /**聊天信息 */ var messages = this.list_msg; /**提示 */ var preface_label; if (type === 'notification') { preface_label = "提示:"; } else if (type === 'nick_update') { preface_label = "警告:"; } else { preface_label = nickname; } self.preface_label = preface_label; var message_text = self.message_text = message; /**在線人數 */ self.lb_online.text = clientcount; } /**點擊OK發送 */ this.btn_ok.addEventListener(egret.TouchEvent.TOUCH_TAP, this.sendMessage, this); } private newLabel(name: string, msg: string) { var label1: eui.Label = new eui.Label(); label1.text = name + ":" + msg; label1.textColor = 0x000000 return label1; }
最后我們來編寫發送消息的函數,在btn_ok中egret.TouchEvent.TOUCH_TAP點擊之后的相應函數為sendMessage方法。
/**發送消息 */ private sendMessage() { var message = this.input_msg.text; if (message.length < 1) { // console.log("不能發送空內容!"); return; } this.ws.send(message); /**清空輸入框內容 */ this.input_msg.text = ""; }
如果輸入框中內容不為空的話就將數據通過 this.ws.send(message); 發送給服務器,並清除輸入框的內容。
最終運行后,我們就可以實現多人在線聊天功能了。
完整版代碼如下:
class Chat extends eui.Component implements eui.UIComponent { /**在線人數文本 */ public lb_online: eui.Label; /**聊天窗口 */ public scr_msg: eui.Scroller; /**聊天信息 */ public list_msg: eui.List; /**輸入框 */ public input_msg: eui.EditableText; /**確定按鈕 */ public btn_ok: eui.Button; /**聊天窗口消息組 */ public group_msg: eui.Group; public constructor() { super(); } protected partAdded(partName: string, instance: any): void { super.partAdded(partName, instance); } protected childrenCreated(): void { this.init(); super.childrenCreated(); } /**WebSocket */ public ws; public preface_label; public message_text; private init() { /**WebSocket連接 */ //線上測試鏈接,服務端代碼需在服務器啟動 //this.ws = new WebSocket('ws://7hds.com:8180'); this.ws = new WebSocket('ws://127.0.01:8180'); this.ws.onopen = function (e) { console.log('Connection to server opened'); } /**昵稱 */ var nickname; var self = this; this.ws.onmessage = function (e) { var data = JSON.parse(e.data); nickname = data.nickname; appendLog(data.type, data.nickname, data.message, data.clientcount); console.log("ID: [%s] = %s", data.id, data.message); //插入消息 self.group_msg.addChild(self.newLabel(data.nickname, data.message)) } function appendLog(type, nickname, message, clientcount) { console.log(clientcount) /**聊天信息 */ var messages = this.list_msg; /**提示 */ var preface_label; if (type === 'notification') { preface_label = "提示:"; } else if (type === 'nick_update') { preface_label = "警告:"; } else { preface_label = nickname; } self.preface_label = preface_label; var message_text = self.message_text = message; /**在線人數 */ self.lb_online.text = clientcount; } /**點擊OK發送 */ this.btn_ok.addEventListener(egret.TouchEvent.TOUCH_TAP, this.sendMessage, this); } /**發送消息 */ private sendMessage() { var message = this.input_msg.text; if (message.length < 1) { // console.log("不能發送空內容!"); return; } this.ws.send(message); /**清空輸入框內容 */ this.input_msg.text = ""; // console.log(this.ws.bufferedAmount); } private newLabel(name: string, msg: string) { var label1: eui.Label = new eui.Label(); label1.text = name + ":" + msg; label1.textColor = 0x000000 return label1; } }
本文的demo增加了客戶端與服務器的互動,同時也實現了客戶端之間的聯系。
