nodejs+express搭建多人聊天室步骤

前言

本文主要是笔者在学习node的时候,作为练手的一个小项目,花了几天空余时间,边码边写教程的一个过程。适用于对node理论知识看的多,实战少的同学,那么现在就让我们开始吧!

准备工作

新建一个文件夹 chatroom

在终端输入以下命令,按照步骤npm(没装过的去官网安装下node和npm)会自动给你生成一个package.json文件

安装express和socket.io

package.json文件如下:

安装express和socket.io

package.json自动新增依赖

因为我们使用express框架写后端服务,用socket.io(Socket.io实际上是WebSocket的父集,Socket.io封装了WebSocket和轮询等方法,他会根据情况选择方法来进行通讯。)来对客户端和服务端建立一个持久链接,便于通讯。

到这里准备工作进行的差不多了,下面我们开始一步步实现。

搭建web服务器

express创建服务

学过node同学应该不陌生,利用http.createServer就能简单的创建一个服务器,这次我们利用express来创建服务。在项目根目录创建一个app.js。

app.listen(3000,()=>{
console.log("server running at 127.0.0.1:3000"); // 代表监听3000端口,然后执行回调函数在控制台输出。
});

/**

  • app.get(): express中的一个中间件,用于匹配get请求,说的简单点就是node处理请求的路由,对于不同url请求,让对应的不同app.get()去处理
  • '/': 它匹配get请求的根路由 '/'也就是 127.0.0.1:3000/就匹配到它了
  • req带表浏览器的请求对象,res代表服务器的返回对象
    */
    app.get('/',(req,res)=>{
    res.redirect('/chat.html'); // express的重定向函数。如果浏览器请求了根路由'/',浏览器就给他重定向到 '127.0.0.1:3000/chat.html'路由中
    });

/**

  • 这里匹配到的是/chat.html就是上面重定向到的路径。
    */
    app.get('/chat.html',res)=>{
    fs.readFile(path.join(__dirname,'./public/chat.html'),function(err,data){ //读取文件,readFile里传入的是文件路径和回调函数,这里用path.join()格式化了路径。
    if(err){
    console.error("读取chat.html发生错误",err); //错误处理
    res.send('4 0 4'); //如果发生错误,向浏览器返回404
    } else {
    res.end(data); //这里的data就是回调函数的参数,在readFile内部已经将读取的数据传递给了回调函数的data变量。
    } //我们将data传到浏览器,就是把html文件传给浏览器
    })
    });

你们看了以后会说,这express框架看来也没那么简便啊,一个最简单的发送单页面的方法跟node自带http.createServer没太大区别饿,也挺麻烦的。从目前来看确实如此,我这不是为了让你们容易理解嘛~ express提供了一个非常强大的中间件,帮我们托管静态资源文件,下面我们就来实现:

代替原来的:

__dirname表示当前文件所在的绝对路径,所以我们使用path.join将app.js的绝对路径和public加起来就得到了public的绝对路径。用path.join是为了避免出现 ././public 这种奇怪的路径,express.static就帮我们托管了public文件夹中的静态资源。只要有 127.0.0.1:3000/XXX/AAA 的路径都会去public文件夹下找XXX文件夹下的AAA文件然后发送给浏览器。

现在再来看这段代码是不是简介了很多,具体了解app.use()干了什么的同学可以去这里

socket.io建立客户端和服务端的链接

创建完上面的服务后,我们需要把socket.io引用进来,让客户端和服务端建立长久链接。我们把app.js进行如下改造:

  • Created by ddvdd on 2018-02-07.
    */
    const express = require('express');
    const app = express(); // 创建express实例,赋值给app。
    const server = require('http').Server(app);
    const io = require('socket.io')(server); //将socket的监听加到app设置的模块里。这两句理解不了的可以去socket.io官网去看
    const path = require('path'); // 这是node的路径处理模块,可以格式化路径
  • server.listen(3000,()=>{
    console.log("server running at 127.0.0.1:3000"); // 代表监听3000端口,然后执行回调函数在控制台输出。
    });
    ...
    ...
    app.use('/','./public'))); //一句话就搞定。

    /socket/
    io.on('connection',(socket)=>{ //监听客户端的连接事件

    });

    o.on表示监听某个事件,该事件一发生,就触发回调函数。'connection‘就是一个事件名,它已经定义好了,只要用户连接上就会触发。现在app.js基本已经完成,我们在根目录执行:

    node app.js

    >

    现在访问http://127.0.0.1:3000/static/chat.html:

    哎?啥也没有。。。那不废话!我们都没url请求对应的静态资源!

    添加静态html

    我们在项目根目录创建public文件夹,public文件夹里面新建chat.html文件:

    聊天室 这是我们的聊天室

    现在我们刷新下页面,你看页面出现了:

    >

    到这里其实一个最简单的浏览器和web服务器协作的项目就已经完成,后面我们要不断完善页面,给服务器后端加业务功能来实现多人聊天室。

    基本功能实现

    登陆功能,我们需要一个用户名,(不需要密码),该用户名必须客户端服务器都有存储。每次传输信息基本都需要包括用户名,否则不知道是谁发的。

    群聊功能,我们需要分辨信息来己方和对方

    登陆功能实现

    login页面重构

    最基本的登陆界面由一个用户名输入框和登录按钮组成:

    聊天室

    简单的加点样式,静态页面就完成了,我们刷新下页面:

    login页面交互

    昨天下午写到一半。。。部门突然要去团建聚会,只能匆匆提交代码,草草了事。今天一大早来到公司继续给大家码

    废话不多说进入正题,登陆这块交互,当用户访问服务器并且成功登陆算一个在线登陆人数,每登陆一个用户,服务器都会把用户信息存入一个数组中,保存在服务器,这里要注意一点,服务器会对用户登陆的用户名进行校验,校验结果会返回给客户端,客户端通过校验结果,改变当前页面是否进入聊天页面。

    上面的服务器和客户端交互都是通过socket.io来实现通讯的,前端的业务交互我们这里就采用jquery来实现,在public文件夹下新建js文件夹,下载jquery-3.2.1.min.js、新建main.js。然后对chat.html引入需要的sdk:

    引入完sdk,我们对main的js添加登录功能:

    let socket = io.connect(url);

    //设置用户名,当用户登录的时候触发
    let setUsername = () => {

    username = $inputname.val().trim(); //得到输入框中用户输入的用户名

    //判断用户名是否存在
    if(_username) {
    socket.emit('login',{username: _username}); //如果用户名存在,就代表可以登录了,我们就触发登录事件,就相当于告诉服务器我们要登录了
    }
    else{
    alert('请输入用户名!');
    }
    };

    /前端事件/
    _$loginButton.on('click',function (event) { //监听按钮的点击事件,如果点击,就说明用户要登录,就执行setUsername函数
    setUsername();
    });

    /socket.io部分逻辑/
    socket.on('loginResult',(data)=>{
    /**

    • 如果服务器返回的用户名和刚刚发送的相同的话,就登录
    • 否则说明有地方出问题了,拒绝登录
      */
      if(data.code === 0) {
      // 登陆成功,切换至聊天室页面
      }
      else if(data.code ===1){
      alert('用户已登录!');
      }
      else{
      alert('登录失败!');
      }
      })

    });
    //app.js
    /**

    • Created by ddvdd on 2018-02-07.
      */
      const express = require('express');
      const app = express(); // 创建express实例,赋值给app。
      const server = require('http').Server(app);
      const io = require('socket.io')(server); //将socket的监听加到app设置的模块里。这两句理解不了的可以去socket.io官网去看
      const path = require('path'); // 这是node的路径处理模块,可以格式化路径

    const users = []; //用来保存所有的用户信息
    let usersNum = 0; //统计在线登录人数

    server.listen(3000,()=>{
    console.log("server running at 127.0.0.1:3000"); // 代表监听3000端口,然后执行回调函数在控制台输出。
    });

    /**

    • app.get(): express中的一个中间件,用于匹配get请求,说的简单点就是node处理请求的路由,对于不同url请求,让对应的不同app.get()去处理
    • '/': 它匹配get请求的根路由 '/'也就是 127.0.0.1:3000/就匹配到它了
    • req带表浏览器的请求对象,res代表服务器的返回对象
      */
      app.get('/',res)=>{
      res.redirect('/static/chat.html'); // express的重定向函数。如果浏览器请求了根路由'/',浏览器就给他重定向到 '127.0.0.1:3000/chat.html'路由中
      });

    /**

    • __dirname表示当前文件所在的绝对路径,所以我们使用path.join将app.js的绝对路径和public加起来就得到了public的绝对路径。
    • 用path.join是为了避免出现 ././public 这种奇怪的路径
    • express.static就帮我们托管了public文件夹中的静态资源。
    • 只要有 127.0.0.1:3000/XXX/AAA 的路径都会去public文件夹下找XXX文件夹下的AAA文件然后发送给浏览器。
      */
      app.use('/static',(socket)=>{ //监听客户端的连接事件

    socket.on('login',(data)=>{

    if(checkUserName(data)){
    socket.emit('loginResult',{code:1}); //code=1 用户已登录
    }
    else{
    //将该用户的信息存进数组中
    users.push({
    username: data.username,message: []
    });
    socket.emit('loginResult',{code:0}); //code=0 用户登录成功
    usersNum = users.length;
    console.log(用户${data.username}登录成功,进入ddvdd聊天室,当前在线登录人数:${usersNum});
    }

    });

    //断开连接后做的事情
    socket.on('disconnect',()=>{ //注意,该事件不需要自定义触发器,系统会自动调用
    usersNum = users.length;
    console.log(当前在线登录人数:${usersNum});
    });
    });
    //校验用户是否已经登录
    const checkUserName = (data) => {
    let isExist = false;
    users.map((user) => {
    if(user.username === data.username){
    isExist = true;
    }
    });
    return isExist;
    }

    上面代码大家需要了解以下几点:

    1. socket.on 表示监听事件,后面接一个回调函数用来接收emit发出事件传递过来的对象。
    2. socket.emit 用来触发事件,传递对象给on监听事件。
    3. 我们socket连接之后的监听触发事件都要写在io.on('connection')的回调里面,因为这些事件都是连接之后发生的,就算是断开连接的事件 disconnect 也是在连接事件中发生的,没有正在连接的状态,哪来的断开连接呢?
    4. 理解虽然服务器端只有app.js一个文件,但是不同的客户端连接后信息是不同的,所以我们必须要将一些公用的信息,比如说,储存所有登录用户的数组,所有用户发送的所有信息存储在外部,一定不能存储在connecion里

    效果展示:

    群聊功能实现

    写完简单的登录功能,现在我们来写这项目最重要的功能群聊。首先我们先来处理下页面,因为功能简单,所以不单独建立html来显示聊天室,就直接写在login页面,通过class名称的变化来切换登录后,聊天室的显示。

    聊天室页面重构

    下面我们对chat.html进行整改:

    聊天室