黑马程序员技术交流社区

标题: 【广州前端】-Node.js + Web Socket 实现聊天程序(2) [打印本页]

作者: harryfun    时间: 2018-7-5 15:19
标题: 【广州前端】-Node.js + Web Socket 实现聊天程序(2)
设置昵称

我们要求连接的用户需要首先设置一个昵称,且这个昵称还要唯一,也就是不能与别人同名。一是方便用户区分,二是为了统计在线人数,同时也方便维护一个保存所有用户昵称的数组。

为此在后台server.js中,我们创建一个名叫users的全局数组变量,当一个用户设置好昵称发送到服务器的时候,将昵称压入users数组。同时注意,如果用户断线离开了,也要相应地从users数组中移除以保证数据的正确性。

在前台,输入昵称点击OK提交后,我们需要发起一个设置昵称的事件以便服务器侦听到。将以下代码添加到之前的init方法中。

www/scripts/hichat.js

[AppleScript] 纯文本查看 复制代码
//昵称设置的确定按钮  
document.getElementById('loginBtn').addEventListener('click', function() {  
    var nickName = document.getElementById('nicknameInput').value;  
    //检查昵称输入框是否为空  
    if (nickName.trim().length != 0) {  
        //不为空,则发起一个login事件并将输入的昵称发送到服务器  
        that.socket.emit('login', nickName);  
    } else {  
        //否则输入框获得焦点  
        document.getElementById('nicknameInput').focus();  
    };  
}, false);  


server.js

[AppleScript] 纯文本查看 复制代码
//服务器及页面部分  
var express = require('express'),  
    app = express(),  
    server = require('http').createServer(app),  
    io = require('socket.io').listen(server),  
    users=[];//保存所有在线用户的昵称  
app.use('/', express.static(__dirname + '/www'));  
server.listen(80);  
//socket部分  
io.on('connection', function(socket) {  
    //昵称设置  
    socket.on('login', function(nickname) {  
        if (users.indexOf(nickname) > -1) {  
            socket.emit('nickExisted');  
        } else {  
            socket.userIndex = users.length;  
            socket.nickname = nickname;  
            users.push(nickname);  
            socket.emit('loginSuccess');  
            io.sockets.emit('system', nickname); //向所有连接到服务器的客户端发送当前登陆用户的昵称   
        };  
    });  
});  


需要解释一下的是,在connection事件的回调函数中,socket表示的是当前连接到服务器的那个客户端。所以代码socket.emit('foo')则只有自己收得到这个事件,而socket.broadcast.emit('foo')则表示向除自己外的所有人发送该事件,另外,上面代码中,io表示服务器整个socket连接,所以代码io.sockets.emit('foo')表示所有人都可以收到该事件。

上面代码先判断接收到的昵称是否已经存在在users中,如果存在,则向自己发送一个nickExisted事件,在前端接收到这个事件后我们显示一条信息通知用户。

将下面代码添加到hichat.js的inti方法中。

www/scripts/hichat.js

[AppleScript] 纯文本查看 复制代码
this.socket.on('nickExisted', function() {  
     document.getElementById('info').textContent = '!nickname is taken, choose another pls'; //显示昵称被占用的提示  
});  

如果昵称没有被其他用户占用,则将这个昵称压入users数组,同时将其作为一个属性存到当前socket变量中,并且将这个用户在数组中的索引(因为是数组最后一个元素,所以索引就是数组的长度users.length)也作为属性保存到socket中,后面会用到。最后向自己发送一个loginSuccess事件,通知前端登陆成功,前端接收到这个成功消息后将灰色遮罩层移除显示聊天界面。

将下面代码添加到hichat.js的inti方法中。

www/scripts/hichat.js

[AppleScript] 纯文本查看 复制代码
this.socket.on('loginSuccess', function() {  
     document.title = 'hichat | ' + document.getElementById('nicknameInput').value;  
     document.getElementById('loginWrapper').style.display = 'none';//隐藏遮罩层显聊天界面  
     document.getElementById('messageInput').focus();//让消息输入框获得焦点  
});

在线统计

这里实现显示在线用户数及在聊天主界面中以系统身份显示用户连接离开等信息。

上面server.js中除了loginSuccess事件,后面还有一句代码,通过io.sockets.emit 向所有用户发送了一个system事件,传递了刚登入用户的昵称,所有人接收到这个事件后,会在聊天窗口显示一条系统消息'某某加入了聊天室'。同时考虑到在前端我们无法得知用户是进入还是离开,所以在这个system事件里我们多传递一个数据来表明用户是进入还是离开。

将server.js中login事件更改如下:

server.js

[AppleScript] 纯文本查看 复制代码
socket.on('login', function(nickname) {  
     if (users.indexOf(nickname) > -1) {  
         socket.emit('nickExisted');  
     } else {  
         socket.userIndex = users.length;  
         socket.nickname = nickname;  
         users.push(nickname);  
         socket.emit('loginSuccess');  
         io.sockets.emit('system', nickname, users.length, 'login');  
     };  
});  


较之前,多传递了一个login字符串。

同时再添加一个用户离开的事件,这个可能通过socket.io自带的disconnect事件完成,当一个用户断开连接,disconnect事件就会触发。在这个事件中,做两件事情,一是将用户从users数组中删除,一是发送一个system事件通知所有人'某某离开了聊天室'。

将以下代码添加到server.js中connection的回调函数中。

server.js

[AppleScript] 纯文本查看 复制代码
//断开连接的事件  
socket.on('disconnect', function() {  
    //将断开连接的用户从users中删除  
    users.splice(socket.userIndex, 1);  
    //通知除自己以外的所有人  
    socket.broadcast.emit('system', socket.nickname, users.length, 'logout');  
});

上面代码通过JavaScript数组的splice方法将当前断开连接的用户从users数组中删除,这里我们看到之前保存的用户索引被使用了。同时发送和用户连接时一样的system事件通知所有人'某某离开了',为了让前端知道是离开事件,所以发送了一个'logout'字符串。

下面开始前端的实现,也就是接收system事件。

在hichat.js中,将以下代码添加到init方法中。

www/scripts/hichat.js

[AppleScript] 纯文本查看 复制代码
this.socket.on('system', function(nickName, userCount, type) {  
     //判断用户是连接还是离开以显示不同的信息  
     var msg = nickName + (type == 'login' ? ' joined' : ' left');  
     var p = document.createElement('p');  
     p.textContent = msg;  
     document.getElementById('historyMsg').appendChild(p);  
     //将在线人数显示到页面顶部  
     document.getElementById('status').textContent = userCount + (userCount > 1 ? ' users' : ' user') + ' online';  
});

现在运行程序,打开多个浏览器标签,然后登陆离开,你就可以看到相应的系统提示消息了。

发送消息

用户连接以及断开我们需要显示系统消息,用户还要频繁的发送聊天消息,所以可以考虑将消息显示到页面这个功能单独写一个函数方便我们调用。为此我们向HiChat类中添加一个_displayNewMsg的方法,它接收要显示的消息,消息来自谁,以及一个颜色共三个参数。因为我们想系统消息区别于普通用户的消息,所以增加一个颜色参数。同时这个参数也方便我们之后实现让用户自定义文本颜色做准备。

将以下代码添加到的我的HiChat类当中。

www/scripts/hichat.js

[AppleScript] 纯文本查看 复制代码
//向原型添加业务方法  
HiChat.prototype = {  
    init: function() { //此方法初始化程序  
        //...  
    },  
    _displayNewMsg: function(user, msg, color) {  
        var container = document.getElementById('historyMsg'),  
            msgToDisplay = document.createElement('p'),  
            date = new Date().toTimeString().substr(0, 8);  
        msgToDisplay.style.color = color || '#000';  
        msgToDisplay.innerHTML = user + '<span class="timespan">(' + date + '): </span>' + msg;  
        container.appendChild(msgToDisplay);  
        container.scrollTop = container.scrollHeight;  
    }  
};  

在_displayNewMsg方法中,我们还向消息添加了一个日期。我们也判断了该方法在调用时有没有传递颜色参数,没有传递颜色的话默认使用#000即黑色。

同时修改我们在system事件中显示系统消息的代码,让它调用这个_displayNewMsg方法。

www/scripts/hichat.js

[AppleScript] 纯文本查看 复制代码
this.socket.on('system', function(nickName, userCount, type) {  
    var msg = nickName + (type == 'login' ? ' joined' : ' left');  
    //指定系统消息显示为红色  
    that._displayNewMsg('system ', msg, 'red');  
    document.getElementById('status').textContent = userCount + (userCount > 1 ? ' users' : ' user') + ' online';  
});

现在的效果如下:

有了这个显示消息的方法后,下面就开始实现用户之间的聊天功能了。

做法也很简单,如果你掌握了上面所描述的emit发送事件,on接收事件,那么用户聊天消息的发送接收也就轻车熟路了。

首先为页面的发送按钮写一个click事件处理程序,我们通过addEventListner来监听这个click事件,当用户点击发送的时候,先检查输入框是否为空,如果不为空,则向服务器发送postMsg事件,将用户输入的聊天文本发送到服务器,由服务器接收并分发到除自己外的所有用户。

将以下代码添加到hichat.js的inti方法中。

www/scripts/hichat.js

[AppleScript] 纯文本查看 复制代码
document.getElementById('sendBtn').addEventListener('click', function() {  
    var messageInput = document.getElementById('messageInput'),  
        msg = messageInput.value;  
    messageInput.value = '';  
    messageInput.focus();  
    if (msg.trim().length != 0) {  
        that.socket.emit('postMsg', msg); //把消息发送到服务器  
        that._displayNewMsg('me', msg); //把自己的消息显示到自己的窗口中  
    };  
}, false);  

在server.js中添加代码以接收postMsg事件。

server.js

[AppleScript] 纯文本查看 复制代码
io.on('connection', function(socket) {  
    //其他代码。。。  

    //接收新消息  
    socket.on('postMsg', function(msg) {  
        //将消息发送到除自己外的所有用户  
        socket.broadcast.emit('newMsg', socket.nickname, msg);  
    });  
});  

然后在客户端接收服务器发送的newMsg事件,并将聊天消息显示到页面。

将以下代码显示添加到hichat.js的init方法中了。

[AppleScript] 纯文本查看 复制代码
this.socket.on('newMsg', function(user, msg) { 

    that._displayNewMsg(user, msg);  
});

运行程序,现在可以发送聊天消息了。


点击查看更多精彩前端资源
点击有惊喜









欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2