本文,我们通过Egret和Node.js实现一个在线聊天室的demo。主要包括:聊天,改用户名,查看其他用户在线状态的功能。大致流程为,用户访问网页,即进入聊天状态,成为新游客,通过底部的输入框,可以输入自己想说的话,点击发布,信息呈现给所有在聊天的人的页面。用户可以实时修改自己的昵称,用户离线上线都会实时广播给其他用户。

体验链接 http://7hds.com:8888/

下图为最终制作完成的聊天面板

搭建Websocket简易聊天室

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进行快速开发。如下图:

搭建Websocket简易聊天室

首先创建一个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增加了客户端与服务器的互动,同时也实现了客户端之间的联系。