构建有多个房间的聊天室程序

1. 程序概览

  • 用户可以在一个简单的表单中输入消息,相互聊天。消息输入后会发送给同一个聊天室内的其他所有用户。

这里写图片描述

  • 进入聊天室后,程序会自动给用户分配一个昵称,但他们可以用聊天命令修改自己的昵称,如图2-2所示。聊天命令以斜杠(/)开头。

这里写图片描述

  • 同样,用户也可以输入命令创建新的聊天室(或加入已有的聊天室) ,如图2-3所示。在加入或创建聊天室时,新聊天室的名称会出现在聊天程序顶端的水平条上,也会出现在聊天消息区域右侧的可用房间列表中。

这里写图片描述

  • 在用户换到新房间后,系统会确认这一变化,如图2-4所示。

这里写图片描述

2. 程序需要及初始设置

  • 提供静态文件(比如HTML、 CSS和客户端JavaScript);
  • 在服务器上处理与聊天相关的消息;
  • 在用户的浏览器中处理与聊天相关的消息。

  • 第三方的模块mime

  • WebSocket 这是一个为支持实时通讯而设计的轻量的双向通信协议。
  • Socket.IO库 它给不能使用WebSocket的浏览器提供了一些后备措施,包括使用Flash。

  • Express

2.1 提供HTTP和WebSocket服务

这里写图片描述

2.2 创建程序的文件结构

  • 项目目录:主程序文件会直接放在这个目录下
  • lib子目录:用来放一些服务端逻辑
  • public子目录: 用来放客户端文件
  • 在public子目录下,创建一个javascripts子目录和一个stylesheets目录。

这里写图片描述

2.2.3 指明依赖项

程序的依赖项是在package.json文件中指明的。这个文件总是被放在程序的根目录下。在package.json文件中可以定义很多事情,但最重要的是程序的名称、版本号、对程序的描述,以及程序的依赖项。

{
  "name": "chatrooms",
  "version": "0.0.1",
  "description": "Minimalist multiroom chat server",
  "dependencies": {
    "socket.io": "~0.9.6",
    "mime": "~1.2.7"
  }
}

2.2.4 安装依赖项

在跟目录输入下面这条命令:

npm install

这里写图片描述

2.3 提供 HTML、 CSS 和客户端 JavaScript 的服务

这里写图片描述

2.3.1 创建静态文件服务器

创建静态文件服务器既要用到Node内置的功能,也要用第三方的mime附加模块来确定文件
的的MIME类型。

这里写图片描述

  1. 发送文件数据及错误响应

接下来要添加三个辅助函数以提供静态HTTP文件服务。第一个是在所请求的文件不存在时
发送404错误的。把下面的辅助函数加到server.js中:

function send404(response) {
    response.writeHead( 404, { 'Content-Type': 'text/plain' } ) ;
    response.write( 'Error 404: resource not found.' ) ;
    response.end() ;
}

第二个辅助函数提供文件数据服务。这个函数先写出正确的HTTP头,然后发送文件的内容。
把下面的代码添加到server.js中:

function sendFile(response, filePath, fileContents) {
    response.writeHead(
        200,
        { 'Content-type': mime.lookup( path.basename( filePath ) ) }
    ) ;
    response.end( fileContents ) ;
}

访问内存(RAM)要比访问文件系统快得多,所以Node程序通常会把常用的数据缓存到内
存里。我们的聊天程序就要把静态文件缓存到内存中,只有第一次访问的时候才会从文件系统中
读取。下一个辅助函数会确定文件是否缓存了,如果是,就返回它。如果文件还没被缓存,它会
从硬盘中读取并返回它。如果文件不存在,则返回一个HTTP 404错误作为响应。把这个辅助函
数加到server.js中:

这里写图片描述

  1. 创建HTTP服务器

这里写图片描述

  1. 启动HTTP服务器
server.listen( 3000, function () {
    console.log( 'Server listening on port 3000.' ) ;
} ) ;

启动服务器:

node server.js

2.3.2 添加HTML和CSS文件

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

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

处理浏览器和服务器之间的通信。现代浏览器能用WebSocket处理浏览器跟服务器两者之间的通
信(参见Socket.IO浏览器支持页以了解详情: http://socket.io/#browser-support)。

  • 简要介绍下Socket.IO,并确定要在服务器端使用的Socket.IO功能;
  • 添加代码设置Socket.IO服务器;
  • 添加代码处理各种聊天程序的事件。

Socket.IO提供了开箱即用的虚拟通道,所以程序不用把每条消息都向已连接的用户广播,而
是只向那些预订了某个通道的用户广播。

Socket.IO还是事件发射器(Event Emitter)的好例子。事件发射器本质上是组织异步逻辑的
一种很方便的设计模式。

事件发射器:事件发射器是跟某种资源相关联的,它能向这个资源发送消息,也能从这个资源接收消息。
资源可以连接远程服务器,或者更抽象的东西,比如游戏中的角色。 Johnny-Five项目
https://github.com/rwldrn/johnny-five)是一个用Node做的机器人程序,实际上就是用事件发射
器控制Arduino微控制器。

2.4.1 设置Socket.IO服务器

server.js

var chatServer = require( './lib/chat_server' ) ;
chatServer.listen( server ) ;

现在你要在lib目录中创建一个新文件, chat_server.js。先把下面的变量声明添加到这个文件
中。这些声明让我们可以使用Socket.IO,并初始化了一些定义聊天状态的变量:

var socketio = require( 'socket.io' ) ;
var io ;
var guestNumber = 1 ;
var nickNames = {} ;
var namesUsed = [] ;
var currentRoom = {} ;
  • 确立建立连接

接下来添加代码清单2-7中的逻辑,定义聊天服务器函数listen。server.js中会调用这个函数。
它启动Socket.IO服务器,限定Socket.IO向控制台输出的日志的详细程度,并确定该如何处理每个
接进来的连接。

这里写图片描述

这里写图片描述

2.4.2 处理程序场景及事件

  • 分配昵称
  • 房间更换请求
  • 昵称更换请求
  • 发送聊天服务
  • 房间创建
  • 用户断开连接

  • 分配昵称

要添加的第一个辅助函数是assignGuestName,用来处理新用户的昵称。当用户第一次连到聊天服务器上时,用户会被放到一个叫做Lobby的聊天室中,并调用assignGuestName给他们分配一个昵称,以便可以相互区分开。
程序分配的所有昵称基本上都是在Guest后面加上一个数字,有新用户连进来时这个数字就会往上增长。用户昵称存在变量nickNames中以便于引用,并且会跟一个内部socket ID关联。昵称还会被添加到namesUsed中,这个变量中保存的是已经被占用的昵称。把下面清单中的代码添加到lib/chat_server.js中实现这个功能。

这里写图片描述

  • 进入聊天室

这里写图片描述

  • 处理昵称变更请求

这里写图片描述

  • 发送聊天消息
function handleMessageBroadcasting(socket) {
    socket.on( 'message', function (message) {
        socket.broadcast.to( message.room ).emit( 'message', {
            text: nickNames[ socket.id ] + ': ' + message.text
        } ) ;
    } ) ;
}
  • 创建房间
function handleRoomJoining(socket) {
    socket.on( 'join', function (room) {
        socket.leave( currentRoom[ socket.id ] )
        joinRoom( socket, room.newRoom ) ;
    } ) ;
}
  • 用户断开连接
function handleClientDisconnection(socket) {
    socket.on( 'disconnect', function () {
        var nameIndex = namesUsed.indexOf( nickNames[ socket.id ] ) ;
        delete namesUsed[ nameIndex ] ;
        delete nickNames[ socket.id ] ;
    } ) ;
}

2.5 在程序的用户界面上使用客户端 JavaScript

  • 向服务器发送用户的消息和昵称/房间变更请求
  • 显示其他用户的消息,以及可用房间的列表

2.5.2 将消息和昵称/房间变更请求传给服务器

要添加的第一段客户端JavaScript代码是一个JavaScript原型对象,用来处理聊天命令、发送
消息、请求变更房间或昵称。
在public/javascripts目录下创建一个chat.js文件,把下面的代码放进去。

/**
 * Created by 23782 on 2016/4/19.
 */
var Chat = function (socket) {
    this.socket = socket ;
}

// 发送消息
Chat.prototype.sendMessage = function (room, text) {
    var message = {
        room: room,
        text: text
    } ;
    this.socket.emit( 'message', message ) ;
}

// 变更房间
Chat.prototype.changeRoom = function (room) {
    this.socket.emit( 'join', {
        newRoom: room
    } ) ;
}

// 处理聊天命令
Chat.prototype.processCommand = function (command) {
    var words = command.split( ' ' ) ;
    var command = words[ 0 ].substring( 1, words[ 0 ].length ).toLowerCase() ;
    var message = false ;

    switch ( command ) {
        case 'join': {
            words.shift() ;
            var room = words.join( ' ' ) ;
            this.changeRoom( room ) ;
            break ;
        } ;
        case 'nick': {
            words.shift() ;
            var name = words.join( ' ' ) ;
            this.socket.emit( 'nameAttempt', name ) ;
            break ;
        }
        default: {
            message = 'Unrecognized command' ;
            break ;
        }
    }

    return message ;
}

2.5.2 在用户界面中显示消息及可用房间

现在该添加使用jQuery跟用户界面(基于浏览器)直接交互的逻辑了。要添加的第一个功能
是显示文本数据。;

从安全角度来看, Web程序中有两种文本数据。一种是受信的文本数据,由程序提供的文本组成,另一种是可疑的文本数据,是由程序的用户创建的文本,或从用户创建的文本中提取出来的。我们之所以认为来自用户的文本数据是可疑的,是因为恶意用户可能会蓄意在提交的文本数据中包含\

function divEscapedContentElement(message) {
    return $( '<div></div>' ).text( message ) ;
}

function divSystemContentElement(message) {
    return $( '<div></div>' ).html( '<i>' + message + '</i>' ) ;
}

下一个要加到chat_ui.js中的函数是用来处理用户输入的,具体内容见代码清单2-12。如果用
户输入的内容以斜杠(/)开头,它会将其作为聊天命令处理。如果不是,就作为聊天消息发送
给服务器并广播给其他用户,并添加到用户所在聊天室的聊天文本中。

// 处理原始的用户输入
function processUserInput(chatApp, socket) {
    var message = $( '#send-message' ).val() ;
    var systemMessage ;

    if ( message.charAt( 0 ) == '/' ) {
        systemMessage = chatApp.processCommand( message ) ;
        if ( systemMessage ) {
            $( '#messages' ).append( divSystemContentElement( systemMessage ) ) ;
        }
    } else {
        chatApp.sendMessage( $( '#room' ).text(), message ) ;
        $( '#messages' ).append( divEscapedContentElement( message ) ) ;
        $( '#messages' ).scrollTop( $( '#messages' ).prop( 'scrollHeight' ) ) ;
    }

    $( '#send-message' ).val( '' ) ;
}

辅助函数现在已经定义好了,你还需要添加下面这个代码清单中的逻辑,它要在用户的浏览器加载完页面后执行。这段代码会对客户端的Socket.IO事件处理进行初始化。

var socket = io.connect() ;

$( document ).ready( function () {
    var chatApp = new Chat( socket ) ;

    socket.on( 'nameResult', function (result) {
        var message ;

        if ( result.success ) {
            message = 'You are now known as ' + result.name + '. ' ;
        } else {
            messge = result.messge ;
        }
        $( '#messages' ).append( divSystemContentElement( message ) ) ;
    } ) ;

    socket.on( 'joinResult', function (result) {
        $( '#room' ).text( result.room ) ;
        $( '#messages' ).append( divSystemContentElement( 'Room changed.' ) ) ;
    } ) ;

    socket.on( 'message', function (message) {
        var newElement = $( '<div></div>' ).text( message.text ) ;
        $( '#messages' ).append( newElement ) ;
    } ) ;

    socket.on( 'rooms', function (rooms) {
        $( '#room-list' ).empty() ;

        for( var room in rooms ) {
            room = room.substring( 1, room.length ) ;
            if ( room != '' ) {
                $( '#room-list' ).append( divEscapedContentElement( room ) ) ;
            }
        }

        $( '#room-list div' ).click( function () {
            chatApp.processCommand( '/join' + $( this ).text() ) ;
            $( '#send-message' ).focus() ;
        } ) ;
    } ) ;

    setInterval( function () {
        socket.emit( 'rooms' ) ;
    }, 1000 ) ;

    $( '#send-message' ).focus() ;

    $( '#send-form' ).submit( function () {
        processUserInput( chatApp, socket ) ;
        return false ;
    } ) ;

} ) ;

接下来让我们把程序做完,将下面代码清单中的CSS样式代码添加到public/stylesheets/style.css文件中。

#room-list {
    float: right;
    width: 100px;
    height: 300px;
    overflow: auto;
}

#room-list div {
    border-bottom: 1px solid #eeeeee;
}

#room-list div:focus {
    background-color: #dddddd;
}

#send-message {
    width: 700px;
    margin-bottom: 1em;
    margin-right: 1em;
}

#help {
    font: 10px "Lucida Grande", Helvetica, Arial, sans-serif;
}

加好最后的代码,让我们把程序跑起来试试(用node server.js) 。

  • 个人效果图:

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

原创文章 27 获赞 18 访问量 3万+

猜你喜欢

转载自blog.csdn.net/b635781894/article/details/51200879
今日推荐