1 项目名称

   
Web聊天室(《那是NodeJs实战》第3章的一个案例,把整个开发进度记录下来)

json 1

 

2 项目描述

   
该品种是二个简单易行的在线聊天程序。打开聊天页面,程序自动给用户分配一个外号,进入暗中认可的Lobby聊天室。用户能够发送新闻,也足以行使聊天命令(聊天命令以/发轫)修改本身的别名只怕加入已某些聊天室(聊天室不设有时,创立新的聊天室)。在参预或创办聊天室时,新聊天室的名目会现身在闲聊程序顶端的档次条上,也会现出在闲谈音信区域左侧的可用房间列表中。在用户换成新房间后,系统会显示音信以确认这一转移。

3 系统规划

   
该品种选取Node完结,因为Node用二个端口就足以轻松地提供HTTP和WebSocket两种服务。使用HTTP处理静态文件的还要利用WebSocket实现实时数据(聊天音信)。程序的贯彻可以分开以下几个效能模块:

  1. 提供静态文件(比如HTML、CSS和客户端JavaScript)
  2. 在服务器上处理与聊天相关的信息
  3. 在用户的浏览器中处理与聊天相关的新闻

   
为了提供静态文件,要求利用Node内置的http模块。但透过HTTP提供文件时,经常不能够只是出殡和埋葬文书中的内容,还应有负有发送文书的花色。也便是说要用正确的MIME类型设置HTTP
头的Content-Type。为了探寻这一个MIME类型,会用到第②方的模块mime。

   
为了处理与聊天相关的音信,需求用Ajax轮询服务器。为了让那些顺序能尽大概快的作出响应,大家不会用古板的Ajax发送音信。采取WebSocket,那是一个为协助实时广播发表而布置的轻量的双向通讯协议。因为在大部情景下,只有包容HTML5的浏览器才支撑WebSocket,所以这么些顺序会动用流行的Socket.IO库,他给无法应用WebSocket的浏览器提供了一些后备措施。

4 系统贯彻

   
使用WebStorm开发该品种。WebStorm被称之为“最精锐的HTML5编辑器”、“最智能的JavaScript
IDE”。

4.1 创造程序的文本结构

  
使用WebStorm,选取七个索引,创制2个新的空项目。设计项目布局如下所示:

json 2

 

4.2 指明依赖项

   
程序的借助项是在package.json文件中指明的。这几个文件一而再被放在程序的根目录下。
package.json文件用于描述您的应用程序,它含有部分JSON表明式。在package.json文件中能够定义很多事务,但最重视的是先后的称呼、版本号、对程序的叙述,以及程序的依靠项。
代码清单第11中学是2个包描述文件,描述了种类的功能和依赖项。将以此文件保留到品种的根目录中,命名为package.json。

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

 

4.3 安装正视项

切换来DOS窗口,在档次的根目录下输入以下那条命令

npm install

一经依据失利,切换来国内的npm镜像,然后再设置。镜像使用方法(两种艺术任意一种都能解决难点,提议选择第一种,将布置写死,下次用的时候配置还在):

1.通过config命令

npm config set registry https://registry.npm.taobao.org

npm info underscore (假设上边配置不错,这么些命令会有字符串response)

2.命令行内定

npm –registry https://registry.npm.taobao.org info underscore

3.编辑 ~/.npmrc 到场上边内容

registry = https://registry.npm.taobao.org

 json 3

   
安装成功后,在根目录下创立的node_modules目录,这几个目录中放的正是程序的借助项。 

 json 4

4.4提供HTML、CSS和客户端 JavaScript的劳务

    程序的逻辑是由局地文书贯彻的,有些运营在服务器上,某个运行在客户端。
在客户端运行的JavaScript需求当作静态能源发给浏览器,而不是在Node上推行。

劳动器端的文件:

server.js

lib/chat_server.js

发送给客户端的文书:

public/index.html

public/stylesheets/style.css

public/javascripts/chat.js

public/javascripts/chat_ui.js

4.4.1 在server.js中提供静态文件服务器

/**

 * Created by Administrator on 2016-05-05.

 */

var http = require('http');

var fs = require('fs');

var path = require('path');

var mime = require('mime');

var cache={};//缓存文件内容的对象



  /* 请求的文件不存在时,发送404错误*, /

function send404(response){

    response.writeHead(404,{'Content-Type':'text/plain'});

    response.write('Error 404:resource not found.');

    response.end();

}

  /*  发送数据文件*/

function sendFile(response,filePath,fileContents){

 response.writeHead(200,{"Content-type":mime.lookup(path.basename(filePath))});

    response.end(fileContents);

}

  /*提供静态文件服务*/

function serverStatic(response,cache,absPath){

    if(cache[absPath]){

        sendFile(response, absPath,cache[absPath]);//从内存中返回数据

    }else{

        fs.exists(absPath,function(exists){//检查文件是否存在

              if(exists){

                fs.readFile(absPath,function(err,data){

                    if(err){

                        send404(response);

                    }else{

                        cache[absPath] = data;

                        sendFile(response,absPath,data);

                    }

                });

            }else{

                send404(response);

            }

        });

    }

}

  /* 1 创建HTTP服务器*,从该句代码开始阅读*/

var server= http.createServer(function(request,response){

    var filePath = false;

    if(request.url == '/'){

        filePath = 'public/index.html';

    }else{

        filePath = 'public' + request.url;

    }

    var absPath='./' + filePath;

    serverStatic(response,cache,absPath);

});

server.listen(3000,function(){

    console.log("server listening on port 3000.");

});
/* 2 加载chat_server,创建聊天服务器,chat_server 模块随后实现*/

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

chatServer.listen(server);

 

4.4.2添加HTML和CSS文件

    Index.html文件内容:

<!doctype html>

<html lang='en'>



<head>

    <title>Chat</title>

    <link rel='stylesheet' href='/stylesheets/style.css'></link>

</head>



<body>

<div id='content'>

    <div id='room'></div>

    <div id='room-list'></div>

    <div id='messages'></div>



    <form id='send-form'>

        <input id='send-message' />

        <input id='send-button' type='submit' value='Send'/>



        <div id='help'>

            Chat commands:

            <ul>

                <li>Change nickname: <code>/nick [username]</code></li>

                <li>Join/create room: <code>/join [room name]</code></li>

            </ul>

        </div>

    </form>

</div>



<script src='/socket.io/socket.io.js' type='text/javascript'></script>

<script src='http://code.jquery.com/jquery-1.8.0.min.js' type='text/javascript'></script>

<script src='/javascripts/chat.js' type='text/javascript'></script>

<script src='/javascripts/chat_ui.js' type='text/javascript'></script>

</body>

</html>

 

    style.css文件内容

body {

    padding: 50px;

    font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;

}

a {

    color: #00B7FF;

}



#content {

    width: 800px;

    margin-left: auto;

    margin-right: auto;

}

#room {

    background-color: #ddd;

    margin-bottom: 1em;

}

#messages {

    width: 690px;

    height: 300px;

    overflow: auto;

    background-color: #eee;

    margin-bottom: 1em;

    margin-right: 10px;

}

#room-list {

    float: right;

    width: 100px;

    height: 300px;

    overflow: auto;

}



#room-list div {

    border-bottom: 1px solid #eee;

}

#room-list div:hover {

    background-color: #ddd;

}

#send-message {

    width: 700px;

    margin-bottom: 1em;

    margin-right: 1em;

}

#help {

    font: 10px "Lucida Grande", Helvetica, Arial, sans-serif;

}

 

4.5用Socket.IO处理与聊天相关的成效

4.5.1 chat_server.js达成服务器端功用

var socketio = require('socket.io');

var io;

var guestNumber = 1;

var nickNames = {};

var namesUsed = [];

var currentRoom = {};

exports.listen = function(server){
//启动socket.io服务器,允许它搭载在已有的http服务器上

      io = socketio.listen(server);

    io.set('log level',1);
//定义每个用户连接的处理逻辑

    io.sockets.on('connection',function(socket){



        // 1 客户端连接后,分配用户昵称

          guestNumber=assignGuestName(socket,guestNumber,nickNames,namesUsed);

        // 2 加入Lobby聊天室

          joinRoom(socket,'Lobby');

        // 3 处理广播消息

          handleMessageBroadcasting(socket,nickNames);

        // 4处理修改昵称命令

          handleNameChangeAttempts(socket,nickNames,namesUsed);

        // 5 处理切换/创建聊天室命令

          handleRoomJoining(socket);

        // 6 当收到客户端请求后,发送给客户端房间列表

          socket.on('rooms',function(){

            socket.emit('rooms',io.sockets.manager.rooms);

        });

        // 7 处理客户端断开连接

          handleClientDisconnection(socket,nickNames,namesUsed);

    });

};

  /* 分配用户昵称*/

function assignGuestName(socket,guestNumber,nickNames,namesUsed){

    var name='Guest'+guestNumber;
//将昵称保存在昵称集合nickNames中

      nickNames[socket.id] = name;
//发送给客户端知悉其昵称

      socket.emit('nameResult',{ 

        success:true,

        name:name

    });
//将昵称保存在另一个已使用的昵称数组中

      namesUsed.push(name);

    return guestNumber + 1;

}





  /*加入房间*/

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);
//如果用户数量大于1

      if (usersInRoom.length > 1) {

        var usersInRoomSummary = 'Users currently in ' + room + ': ';

        for (var index in usersInRoom) {

            var userSocketId = usersInRoom[index].id;
//判断非当前用户

            if (userSocketId != socket.id) { 

                  if (index > 0) {

                    usersInRoomSummary += ', ';

                }

                usersInRoomSummary += nickNames[userSocketId];

            }

        }

        usersInRoomSummary += '.';

        //汇总该房间里的其它成员名称发送给给该用户

          socket.emit('message', {text: usersInRoomSummary});

    }

}

  /* 处理更名请求*/

function handleNameChangeAttempts(socket, nickNames, namesUsed) {

    socket.on('nameAttempt', function(name) {

        if (name.indexOf('Guest') == 0) {//不能以Guest开头

              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.'

                });

            }

        }

    });

}

  /*发送聊天消息,即Node服务器收到客户端消息,转发给该房间的其它用户*/

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];

    });

}

 

4.5.2 chat.js以及chat_ui.js实现客户端作用

   
客户端JavaScript须求达成以下功用:向服务器发送用户的音讯和别名/房间变更请求;
展现其余用户的新闻,以及可用房间的列表。Chat.js中定义二个原型对象,用于拍卖聊天消息和下令,该原型对象中的函数在chat_ui.js中调用。

chat.js文件内容:

/**

 * Created by Administrator on 2016-05-05.

 */

/*JavaScript原型对象,处理发送聊天消息、变更房间、处理聊天命令*/

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;

};

 

chat_ui.js文件内容:

/**

 * Created by Administrator on 2016-05-05.

 */
/*用来显示可疑的文本。它会净化文本,将特殊字符转换 成HTML实体*/

function divEscapedContentElement(message) {

    return $('<div></div>').text(message);

}

  /*显示系统创建的受信内容*/

function divSystemContentElement(message) {

    return $('<div></div>').html('<i>' + message + '</i>');

}

  /*显示用户输入的信息*/

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('');

}

  /*客户端程序初始化逻辑*/

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 {

            message = result.message;

        }

        $('#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();

        });

    });

    //每间隔1秒,向服务器重新请求房间列表

      setInterval(function() {

        socket.emit('rooms');

    }, 1000);



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

    //提交表单,发送聊天消息

      $('#send-form').submit(function() {

        processUserInput(chatApp, socket);

        return false;

    });

});

 

4.6 服务器与客户端的风浪分析

服务器与客户端的相互主假如经过互动发送事件-处管事人件实现的,以下是在全部工艺流程中生出的事件:

4.6.1服务器事件流程

‘connection’事件//接收客户端连接

{

    // 1 assignGuestName()函数中的事件

    socket.emit(‘nameResult’,{ //分配默许别名

        success:true,

        name:name

    });

 

   // 2 joinRoom()函数中的事件

   socket.emit(‘joinResult’, {room: room});//到场房间

   socket.broadcast.to(room).emit(‘message’, {//广播新闻

        text: nickNames[socket.id] + ‘ has joined ‘ + room + ‘.’

    });

   socket.emit(‘message’, {text:
usersInRoomSummary});//发送给每一种用户包蕴该房间的其余用户的列表

 

   // 3 handleMessage布罗兹casting()函数中的事件

   socket.on(‘message’, function (message)
{//收到客户端信息后,发射给同房间的别的用户

        socket.broadcast.to(message.room).emit(‘message’, {

            text: nickNames[socket.id] + ‘: ‘ + message.text

        });

    });

 

   // 4 handleNameChangeAttempts()函数中的事件

   socket.emit(‘nameResult’, {//更名

                    success: true,

                    name: name

                });

   socket.broadcast.to(currentRoom[socket.id]).emit(‘message’,
{//房间中别的用户知悉当前用户已更名

                    text: previousName + ‘ is now known as ‘ + name +
‘.’

                });

    // 5 handleRoomJoining()函数中的事件

    socket.on(‘join’, function(room) {//切换聊天室

        socket.leave(currentRoom[socket.id]);

        joinRoom(socket, room.newRoom);

    });

    // 6 当接到客户端请求后,发送给客户端房间列表

        socket.on(‘rooms’,function(){

            socket.emit(‘rooms’,io.sockets.manager.rooms);

        });

    // 7 handleClientDisconnection()函数中的事件

    socket.on(‘disconnect’, function() {

        var nameIndex = namesUsed.indexOf(nickNames[socket.id]);

        delete namesUsed[nameIndex];

        delete nickNames[socket.id];

    });

 

}

4.6.2客户端事件流程

// 1 连接服务器

var socket = io.connect();

// 2 接连服务器后,处理服务器发送过来的轩然大波

socket.on(‘nameResult’, function(result) {…});//展现小名

socket.on(‘joinResult’, function(result) {…});//显示插足房间

socket.on(‘message’, function (message) {…});//显示该房间的别的用户

socket.on(‘rooms’, function(rooms) {…});//呈现房间列表

setInterval(function() {

        socket.emit(‘rooms’);//定期向服务器请求房间列表

    }, 1000);

 

// 3 点击房间列表

$(‘#room-list div’).click(function() {

       this.socket.emit(‘join’, { newRoom: room});//切换聊天室

     });

 

// 4 点击提交按钮调用processUserInput()函数中触发的客户端事件

this.socket.emit(‘message’, message);//发送音讯

this.socket.emit(‘nameAttempt’, name);//更名

this.socket.emit(‘join’, { newRoom: room});//创造聊天室

相关文章

网站地图xml地图