教你用Node.js、Socket.io和ExpressJS构建实时在线聊天应用
Node.js
魔王
某人
21人收藏 8440次学习

教你用Node.js、Socket.io和ExpressJS构建实时在线聊天应用

Node.js 的出现使得我们可以直接用JavaScript来构建后端程序。这个技术尤其适合实现实时的应用。本篇教程中,我们会探索如何构建一个在线聊天应用,我们会用到ExpressJS和Socket.io。

 

配置环境

二话不说,我们要先装上Node.js。如果你用的是Windows或Mac,可以直接到Nodejs.org下载安装包。如果用的是Linux,那最好按照这个Github 文章的指导。我们先不深究这个问题,如果你对安装有什么疑问,可以写在评论里,我会进行解答。

完成了Node.js的安装,你就看可以开始配置你的工作环境了。

  1. ExpressJS:负责服务端程序以及处理给用户的响应。
  2. Jade:本次的模板引擎。
  3. Socket.io:负责前后端的实时交互。

我们继续。在空目录里,我们创建一个package.json,并且在里面添加如下内容:

{

    "name": "RealTimeWebChat",

    "version": "0.0.0",

    "description": "Real time web chat",

    "dependencies": {

        "socket.io": "latest",

        "express": "latest",

        "jade": "latest"

    },

    "author": "developer"

}

 

打开命令行,在这个目录中运行

npm install

不一会儿,所有的依赖组件就都被下载到node_modules目录里去了。

开发后端

咱们会先从简单的服务器程序开始,先弄好程序的HTML页面,然后就开始最有意思的一块:实时交互在线聊天。

 

先用ExpressJS代码创建一个index.js:

var express = require("express");

var app = express();

var port = 3700;


app.get("/", function(req, res){

    res.send("成了!");

});


app.listen(port);

console.log("监听端口: " + port);

 

上面的代码,我们创建了一个在线应用并定义了它的端口,然后,我们注册了一个路由(Route),在这里,我们就注册一个最简单的GET路径,不需要任何参数。这个路由简单的负责发送一条文字信息给客户端。最后,监听端口,相当于程序上线。好了,我们来启动这个应用:

node index.js

 

服务开始运行,当你打开http://127.0.0.1:3700/ ,应该会看到:

成了!

 

好吧,光写上“成了!”可不成,我们要HTML。纯HTML不够方便,我们要用上模板引擎。Jade在这里是不错的选择,她与ExpressJS是很好的搭档。反正我在项目中一直是这么用的。

 

创建一个tpl目录,在其中建立一个page.jade文件,添加如下内容:

!!!

html

    head

        title= "实时在线聊天"

    body

        #content(style='width: 500px; height: 300px; margin: 0 0 20px 0; border: solid 1px #999; overflow-y: scroll;')

        .controls

            input.field(style='width:350px;')

            input.send(type='button', value='发送')

 

Jade的语法不算复杂,不过本着厚积薄发原则,推荐大家去看看jade-lang.com。要让Jade和ExpressJS好好合作,需要在后者添加如下设置:

app.set('views', __dirname + '/tpl');

app.set('view engine', "jade");

app.engine('jade', require('jade').__express);

app.get("/", function(req, res){

    res.render("page");

});

 

以上这些代码,就是告诉ExpressJS说,咱们的模板文件在哪,用的啥模板引擎。这样,用于处理模板代码的功能就加载上来了。一切就绪后,我们就可以用response对象的.render功能,把我们的Jade代码发送到客户端。

 

然而现在的输出并没有什么用;只是一个div元素(附加了id=’content’),之后,这个div将作为内容的载体,来显示要发送的内容和控制按钮(input和button)。

 

由于我们要使用外部的JavaScript文件来处理前端逻辑,我们要告诉ExpressJS去哪找这样的文件。

 

新建一个public目录,然后在index.js的.listen命令之前,加入下面一行代码:

app.use(express.static(__dirname + '/public'));

 

嗯,很好很好。现在我们有一个server(服务端程序)可以响应GET请求了。我们要开始将Socket.io整合进来了。我们要把这一行变变:

app.listen(port);

 

变为:

var io = require('socket.io').listen(app.listen(port));

 

上面的代码中,我们将ExpressJS服务传递给了Socket.io。我们的实时交互仍是用的这个端口。

 

继续前进,我们要开始写代码来从客户端接收信息,然后发送给所有人。每一个Socket.io应用都是从connection  Handler(事件处理函数,比如io.sockets.on(),下略)开始的,我们先来写一个:

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

    socket.emit('message', { message: '欢迎来聊天' });

    socket.on('send', function (data) {

        io.sockets.emit('message', data);

    });

});

 

其中名为“socket”的对象,也就是传递给handler(io.sockets.on)的那个,其实就是客户端的socket(套接字)。

 

形象一点说,就像是客户的浏览器,与你的服务器程序,中间有一个转接插头,可以互通。当连接成功建立,我们发送一条欢迎信息,同时我们bind(绑定)另外一个方法作为接收处理器。于是,客户端会发送一条带名字的信息,然后我们会捕捉这个’send’的信息,然后把这个信息,通过io.sockets.emit ,传到所有建立了socket的客户端上。

 

有了以上代码,我们的服务端就可以接收客户信息并转发出去了。那么我们把注意力转向前端。

开发前端

创建一个chat.js,把它放到public目录去,然后添加以下代码:

window.onload = function() {

    var messages = [];

    var socket = io.connect('http://localhost:3700');

    var field = document.getElementById("field");

    var sendButton = document.getElementById("send");

    var content = document.getElementById("content");


    socket.on('message', function (data) {

        if(data.message) {

            messages.push(data.message);

            var html = '';

            for(var i=0; i<messages.length; i++) {

                html += messages[i] + '<br />';

            }

            content.innerHTML = html;

        } else {

            console.log("出了一点问题:", data);

        }

    });


    sendButton.onclick = function() {

        var text = field.value;

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

    };


}

 

我们的代码用一个window.onload方法包起来,以确保执行的时候所有的标签和js全部加载完毕。接下来的几行里,我们创建一个数组,来存放所有的信息;一个socket对象,还有一些关于DOM对象的变量。

 

就像在后端做过的那样,我们要绑定一个事件方法用来处理套件字活动,这个事件叫做message。当这个事件发生,我们期望data对象将message属性带进来,我们就可以将message存储进变量,然后更新到html中去。

 

当然我们还处理了发送按钮的逻辑,很简单,获取input的内容然后emit一个’send’出去。

 

这时,如果你打开http://localhost:3700,会遇到一些错误。因为我们还没有在page.jade文件里更新这些必要的js文件调用:

head

    title= "Real time web chat"

    script(src='/chat.js')

    script(src='/socket.io/socket.io.js')

 

注意Socket.io会自动提供socket.io.js你无须手动下载这个文件。

 

好了,我们再运行一次node index.js,然后打开http://localhost:3700

 

你应该就看到闪耀的欢迎信息了。

 

如果你发送信息,就会显示在div中。你要是想试试到底灵不灵,打开两个不同的浏览器(当然不同标签也可以),然后加载这个程序。Socket.io最牛之处就是,即便你停止了NodeJS服务,它仍然可以工作。前端工作时,重启NodeJS服务,聊天也没事。

 

目前咱们的程序还远不完美,还要做一些调整。

精益求精

我们第一件要做的事情,就是给信息加一个身份。我们现在根本不知道哪条信息是谁发的。好消息是,我们并不需要弄Node.js代码,因为我们的服务端只是推送了data对象。

于是这里我们要加一个新属性给它,便于之后读出来用。我们先在page.jade中动手,加上一组新的input,用来填写发送者的名字。我们在div里作如下修改:

.controls

    | 您的姓名:

    input#name(style='width:350px;')

    br

    input#field(style='width:350px;')

    input#send(type='button', value='send')

 

然后,在code.js中:

window.onload = function() {


    var messages = [];

    var socket = io.connect('http://localhost:3700');

    var field = document.getElementById("field");

    var sendButton = document.getElementById("send");

    var content = document.getElementById("content");

    var name = document.getElementById("name");


    socket.on('message', function (data) {

        if(data.message) {

            messages.push(data);

            var html = '';

            for(var i=0; i<messages.length; i++) {

                html += '<b>' + (messages[i].username ? messages[i].username : 'Server') + ': </b>';

                html += messages[i].message + '<br />';

            }

            content.innerHTML = html;

        } else {

            console.log("有问题:", data);

        }

    });


    sendButton.onclick = function() {

        if(name.value == "") {

            alert("请输入你的名字!");

        } else {

            var text = field.value;

            socket.emit('send', { message: text, username: name.value });

        }

    };


}

 

我们修改了如下几项:

  1. 为姓名项input增加了一个变量。
  2. 更改了发送信息的内容。
  3. 增加了一个username属性,并传到服务端。

如果消息数量太多,我们还要加上滚动:

content.innerHTML = html;

content.scrollTop = content.scrollHeight;

 

注意上面这条不兼容IE7以下版本,其实无所谓,因为IE7已经淡出主流,但如果还是不放心,也可用jQuery:

$("#content").scrollTop($("#content")[0].scrollHeight);

 

还有就是,最好每发一条信息,清空客户端的输入框:

socket.emit('send', { message: text, username: name.value });

field.value = "";

 

最后一个影响体验的地方就是每次都要鼠标点击发送按钮,所以我们要监听Enter按下的事件,这里用到了jQuery:

$(document).ready(function() {

    $("#field").keyup(function(e) {

        if(e.keyCode == 13) {

            sendMessage();

        }

    });

});

 

其中sendMessage这个方法可以用如下方式连上:

sendButton.onclick = sendMessage = function() {

    ...

};

 

当然这里污染了全局变量,最好别这么做,这里只是方便测试。

总结

Node.js是一种相当牛的技术,给我们带来了数不清的助力和乐趣,特别是我们可以直接用纯JavaScript完成程序。你看,咱们就这么几行代码,就写出一个简单的在线聊天应用,是不是很开心?是不是很开心?

加入1KE学习俱乐部

1KE学习俱乐部是只针对1KE学员开放的私人俱乐部