第二章补充

  2.4用Socket.IO处理与聊天相关的消息

  我们前面说过程序必须要做三件事,其中第一个提供静态文件已经做了,现在来解决第二个,处理浏览器和服务器之间的通信。现代浏览器能用WebSocket处理浏览器跟服务器两者之间的通信

  Socket.IO为Node及客户端JavaScript提供了基于WebSocket以及其他传输方式的封装,它提供了一个抽象层。如果浏览器没有实现WebSocket,Socket.IO会自动去自动一个备选方案,而外提供的API还是一样的。本节将会:

  1:简要介绍下Socket.IO,并确定要在服务器端使用的Socket.IO功能;

  2:添加代码设置Socket.IO服务器;

  3:添加代码处理各种聊天程序的事件;

  Socket.IO提供了开箱即用的虚拟通道,所有程序不用吧每条消息都向已连接的用户广播,而是只向那些预定了某个通道的用户广播。用这个功能实现程序里的聊天室功能非常简单。

  Socket.IO还是事件发射器的好例子。时间发射器本质上是组织异步逻辑的一种很方便的设计模式。本章中会有一些事件发射器的代码,但下一章才会做更深入的讨论。

  事件发射器是跟某种资源相关联的,它能向这个资源发送消息,也能从这个资源接收消息。

  我们先开始做服务器上的功能,并确立处理连接的逻辑。然后会定义服务器所需要的功能。

  2.4.1设置Socket.IO服务器

  在lib下创建chat._server.js:

//声明被提供使用的Socket.IO,并初始化部分定义聊天状态的变量
var socketio = require('socket.io');
var io;
var guestNumber = 1;
var nickNames = {};
var namesUsed = {};
var currentRoom = {};

//加载一个定制的Node模块,提供处理基于Socket.IO的服务端聊天功能的,暂未定义。
var chatServer = require('./lib/chat_server');
//启动Socket.IO服务器,给她提供一个已经定义好的HTTP服务器,跟HTTP服务器共享同一个TCP/IP端口
chatServer.listen(server);

exports.listen = function(server){
    //启动Socket.IO服务器允许它搭载在已有的HTTP服务器上
    io = socketio.listen(server);
    io.set('log level',1);
    //定义每个用户连接处理的逻辑
    io.sockets.on('connection',function(socket){
        //在用户连接上来时赋予一个访问名
        guestNumber = assignGuestName(socket, guestNumber, nickNames, namesUsed);
        //在用户连接上来时把他放入聊天室Lobby里
        joinRoom(socket,'Lobby');
        //处理用户的消息,更名,以及聊天室的创建和变更
        handleMessageBroadcasting(socket, nickNames);
        handleNameChangeAttempts(socket, nickNames, namesUsed);
        handleRoomJoining(socket);
        //用户发出请求时,向其提供已经被占用的聊天室的列表
        socket.on('rooms',function(){
                socket.emit('rooms', io.sockets.manager.rooms);
        });
        //定义用户断开连接后的清楚逻辑
        handleClientDisconnection(socket, nickNames, namesUsed);
    });
};

  已经确定了连接的处理逻辑,现在该添加用来处理程序需求的所有辅助函数了。

  

  2.4.2 处理程序场景及事件

  聊天程序需要处理下面这些场景和事件

  1:分配昵称

  2:房间更换请求

  3:昵称更换请求

  4:发送聊天消息

  5:房间创建

  6:用户断开连接

  要实现这些功能得添加几个辅助函数,如下文所述。

  1.分配昵称

  要添加的第一个辅助函数是assignGuestName,用来处理新用户的昵称。当用户第一次连接到聊天服务器上时,用户会被放到一个叫做Lobby的聊天室中,并调用assignGuestName给他们分配一个昵称,以便可以相互区分开来。

//分配用户昵称
function assignGuestName(socket, guestNumber, nickNames, namesUsed){
    //生成新的昵称
    var name = 'Guest' + guestNumber;
    //把用户的昵称跟客户端连接ID关联上
    nickNames[socket.id] = name;
    //让用户知道他们的昵称
    socket.emit('nameResult', {
        success:true,
        name:name
    });
    //存放已被占用的昵称
    namesUsed.push(name);
    //增加用来生产昵称的计数器
    return guestNumber + 1;
}

  程序分配的所有昵称基本上都是在Guest后面加上一个数字,,有新用户连接进来时这个数字就会增长。用户昵称存在变量nickNames中以便于引用,并且会跟一个内部socketID关联。昵称还会被添加到namesUsed中,这个变量中保存的是已经被占用的昵称。把下面清单中的代码添加到lib/chat_server.js中实现这个功能。

  2.进入聊天室相关的逻辑

  要添加到chat_server.js中的第二个辅助行数是joinRoom。处理逻辑跟用户加入聊天室有关。

  

//进入聊天室相关的逻辑
function joinRoom(socket, room){
    //让用户进入房间
    socket.join(room);
    //记录用户的当前房间
    currentRoom[socket.id] = room;
    //让用户知道他们进入了新的房间
    socket.emit('joinResult',{room: room});
    //让房间里的其他用户知道有新用户进入了房间
    socket.broadcast.to(room).emit('message',{
        text:nickNames[socket.id] + 'has joined' + room +'.'
    });
    //确定有哪些用户在这个房间里
    var usersInRoom = io.sockets.clients(room);
    //如果不止一个用户在这个房间里,汇总下有哪些用户
    if(usersInRoom.length > 1){
        var usersInRoomSummary = 'Userd currently in ' + room + ':';
        for(var index in userdInRoom){
            var userSocketId = usersInRoom[index].id;
            if(userSocketId != socket.id){
                if(index > 0){
                    usersInRoomSummary += ', ';
                }
                usersInRoomSummary +=nickNames[userSocketId];
            }
        }
        usersInRoomSummary += '.';
        //将房间里其他用户的汇总发送给这个用户
        socket.emit('message', {text:usersInRoomSummary});
    }
}

  调用socket对象上的join方法就可以将用户加入Socket.IO房间。然后程序会吧相关的细节向这个用户及同一房间中的其他用户发送。程序会让用户知道有哪些用户在这个房间里,还会让其他用户知道这个用户进来了。

  3.处理昵称变更请求

  如果用户都用程序分配的昵称,很难记住谁是谁。因此聊天程序允许用户发起更名请求。更名需要用户的浏览器通过Socket.IO发送一个请求,并接收表示成功或失败的响应。

  以下定义了一个出来用户更名请求的函数,加入到lib/chat_server.js中,用户不能将你从改成以Guest开头,或改成其他已经被占用的昵称。

//更名请求的处理逻辑
function handleNameChangeAttempts(socket, nickNames, namesUsed){
    //添加nameAttempt事件的监听器
    socket.on('nameAttempt', function(name){
        //昵称不能以Guest开头
        if(name.indexOf('Guest') == 0){
            socket.emit('nameResult',{
                success : false,
                message: 'Names cannot begin with "Guest".'
            });
        }else{
            //如果昵称还没注册就允许注册
            if(namesUsed.indexOf(name) == -1){
                var previousName = nickNames[socket.id]
                var previousNameIndex = namesUsed.indexOf(previousName);
                namesUsed.push(name);
                nickNames[socket.id] = name;
                //删除之前用的昵称,让其他用户可以使用
                delete namesUsed[previousNameIndex];
                socket.emit('nameResult', {
                    success: true,
                    name:name
                });
                socket.broadcast.to(currentRoom[socket.id]).emit('message',{
                    text:previousName + 'is now known as ' + name + '.'
                });
            }else{
                //如果昵称已经被占用,则给客户端发送错误信息
                socket.emit('nameResult', {
                    success: false,
                    message: 'That name is already in use.'
                })
            }
        }
    });
}

  4.发送聊天消息

  用户昵称没问题了,现在需要加个函数处理用户发过了的聊天消息。基本流程是:用户发射一个事件,表明消息是从哪个房间发出来的,已经消息的内容是什么;然后服务器将这条消息,转发给同一房间的所有用户。

  将下面的代码加入到lib/chat_server.js中。Socket.IO的broadcast函数是用来转发消息的。

  

//发送聊天消息
function handleMessageBroadcasting(socket){
    socket.on('message',function(message){
        socket.broadcast.to(message.room).emit('message', {
            text: nickNames[socket.id] + ': ' + message.text
        });
    });
}

  5.创建房间

  如果还没有房间的话则创建一个房间,以下代码加入到lib/chat_server.js中,实现更好房间的功能。注意leave方法的使用。

//创建房间
function handleRoomJoining(socket){
    socket.on('join',function(room){
        socket.leave(currentRoom[socket.id]);
        joinRoom(socket, room.newRoom);
    });
}

  6.用户断开连接

  当用户离开聊天程序时,从NickNames和namesUsed中移除用户的昵称,将下面的代码加入到lib/chat_server.js中。

//用户断开连接
function handleClientDisconnection(socket){
    socket.on('disconnect',function(){
        var nameIndex = namesUsed.indexOf(nickNames[socket.id]);
        delete namesUsed(nameIndex);
        delete nickNames[socket.id];
    });
}

  服务端的逻辑已经做好了,现在可以回过头去继续做客户端的逻辑了。

猜你喜欢

转载自www.cnblogs.com/erfsfj-dbc/p/10226997.html
今日推荐