《Node.js實(shí)戰(zhàn)》閱讀筆記(二)

千里之行,始于足下元咙。第二章是實(shí)戰(zhàn)內(nèi)容:構(gòu)建多個(gè)房間的聊天室程序既们。
本章會(huì)構(gòu)建一個(gè)在線聊天程序占调,用戶可以在一個(gè)簡(jiǎn)單的表單中輸入消息,相互聊天丑慎,消息輸入后會(huì)發(fā)送給同一聊天室內(nèi)的其他所有用戶喜喂。

程序需求及初始設(shè)置

需求:進(jìn)入聊天室后,程序會(huì)自動(dòng)給用戶分配一個(gè)昵稱立哑,但他們可以用聊天命令修改自己的昵稱夜惭。聊天命令以斜杠(/)開(kāi)頭。同樣铛绰,用戶可以輸入命令創(chuàng)建新的聊天室(或加入已有的聊天室)诈茧。在加入或創(chuàng)建聊天室時(shí),新聊天室的名稱會(huì)出現(xiàn)在聊天程序頂端的水平條上捂掰,也會(huì)出現(xiàn)在聊天消息區(qū)域右側(cè)的可用房間列表中敢会。
將要?jiǎng)?chuàng)建的聊天程序需要完成如下任務(wù):

  • 提供靜態(tài)文件
  • 在服務(wù)器上處理與聊天相關(guān)的信息
  • 在用戶的瀏覽器中處理與聊天相關(guān)的消息曾沈。
    為了提供靜態(tài)文件,需要使用Node內(nèi)置的http模塊鸥昏。但通過(guò)HTTP提供文件時(shí)塞俱,通常不能只是發(fā)送文件中的內(nèi)容,還應(yīng)該有所發(fā)送文件的類型吏垮。也就是說(shuō)要正確的MIME類型設(shè)置HTTP頭的Content-Type障涯。為了查找這些MIME類型,你會(huì)用到第三方的模塊mime膳汪。
    為了處理與聊天相關(guān)的消息唯蝶,需要用Ajax輪詢服務(wù)器。但為了讓這個(gè)程序盡可能快地做出響應(yīng)遗嗽,我們不會(huì)用傳統(tǒng)的Ajax發(fā)送消息粘我。Ajax用HTTP做傳輸機(jī)制,并且HTTP本來(lái)就不是做實(shí)時(shí)通信的痹换。在用HTTP發(fā)送消息時(shí)征字,必須用一個(gè)新的TCP/IP連接。打開(kāi)和關(guān)閉連接需要時(shí)間娇豫。此外匙姜,因?yàn)槊看握?qǐng)求都要發(fā)送HTTP頭,所以傳輸?shù)臄?shù)據(jù)量也較大锤躁。這個(gè)程序沒(méi)用依賴于HTTP的方案搁料,而采用了WebSocket或详,這是一個(gè)為支持實(shí)時(shí)通訊而設(shè)計(jì)的輕量的雙向通信協(xié)議系羞。

創(chuàng)建程序的文件結(jié)構(gòu)

  • 創(chuàng)建如下的文件結(jié)構(gòu):


    文件結(jié)構(gòu)
  • 命令:

npm init --y
npm i socket.io mime --save-dev
  • 邏輯


    服務(wù)端與客戶端

創(chuàng)建靜態(tài)文件服務(wù)器

  • common.js引入
var http = require('http');
var fs = require('fs');
var path = require('path');
var mime = require('mime');
// cache 是用來(lái)緩存文件內(nèi)容的對(duì)象
var cache = {};
  • 發(fā)送文件數(shù)據(jù)及錯(cuò)誤響應(yīng)三個(gè)輔助函數(shù)
// 404處理
function send404(res) {
    res.writeHead(404, {
        'Content-Type': 'text/plain'
    })
    res.write('Error 404: resource not found.')
    res.end()
}
// 發(fā)送文件
function sendFile(res, filePath, fileContents){
    res.writeHead(200, {
                // mime2.0以上lookup更名為getType
        'Content-Type': mime.getType(path.basename(filePath))
    });
    res.end(fileContents);
}

訪問(wèn)內(nèi)存(RAM)要比訪問(wèn)文件系統(tǒng)快得多,所以Node程序通常會(huì)把常用的數(shù)據(jù)緩存到內(nèi)存里霸琴。我們的聊天程序就要把靜態(tài)文件緩存到內(nèi)存中椒振,只有第一次訪問(wèn)的時(shí)候才會(huì)從文件系統(tǒng)中讀取。下一個(gè)輔助函數(shù)就會(huì)確定文件是否緩存了梧乘,如果是澎迎,就返回它。如果文件還沒(méi)緩存选调,它會(huì)從硬盤中讀取并返回它夹供。如果文件不存在,則返回一個(gè)HTTP 404錯(cuò)誤作為響應(yīng)仁堪。如下:

// 提供靜態(tài)文件服務(wù)
function serveStatic(res, cache, absPath) {
    // 檢查文件是否緩存在內(nèi)存中
    if(cache[absPath]){
        // 從內(nèi)存中返回文件
        sendFile(res, absPath, cache[absPath]);
    } else {
        // 檢查文件是否存在
        fs.exists(absPath, function(exists){
            if(exists) {
                // 從硬盤中讀取文件
                fs.readFile(absPath, function(err, data){
                    if(err){
                        send404(res);
                    }else{
                        // 從硬盤中讀取文件并返回
                        cache[absPath] = data;
                        sendFile(res, absPath, data);
                    }
                })
            } else{
                // 發(fā)送HTTP 404 響應(yīng)
                send404(res);
            }
        });
    }
}
  • 創(chuàng)建HTTP服務(wù)器
// 創(chuàng)建HTTP服務(wù)器
var server = http.createServer(function(req, res){
    var filePath = false;
    if(req.url == '/'){
        // 確定返回的默認(rèn)HTML文件
        filePath = 'public/index.html'
    }else{
        // 將URL路徑轉(zhuǎn)為文件的相對(duì)路徑
        filePath = 'public' + req.url;
    }
    var absPath = './' + filePath;
    // 返回靜態(tài)文件
    serveStatic(res, cache, absPath);
});
  • 啟動(dòng)HTTP服務(wù)器
    現(xiàn)在已經(jīng)寫(xiě)好了創(chuàng)建代碼哮洽,但還沒(méi)添加啟動(dòng)它的邏輯。添加下面代碼弦聂,它會(huì)啟動(dòng)服務(wù)器鸟辅,要求服務(wù)器監(jiān)聽(tīng)TCP/IP端口3000氛什。
server.listen(3000, function() {
    console.log("Server listening on port 3000");
})

在命令行中輸入下面這條命令啟動(dòng):

node server.js

服務(wù)器運(yùn)行起來(lái)后,在瀏覽器中訪問(wèn)http://127.0.0.1:3000會(huì)激發(fā)404錯(cuò)誤輔助函數(shù)匪凉,頁(yè)面上會(huì)顯示“Error 404: resource not found枪眉。”
盡管你已經(jīng)添加了靜態(tài)文件處理邏輯再层,但還沒(méi)添加那些靜態(tài)文件贸铜。記住,在命令行中按下Ctrl-C可以停止正在運(yùn)行的服務(wù)器聂受。
接下來(lái)萨脑,讓我們把必須的靜態(tài)文件加上,把這個(gè)聊天程序的功能再向前推進(jìn)一步饺饭。

添加HTML和CSS文件

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <title>chat</title>
    <link rel="stylesheet" type="text/css" href="stylesheets/style.css">
</head>
<body>
    <div id="content">
        <div id="room">
            <div id="room-list"></div>
            <div id="message"></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>
    </div>
    <script src="./javascripts/jquery-1.11.0.min.js"></script>
    <script src="./javascripts/socket.io.js"></script>
    <script type="text/javascript" src="javascripts/chat.js"></script>
    <script type="text/javascript" src="javascripts/chat_ui.js"></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: #ddd;
    margin-bottom: 1em;
}
#message{
    width: 690px;
    height: 300px;
    overflow: auto;
    background: #eee;
    margin-bottom: 1em;
    margin-right: 10px;
}

這個(gè)程序還不能用渤早,但靜態(tài)文件已經(jīng)可以看了,基本的視覺(jué)布局也搭建好了瘫俊。把這些料理好了之后鹊杖,我們接下來(lái)去定義服務(wù)端聊天消息的分發(fā)。

用Socket.IO 處理與聊天相關(guān)的消息

我們前面說(shuō)過(guò)程序必須要做三件事扛芽,其中第一個(gè)提供靜態(tài)文件已經(jīng)做了骂蓖,現(xiàn)在來(lái)解決第二個(gè),處理瀏覽器和服務(wù)器之間的通信川尖。
Socket.IO為Node及客戶端JavaScript提供了基于WebSocket以及其他傳輸方式的封裝登下,它提供了一個(gè)抽象層。如果瀏覽器沒(méi)有實(shí)現(xiàn)WebSocket叮喳,Socket.IO會(huì)自動(dòng)啟用一個(gè)備選方案被芳,而對(duì)外提供的API還是一樣的。
Socket.IO提供了開(kāi)箱即用的虛擬通道馍悟,所以程序不用把每條消息都向已連接的用戶廣播畔濒,而是只向那些預(yù)訂了某個(gè)通道的用戶廣播。
Socket.IO還是事件發(fā)射器(Event Emitter)的好例子锣咒。事件發(fā)射器本質(zhì)上是組織異步邏輯的一種很方便的設(shè)計(jì)模式侵状。

  • 設(shè)置Socket.IO服務(wù)器
    首先,把下面這兩行代碼添加到server.js中毅整。第一行加載一個(gè)定制的Node模塊趣兄,它提供的邏輯是用來(lái)處理基于Socket.IO的服務(wù)端聊天功能的。第二行啟動(dòng)Socket.IO服務(wù)器悼嫉,給它提供一個(gè)已經(jīng)定義好的HTTP服務(wù)器艇潭,這樣它就能跟HTTP服務(wù)器共享同一個(gè)TCP/IP端口:
// 設(shè)置socket.io服務(wù)器
var chatServer = require('./lib/chat_server.js');
chatServer.listen(server);

現(xiàn)在你要在lib目錄中創(chuàng)建一個(gè)新文件,chat_server.js。先把下面的變量聲明添加到這個(gè)文件中暴区。這些聲明讓我們可以使用Socket.IO闯团,并初始化了一些定義聊天狀態(tài)的變量:

var socketio = require('socket.io');
var io;
var guestNumber = 1;
var nickNames = {};
var namesUsed = [];
var currentRoom = ();
  • 確立連接邏輯
    定義聊天服務(wù)器函數(shù)listen。server.js中會(huì)調(diào)用這個(gè)函數(shù)仙粱。
    它啟動(dòng)Socket.IO服務(wù)器房交,限定Socket.IO向控制臺(tái)輸出的日志的詳細(xì)程度,并確定該如何處理每個(gè)接進(jìn)來(lái)的連接伐割。
// 啟動(dòng)socket.io服務(wù)器
exports.listen = function(server) {
    io = socketio.listen(server);
    io.set('log level', 1);
    // 定義每個(gè)用戶連接的處理邏輯
    io.sockets.on('connection', function(socket){
        // 在用戶連接上來(lái)時(shí)賦予其一個(gè)訪問(wèn)名
        guestNumber = assignGuestName(socket, guestNumber, nickNames, namesUsed);
        // 在用戶連接上來(lái)時(shí)把他放入聊天室Lobby
        joinRoom(socket, 'Lobby');
        // 處理用戶的消息候味,更名,以及聊天室的創(chuàng)建和變更
        handleMessageBroadcasting(socket, nickNames);
        handleNameChangeAttempts(socket, nickNames, namesUsed);
        handleRoomJoining(socket);
        // 用戶發(fā)出請(qǐng)求時(shí)隔心,向其提供已經(jīng)被占用的聊天室的列表
        socket.on('rooms', function() {
            socket.emit('rooms', io.sockets.manager.rooms);
        });
        // 定義用戶斷開(kāi)連接后的清除邏輯
        handleClientDisconnection(socket, nickNames, namesUsed);
    })
}
  • 處理程序場(chǎng)景及事件
    聊天程序需要處理下面這些場(chǎng)景和事件:
    ? 分配昵稱白群;
    ? 房間更換請(qǐng)求;
    ? 昵稱更換請(qǐng)求硬霍;
    ? 發(fā)送聊天消息帜慢;
    ? 房間創(chuàng)建;
    ? 用戶斷開(kāi)連接唯卖。
    要實(shí)現(xiàn)這些功能得添加幾個(gè)輔助函數(shù)粱玲,如下文所述。
    ? 分配昵稱拜轨;
    要添加的第一個(gè)輔助函數(shù)是assignGuestName抽减,用來(lái)處理新用戶的昵稱。當(dāng)用戶第一次連到聊天服務(wù)器上時(shí)橄碾,用戶會(huì)被放到一個(gè)叫做Lobby的聊天室中卵沉,并調(diào)用assignGuestName給他們分配一個(gè)昵稱,以便可以相互區(qū)分開(kāi)法牲。
    程序分配的所有昵稱基本上都是在Guest后面加上一個(gè)數(shù)字史汗,有新用戶連進(jìn)來(lái)時(shí)這個(gè)數(shù)字就會(huì)往上增長(zhǎng)。用戶昵稱存在變量nickNames中以便于引用皆串,并且會(huì)跟一個(gè)內(nèi)部socket ID關(guān)聯(lián)淹办。昵稱還會(huì)被添加到namesUsed中眉枕,這個(gè)變量中保存的是已經(jīng)被占用的昵稱恶复。把下面清單中的代碼添加到lib/chat_server.js中實(shí)現(xiàn)這個(gè)功能。
// 分配昵稱
function assignGuestName(socket, guestNumber, nickNames, namesUsed){
    // 生成新昵稱
    var name = 'Guest' + guestNumber;
    // 把用戶昵稱跟客戶端連接ID關(guān)聯(lián)上
    nickNames[socket.id] = name;
    // 讓用戶知道他們的昵稱
    socket.emit('nameResult', {
        success: true,
        name: name
    });
    // 存放已經(jīng)被占用的昵稱
    namesUsed.push(name);
    // 增加用來(lái)生成昵稱的計(jì)數(shù)器
    return guestNumber + 1;
}
  • 進(jìn)入聊天室
    要添加到chat_server.js中的第二個(gè)輔助函數(shù)是joinRoom速挑。這個(gè)函數(shù)如下所示谤牡,處理邏輯跟用戶加入聊天室相關(guān)。
// 進(jìn)入聊天室
function joinRoom(socket, room){
    // 讓用戶進(jìn)入房間
    socket.join(room);
    // 記錄用戶的當(dāng)前房間
    currentRoom[socket.id] = room;
    // 讓用戶知道他們進(jìn)入了新的房間
    socket.emit('joinResult', {
        room: room
    });
    // 讓房間里的其他用戶知道有新用戶進(jìn)入了房間
    socket.broadcast.to(room).emit('message', {
        text: nickNames[sockets.id] + 'has joined' + room + '.'
    });
    // 確定有哪些用戶在這個(gè)房間里
    var usersInRoom = io.socket.clients(room);
    // 如果不止一個(gè)用戶在這個(gè)房間姥宝,匯總下都是誰(shuí)
    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 += '.';
    // 將房間里的其他用戶的匯總發(fā)送給這個(gè)用戶
    socket.emit('message', {
        text: usersInRoomSummary
    })
}

將用戶加入Socket.IO房間很簡(jiǎn)單翅萤,只要調(diào)用socket對(duì)象上的join方法就行。然后程序就會(huì)把相關(guān)細(xì)節(jié)向這個(gè)用戶及同一房間中的其他用戶發(fā)送腊满。程序會(huì)讓用戶知道有哪些用戶在這個(gè)房間里套么,還會(huì)讓其他用戶知道這個(gè)用戶進(jìn)來(lái)了培己。

  • 處理昵稱變更需求
    如果用戶都用程序分配的昵稱,很難記住誰(shuí)是誰(shuí)胚泌。因此聊天程序允許用戶發(fā)起更名請(qǐng)求省咨。更名需要用戶的瀏覽器通過(guò)socket.io發(fā)送一個(gè)請(qǐng)求,并接收表示成功或失敗的響應(yīng)玷室。
    將下面代碼清單中的代碼加到lib/chat_server.js中零蓉,這段代碼定義了一個(gè)處理用戶更名請(qǐng)求的函數(shù)。從程序的角度來(lái)講穷缤,用戶不能將昵稱改成以Guest開(kāi)頭敌蜂,或改成其他已經(jīng)被占用的昵稱。
// 更名請(qǐng)求的處理邏輯
function handleNameChangeAttempts(socket, nickNames, namesUsed){
    // 添加nameAttempt事件的監(jiān)聽(tīng)器
    socket.on('nameAttempt', function(name){
        if(name.indexOf('Guest') == 0){
            socket.emit('nameResult', {
                success: false,
                message: 'Names cannot begin with "Guest".'
            });
        }else{
            if(namesUsed.indexOf(name) == -1){
                // 昵稱未被占用津肛,注冊(cè)昵稱
                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{
                // 昵稱被占用章喉,給客戶端發(fā)送錯(cuò)誤信息
                socket.emit('nameResult', {
                    success: false,
                    message: 'That name is already in use'
                })
            }
        }
    })
}
  • 發(fā)送聊天信息
    基本流程:用戶發(fā)射一個(gè)事件,表明消息是從哪個(gè)房間發(fā)出來(lái)的身坐,以及消息的內(nèi)容是什么囊陡,然后服務(wù)器將這條消息轉(zhuǎn)發(fā)給同一房間的所有用戶。
    將下面代碼加到lib/chat_server.js中掀亥,sockrtIo的broadcast函數(shù)是用來(lái)轉(zhuǎn)發(fā)消息的:
// 發(fā)送聊天消息
function handleMessageBroadcasting(socket) {
    socket.on('message', function(message){
        socket.broadcast.to(message.room).emit('message', {
            text: nickNames[socket.id] + ':' + message.text
        })
    })
}
  • 創(chuàng)建房間
    接下來(lái)要添加讓用戶加入已有房間的邏輯撞反,如果房間還沒(méi)有的話,則創(chuàng)建一個(gè)房間搪花。
    將下面的代碼添加到lib/chat_server.js文件中遏片,實(shí)現(xiàn)更換房間的功能。注意Socket.IO中l(wèi)eave方法的使用:
// 創(chuàng)建房間
function hanleRoomJoining(socket) {
    socket.on('join', function(room){
        socket.leave(currentRoom[socket.id]);
        joinRoom(socket, room.newRoom);
    })
}
  • 用戶斷開(kāi)連接
    最后還要把下面這段代碼添加到lib/chat_server.js文件中撮竿,當(dāng)用戶離開(kāi)聊天程序時(shí)吮便,從nickNames和namesUsed中移除用戶的昵稱:
function handleClientDisConnection(socket) {
    socket.on('disconnect', function() {
        var nameIndex = namesUsed.indexOf(nickNames[socket.id]);
        delete namesUsed[nameIndex];
        delete nickNames[socket.id];
    });
}

客戶端js配置

客戶端js需要實(shí)現(xiàn)以下功能:

  1. 向服務(wù)器發(fā)送用戶的消息和昵稱/房間變更請(qǐng)求;
  2. 顯示其他用戶的消息幢踏,以及可用房間的列表髓需。
  • 將消息和昵稱、房間變更請(qǐng)求傳給服務(wù)器
    要添加的第一段客戶端JavaScript代碼是一個(gè)JavaScript原型對(duì)象房蝉,用來(lái)處理聊天命令僚匆、發(fā)送消息、請(qǐng)求變更房間或昵稱搭幻。
    在public/javascripts目錄下創(chuàng)建一個(gè)chat.js文件咧擂,把下面的代碼放進(jìn)去。這段代碼相當(dāng)于定義了一個(gè)JavaScript“類”檀蹋,在初始化時(shí)可用傳入一個(gè)Socket.IO的參數(shù)socket:
var Chat = function(socket) {
    this.socket = socket;
}
// 發(fā)送聊天信息的函數(shù)
Chat.prototype.sendMessage = function(room, text){
    var message = {
        room: room,
        text: text
    };
    this.socket.emit('message', message);
}
// 變更房間的函數(shù)
Chat.prototype.changeRoom = function(room) {
    this.socket.emit('join', {
        newRoom: room
    })
}
// 處理聊天命令
Chat.prototype.processCommand = function(command){
    var words = command.split(' ');
    var command = words[0].sustring(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;
}
  • 在用戶界面中顯示消息及可用房間
    這個(gè)聊天程序會(huì)用兩個(gè)輔助函數(shù)顯示文本數(shù)據(jù)松申。一個(gè)函數(shù)用來(lái)顯示可疑的文本數(shù)據(jù),另一個(gè)函數(shù)顯示受信的文本數(shù)據(jù)。
    函數(shù)divEscapedContentElement用來(lái)顯示可疑的文本贸桶。它會(huì)凈化文本舅逸,將特殊字符轉(zhuǎn)換成HTML實(shí)體。函數(shù)divSystemContentElement用來(lái)顯示系統(tǒng)創(chuàng)建的受信內(nèi)容皇筛,而不是其他用戶創(chuàng)建的堡赔。
    在public/javascripts目錄下創(chuàng)建chat_ui.js文件,并把下面兩個(gè)輔助函數(shù)放進(jìn)去:
function divEscapedContentElement(message) {
    return $('<div></div>').text(message);
}

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

下一個(gè)要加到chat_ui.js中的函數(shù)是用來(lái)處理用戶輸入的设联。如果用戶輸入的內(nèi)容以斜杠(/)開(kāi)頭善已,它會(huì)將其作為聊天命令處理。如果不是离例,就作為聊天消息發(fā)送給服務(wù)器并廣播給其他用戶换团,并添加到用戶所在聊天室的聊天文本中。

// 處理原始的用戶輸入
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('');
}

輔助函數(shù)現(xiàn)在已經(jīng)定義好了宫蛆,你還需要添加下面這個(gè)代碼清單中的邏輯艘包,它要在用戶的瀏覽器加載完頁(yè)面后執(zhí)行。這段代碼會(huì)對(duì)客戶端的Socket.IO事件處理進(jìn)行初始化耀盗。

// 客戶端程序初始化邏輯
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));
            }
        }
        // 點(diǎn)擊房間名可以換到那個(gè)房間中
        $('#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(event) {
        processUserInput(chatApp, socket);
        return false;
    });
});

so 運(yùn)行~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末想虎,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子叛拷,更是在濱河造成了極大的恐慌舌厨,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件忿薇,死亡現(xiàn)場(chǎng)離奇詭異裙椭,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)署浩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門揉燃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人筋栋,你說(shuō)我怎么就攤上這事炊汤。” “怎么了弊攘?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵抢腐,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我肴颊,道長(zhǎng)氓栈,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任婿着,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘竟宋。我一直安慰自己提完,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布丘侠。 她就那樣靜靜地躺著徒欣,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蜗字。 梳的紋絲不亂的頭發(fā)上打肝,一...
    開(kāi)封第一講書(shū)人閱讀 51,562評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音挪捕,去河邊找鬼粗梭。 笑死,一個(gè)胖子當(dāng)著我的面吹牛级零,可吹牛的內(nèi)容都是我干的断医。 我是一名探鬼主播,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼奏纪,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼鉴嗤!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起序调,我...
    開(kāi)封第一講書(shū)人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤醉锅,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后发绢,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體荣挨,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年朴摊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了默垄。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡甚纲,死狀恐怖口锭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情介杆,我是刑警寧澤鹃操,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站春哨,受9級(jí)特大地震影響荆隘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜赴背,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一椰拒、第九天 我趴在偏房一處隱蔽的房頂上張望晶渠。 院中可真熱鬧,春花似錦燃观、人聲如沸褒脯。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)番川。三九已至,卻和暖如春脊框,著一層夾襖步出監(jiān)牢的瞬間颁督,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工浇雹, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留沉御,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓箫爷,卻偏偏與公主長(zhǎng)得像嚷节,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子虎锚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容