聊天室入门实战(node,socket.io实现)--第一章(实现登录群聊功能)

项目已经部署,请访问 : "chat.mycollagelife.com"

这几天花时间写了一个聊天室的demo,实现了登录,用户名检测,群聊,单聊,图片发送等功能,这个系列博客会分为几章讲解,由浅入深,逐步优化,章节间关联性较大,建议从第一章开始阅读。由于水平有限,有说的不对的地方还请各位大佬们留言指正。有不清楚的地方也可以留言提问。该博客说的非常基础,请耐心看完

下面我贴几张完成后的项目图片:

登录界面:


登录检查:


群聊界面:



私聊界面:


该项目已经上传至Github   https://github.com/neuqzxy/chat  觉得可以的话给个星星吧吐舌头现在github上有original文件夹是我已经完成的项目,我会将这个系列博客写完后的项目也上传供大家参考。(注意一点的是,我已经完成的项目是用了ejs模板引擎,其实一点用处都没有,大家可以把chat.ejs当做chat.html,修改一下引用的js的路径就行了)



第一天 -- 实现简单的登录和群聊功能

这一章我们需要具备的能力有(具有jquery的简单操作,node以及express的简单操作,socket.io的大致了解的能力)在本章我也会尽量详细的讲解。如果只是想做前端或后台的功能,大家完全可以拷贝自己不做的那一部分。所以不会node的同学不用太担心。

先看看我们将要实现的样式吧


自己发的消息是蓝色的,别人的消息是红色的



好了下面我们开始我们的项目了


node.js环境配置

开始项目前必须要保证你的电脑已经安装好了node环境。win用户直接去node.js官网下载安装包即可安装,和QQ安装没什么差别,Mac和Linux用户如果想源码包安装的话也可

以参考我的这篇文章《ubuntu下配置node环境》。 安装好后输入

node -v
npm -v
应该可以看到版本号的。否则安装失败了。


先写简单demo出来

1. 新建一个文件夹叫 chat

2. 在终端输入 npm init  然后一直回车,他会自动给你生成一个package.json文件

3. 创建如下文件夹


之前说过,original项目中使用的是ejs引擎,这个项目中我只用HTML文件,这里main.js是前端js,app.js是node.js。

4. 下面我们安装express和socket.io,输入:

npm install express --save
npm install socket.io --save
这时发现多了一个node_modules文件夹,这是装第三方模块的文件夹,express就在里面,我们不用管这个文件夹

5. 打开socket.io官网,找到这么一段:






5.1 我们先写一个服务器运行一下,在app.js里写:

/**
 * Created by zhouxinyu on 2017/8/6.
 */
const express = require('express');  // const是ES6的语法,代表常量,准确来说就是指向不发生改变。如果不习惯就用var代替
const app = express();               // express官网就是这么写的就是用来创建一个express程序,赋值给app。如果不理解就当公式记住
const fs = require('fs');            // 这个是node的文件读取模块,用于读取文件
const path = require('path');        // 这是node的路径处理模块,可以格式化路径
app.listen(3000,()=>{                // ()=>是箭头函数,ES6语法,如果不习惯可以使用 function() 来代替 ()=>
    console.log("server running at 127.0.0.1:3000");       // 代表监听3000端口,然后执行回调函数在控制台输出。
});

/**
 * app.get(): express中的一个中间件,用于匹配get请求,所谓中间件就是在该轮http请求中依次执行的一系列函数。
 * '/': 它匹配get请求的根路由 '/'也就是 127.0.0.1:3000/就匹配到他了
 * (req,res): ES6语法的箭头函数,你暂时可以理解为function(req,res){}。
 * req带表浏览器的请求对象,res代表服务器的返回对象
 */
app.get('/',(req,res)=>{
    res.redirect('/chat.html');       // express的重定向函数。如果浏览器请求了根路由'/',浏览器就给他重定向到 '127.0.0.1:3000/chat.html'路由中
});


/**
 * 怕不理解,这里我使用的是ES5的语法,没有用到箭头函数
 * 这里匹配到的是/chat.html就是上面重定向到的路径。
 */
app.get('/chat.html',function (req,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文件传给浏览器
    })
});

这是最简单的一个发送单页面的方法了,打开浏览器输入127.0.0.1:3000就能够看到chat.html的渲染结果了。其实有更简单的方法。express提供了一个非常强大的中间件,帮我们托管静态资源文件,下面我们就来实现:

/**
 * Created by zhouxinyu on 2017/8/6.
 */
const express = require('express');  // const是ES6的语法,代表常量,准确来说就是指向不发生改变。如果不习惯就用var代替
const app = express();               // express官网就是这么写的就是用来创建一个express程序,赋值给app。如果不理解就当公式记住
const path = require('path');        // 这是node的路径处理模块,可以格式化路径
app.listen(3000,()=>{                // ()=>是箭头函数,ES6语法,如果不习惯可以使用 function() 来代替 ()=>
    console.log("server running at 127.0.0.1:3000");       // 代表监听3000端口,然后执行回调函数在控制台输出。
});

/**
 * app.get(): express中的一个中间件,用于匹配get请求,所谓中间件就是在该轮http请求中依次执行的一系列函数。
 * '/': 它匹配get请求的根路由 '/'也就是 127.0.0.1:3000/就匹配到他了
 * (req,res): ES6语法的箭头函数,你暂时可以理解为function(req,res){}。
 * req带表浏览器的请求对象,res代表服务器的返回对象
 */
app.get('/',(req,res)=>{
    res.redirect('/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('/',express.static(path.join(__dirname,'./public')));        //一句话就搞定。


5.2 实现到这一步之后,我们根据socket.io文档进行修改。

/**
 * Created by zhouxinyu on 2017/8/6.
 */
const express = require('express');  // const是ES6的语法,代表常量,准确来说就是指向不发生改变。如果不习惯就用var代替
const app = express();               // express官网就是这么写的就是用来创建一个express程序,赋值给app。如果不理解就当公式记住
const server = require('http').Server(app);
const path = require('path');        // 这是node的路径处理模块,可以格式化路径
const io = require('socket.io')(server);     //将socket的监听加到app设置的模块里。
server.listen(3000,()=>{                // ()=>是箭头函数,ES6语法,如果不习惯可以使用 function() 来代替 ()=>
    console.log("server running at 127.0.0.1:3000");       // 代表监听3000端口,然后执行回调函数在控制台输出。
});

/**
 * app.get(): express中的一个中间件,用于匹配get请求,所谓中间件就是在该轮http请求中依次执行的一系列函数。
 * '/': 它匹配get请求的根路由 '/'也就是 127.0.0.1:3000/就匹配到他了
 * (req,res): ES6语法的箭头函数,你暂时可以理解为function(req,res){}。
 * req带表浏览器的请求对象,res代表服务器的返回对象
 */
app.get('/',(req,res)=>{
    res.redirect('/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 的路径都会去public文件夹下找XXX文件然后发送给浏览器。
 */
app.use('/',express.static(path.join(__dirname,'./public')));        //一句话就搞定。

/*socket*/
io.on('connection',(socket)=>{              //监听客户端的连接事件
    
});
上面的引包部分不懂没关系,当公式记住就行,但是下面的监听一定要懂。 io.on表示监听某个事件,该事件一发生,就触发回调函数。’connection‘就是一个事件名,它已经定义好了,只要用户连接上就会触发,而之后我们触发自定义事件都需要使用 emit关键字

5.3 修改main.js和chat.html文件

chat.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>聊天室</title>
    <script src="JavaScripts/jquery-3.2.1.js"></script>
    <script src="JavaScripts/main.js"></script>
    <script src="/socket.io/socket.io.js"></script>
</head>
<body>
聊天室
</body>
</html>

main.js

$(function(){
    const url = 'http://127.0.0.1:3000';

    let socket = io.connect(url);       //let和const是ES6的语法,不习惯可以改成var

});

main.js创建了一个浏览器端的socket实例,我们可以通过这个socket来实现和服务器交互。


分析业务

这一章我们要实现的功能是登录,群聊功能,以及分辨信息来自己方和对方

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

该登录界面和对话界面是同一个页面,所以我们需要jquery控制页面中不同组件的显示。


补充知识

对于socket.io我们这章需要了解最少以下几点:

1. socket.on 表示监听事件,后面接一个回调函数用来接收出发事件传递过来的对象以及编写之后的逻辑。可以参考jquery中的on方法

2. socket.emit 可以参考node.js中的emit方法,用来触发事件:

socket.emit('a',data);
表示触发a事件,然后将对象data传递过去,(前提是我们监听了a这个事件,才能在on中接收到data,然后传递给on的回调函数)。

3. socket.broadcast.emit 广播式的触发事件,也就是所有的监听了a事件的都会被触发,这里有个坑,就是它不会触发给自己。也就是说他会触发除自己以外的所有监听这一事件的浏览器。这一点很多博客都没有说清楚。

4. 我们socket连接之后的监听触发事件都要写在

io.on('connection'
里面,因为这些事件都是连接之后发生的,就算是断开连接的事件 disconnect 也是在连接事件中发生的,没有正在连接的状态,哪来的断开连接呢?

5. 我们需要理解虽然服务器端只有app.js一个文件,但是不同的客户端连接后信息是不同的,也就是说,

io.on('connection'

里面操作的变量是内部变量,不同的客户端他的socket是不同的,所以我们必须要将一些公用的信息,比如说,储存所有登录用户的数组,所有用户发送的所有信息存储在外部,一定不能存储在connecion里,这一点一定要和第4点分清楚。


前端布局

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>聊天室</title>
    <script src="JavaScripts/jquery-3.2.1.js"></script>
    <script src="JavaScripts/main.js"></script>
    <script src="/socket.io/socket.io.js"></script>
    <style>
        html,body {
            background: #ccc;
            padding: 20px 0 20px 0;
        }
        h1 {
            text-align: center;
        }
        .login {
            text-align: center;
        }
        .chatinput {
            display: block;
            width: 100%;
            position: absolute;
            min-height: 30px;
            bottom: 0;
        }
        #content {
            height: auto;
        }
    </style>
</head>
<body>
<div id="loginbox">
    <h1>登录</h1>
    <hr>
    <div class="login">
        <lable>用户名:</lable><input type="text" id="name"><br><br>
        <input type="button" value="登录" id="loginbutton">
    </div>
</div>
<div style="display: none;" id="chatbox">
    <div id="content"></div>
    <input type="text" placeholder="saying somgthing" class="chatinput" id="chatinput">
</div>
</body>
</html>
这个就不细讲了,简单的布局,有两个box 登录界面显示,chatbox默认隐藏。效果如图:



编写登录逻辑

客户端登录

我们先解决用户登录的时候获取输入框中的用户名问题,然后将用户名保存下来,如果登录成功,这个用户名就一直要在聊天的时候用到。

/**
 * Created by zhouxinyu on 2017/8/6.
 */
$(function(){
    const url = 'http://127.0.0.1:3000';
    let _username = null;
    let _$inputname = $("#name");
    let _$loginButton = $("#loginbutton");
    let _$chatinput = $("#chatinput");

    let socket = io.connect(url);

    //设置用户名,当用户登录的时候触发
    let setUsername = function () {
        _username = _$inputname.val().trim();    //得到输入框中用户输入的用户名

        //判断用户名是否存在
        if(_username) {
            socket.emit('login',{username: _username});   //如果用户名存在,就代表可以登录了,我们就触发登录事件,就相当于告诉服务器我们要登录了
        }
    };
    
    
    
    /*前端事件*/
    _$loginButton.on('click',function (event) {    //监听按钮的点击事件,如果点击,就说明用户要登录,就执行setUsername函数
        setUsername();
    });
    
    _$inputname.on('keyup',function (event) {     //监听输入框的回车事件,这样用户回车也能登录。
        if(event.keyCode === 13) {                //如果用户输入的是回车键,就执行setUsername函数
            setUsername();
        }
    })

});
这里我们使用setUsername函数得到用户名并“告诉服务器”(就是触发登录事件)。


服务器端监听登录事件

/**
 * Created by zhouxinyu on 2017/8/6.
 */
const express = require('express');  // const是ES6的语法,代表常量,准确来说就是指向不发生改变。如果不习惯就用var代替
const app = express();               // express官网就是这么写的就是用来创建一个express程序,赋值给app。如果不理解就当公式记住
const server = require('http').Server(app);
const path = require('path');        // 这是node的路径处理模块,可以格式化路径
const io = require('socket.io')(server);     //将socket的监听加到app设置的模块里。

const users = [];                    //用来保存所有的用户信息
let usersNum = 0;

server.listen(3000,()=>{                // ()=>是箭头函数,ES6语法,如果不习惯可以使用 function() 来代替 ()=>
    console.log("server running at 127.0.0.1:3000");       // 代表监听3000端口,然后执行回调函数在控制台输出。
});

/**
 * app.get(): express中的一个中间件,用于匹配get请求,所谓中间件就是在该轮http请求中依次执行的一系列函数。
 * '/': 它匹配get请求的根路由 '/'也就是 127.0.0.1:3000/就匹配到他了
 * (req,res): ES6语法的箭头函数,你暂时可以理解为function(req,res){}。
 * req带表浏览器的请求对象,res代表服务器的返回对象
 */
app.get('/',(req,res)=>{
    res.redirect('/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 的路径都会去public文件夹下找XXX文件然后发送给浏览器。
 */
app.use('/',express.static(path.join(__dirname,'./public')));        //一句话就搞定。

/*socket*/
io.on('connection',(socket)=>{              //监听客户端的连接事件
    /**
     * 所有有关socket事件的逻辑都在这里写
     */
    usersNum ++;
    console.log(`当前有${usersNum}个用户连接上服务器了`);
    socket.on('login',(data)=>{
        //将该用户的信息存进数组中
        users.push({
            username: data.username,
            message: []
        });
        
        //然后触发loginSuccess事件告诉浏览器登陆成功了
        socket.emit('loginSuccess',data);   //将data原封不动的再发给该浏览器
    });
    
    //断开连接后做的事情
    socket.on('disconnect',()=>{          //注意,该事件不需要自定义触发器,系统会自动调用
        usersNum --;
        console.log(`当前有${usersNum}个用户连接上服务器了`);
    })
});

注意,这里我const的是一个users数组,因为数组是堆栈结构,其内部改变但是地址不变,所以可以使用const常量表示。

我们在监听到login事件的时候,在备份完该用户数据后,就触发了loginSuccess事件告诉浏览器,服务器端允许登录了。由于是在login事件之中发生的,所以要写在login时间里,不然你一连接到服务器他就会触发。接下来我们在客户端写loginSuccess的逻辑。

客户端响应登陆成功事件

    /*socket.io部分逻辑*/
    socket.on('loginSuccess',(data)=>{
        /**
         * 如果服务器返回的用户名和刚刚发送的相同的话,就登录
         * 否则说明有地方出问题了,拒绝登录
         */
        if(data.username === _username) {
            beginChat(data);
        }else {
            alert("用户名不匹配,请重试");
        }
    })

这是前端部分监听到服务器触发loginSuccess后干的事情,就是如果用户名匹配,就启动聊天界面。接下来我们写beginChat的内容


    let beginChat = function () {
        /**
         * 1.隐藏登录框,取消它绑定的事件
         * 2.显示聊天界面
         */
        $("#loginbox").hide('slow');
        _$inputname.off('keyup');
        _$loginButton.off('click');

        /**
         * 显示聊天界面,并显示一行文字,欢迎用户
         * 这里我使用了ES6的语法``中可以使用${}在里面写的变量可以直接被浏览器渲染
         */
        $(`<p>欢迎你${_username}</p>`).insertBefore($("#content"));
        $("#chatbox").show('slow');
    };
这里我们#content元素是专门用来存放聊天记录的。我们把欢迎字样放在顶部。这个 ``的语法如果看不懂百度一下,ES6很强大的功能,不是很难理解。
到这里,基本的登录功能就已经实现了。客户端源码:

/**
 * Created by zhouxinyu on 2017/8/6.
 */
$(function(){
    const url = 'http://127.0.0.1:3000';
    let _username = null;
    let _$inputname = $("#name");
    let _$loginButton = $("#loginbutton");
    let _$chatinput = $("#chatinput");

    let socket = io.connect(url);

    //设置用户名,当用户登录的时候触发
    let setUsername = function () {
        _username = _$inputname.val().trim();    //得到输入框中用户输入的用户名

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

    let beginChat = function () {
        /**
         * 1.隐藏登录框,取消它绑定的事件
         * 2.显示聊天界面
         */
        $("#loginbox").hide('slow');
        _$inputname.off('keyup');
        _$loginButton.off('click');

        /**
         * 显示聊天界面,并显示一行文字,欢迎用户
         * 这里我使用了ES6的语法``中可以使用${}在里面写的变量可以直接被浏览器渲染
         */
        $(`<p>欢迎你${_username}</p>`).insertBefore($("#content"));
        $("#chatbox").show('slow');
    };


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

    _$inputname.on('keyup',function (event) {     //监听输入框的回车事件,这样用户回车也能登录。
        if(event.keyCode === 13) {                //如果用户输入的是回车键,就执行setUsername函数
            setUsername();
        }
    });


    /*socket.io部分逻辑*/
    socket.on('loginSuccess',(data)=>{
        /**
         * 如果服务器返回的用户名和刚刚发送的相同的话,就登录
         * 否则说明有地方出问题了,拒绝登录
         */
        if(data.username === _username) {
            beginChat(data);
        }else {
            alert("用户名不匹配,请重试");
        }
    })

});

现在,打开浏览器输入127.0.0.1:3000试一下吧,是不是可以实现登录了呢



编写聊天部分逻辑

聊天一定是客户端触发的,所以发送信息是客户端触发,服务器监听。
和登录类似,服务器监听到发送信息的事件后会存储信息,然后触发发送信息成功事件给所有客户端,将信息传给所有客户端。

我们先写浏览器sendMessage事件


    let sendMessage = function () {
        /**
         * 得到输入框的聊天信息,如果不为空,就触发sendMessage
         * 将信息和用户名发送过去
         */
        let _message = _$chatinput.val();

        if(_message) {
            socket.emit('sendMessage',{username: _username, message: _message});
        }
    };

然后我们规定在聊天框回车的时候调用sendMessage函数,如下:
    /*聊天事件*/
    _$chatinput.on('keyup',function (event) {
        if(event.keyCode === 13) {
            sendMessage();
            _$chatinput.val('');
        }
    });


服务器端监听sendMessage

    /**
     * 监听sendMessage,我们得到客户端传过来的data里的message,并存起来。
     * 我使用了ES6的for-of循环,和ES5 的for-in类似。
     * for-in是得到每一个key,for-of 是得到每一个value
     */
    socket.on('sendMessage',(data)=>{
        for(let _user of users) {
            if(_user.username === data.username) {
                _user.message.push(data.message);
                //信息存储之后触发receiveMessage将信息发给所有浏览器
                io.emit('receiveMessage',data);
                break;
            }
        }
    });
我们是遍历服务器端的用户数组,找到该用户,将发送的信息存起来,然后触发receiveMessage事件广播到所有浏览器,sendMessage是写在connection里,login之外的,为什么这么做大家一定要理解,发送消息是连接时候做的事情,而不是登录时做的事情。
注意的是,我使用的是io.emit,他是真正的广播到所有浏览器,socket.broadcast.emit则不会广播到自己的浏览器。

客户端监听receiveMessage事件

客户端监听到广播后自然就要显示了,这里注意一点,自己的信息和别人的信息显示的样式是不同的,下面编写逻辑
    socket.on('receiveMessage',(data)=>{
        /**
         * 监听到事件发生,就显示信息
         */
        showMessage(data);
    })

我们看到,广播除了发送范围,其余的没什么不同的,下面写showMessage的逻辑
    let showMessage = function (data) {
        //先判断这个消息是不是自己发出的,然后再以不同的样式显示
        if(data.username === _username){
            $("#content").append(`<p style='background: lightskyblue'><span>${data.username} : </span> ${data.message}</p>`);
        }else {
            $("#content").append(`<p style='background: lightpink'><span>${data.username} : </span> ${data.message}</p>`);
        }
    };
很简单是不是,现在我们基本完成了,代码如下:
main.js:
/**
 * Created by zhouxinyu on 2017/8/6.
 */
$(function(){
    const url = 'http://127.0.0.1:3000';
    let _username = null;
    let _$inputname = $("#name");
    let _$loginButton = $("#loginbutton");
    let _$chatinput = $("#chatinput");

    let socket = io.connect(url);

    //设置用户名,当用户登录的时候触发
    let setUsername = function () {
        _username = _$inputname.val().trim();    //得到输入框中用户输入的用户名

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

    let beginChat = function () {
        /**
         * 1.隐藏登录框,取消它绑定的事件
         * 2.显示聊天界面
         */
        $("#loginbox").hide('slow');
        _$inputname.off('keyup');
        _$loginButton.off('click');

        /**
         * 显示聊天界面,并显示一行文字,欢迎用户
         * 这里我使用了ES6的语法``中可以使用${}在里面写的变量可以直接被浏览器渲染
         */
        $(`<p>欢迎你${_username}</p>`).insertBefore($("#content"));
        $("#chatbox").show('slow');
    };

    let sendMessage = function () {
        /**
         * 得到输入框的聊天信息,如果不为空,就触发sendMessage
         * 将信息和用户名发送过去
         */
        let _message = _$chatinput.val();

        if(_message) {
            socket.emit('sendMessage',{username: _username, message: _message});
        }
    };

    let showMessage = function (data) {
        //先判断这个消息是不是自己发出的,然后再以不同的样式显示
        if(data.username === _username){
            $("#content").append(`<p style='background: lightskyblue'><span>${data.username} : </span> ${data.message}</p>`);
        }else {
            $("#content").append(`<p style='background: lightpink'><span>${data.username} : </span> ${data.message}</p>`);
        }
    };


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

    _$inputname.on('keyup',function (event) {     //监听输入框的回车事件,这样用户回车也能登录。
        if(event.keyCode === 13) {                //如果用户输入的是回车键,就执行setUsername函数
            setUsername();
        }
    });


    /*聊天事件*/
    _$chatinput.on('keyup',function (event) {
        if(event.keyCode === 13) {
            sendMessage();
            _$chatinput.val('');
        }
    });


    /*socket.io部分逻辑*/
    socket.on('loginSuccess',(data)=>{
        /**
         * 如果服务器返回的用户名和刚刚发送的相同的话,就登录
         * 否则说明有地方出问题了,拒绝登录
         */
        if(data.username === _username) {
            beginChat(data);
        }else {
            alert("用户名不匹配,请重试");
        }
    });

    socket.on('receiveMessage',(data)=>{
        /**
         * 监听到事件发生,就显示信息
         */
        showMessage(data);
    })

});


app.js:
/**
 * Created by zhouxinyu on 2017/8/6.
 */
const express = require('express');  // const是ES6的语法,代表常量,准确来说就是指向不发生改变。如果不习惯就用var代替
const app = express();               // express官网就是这么写的就是用来创建一个express程序,赋值给app。如果不理解就当公式记住
const server = require('http').Server(app);
const path = require('path');        // 这是node的路径处理模块,可以格式化路径
const io = require('socket.io')(server);     //将socket的监听加到app设置的模块里。

const users = [];                    //用来保存所有的用户信息
let usersNum = 0;

server.listen(3000,()=>{                // ()=>是箭头函数,ES6语法,如果不习惯可以使用 function() 来代替 ()=>
    console.log("server running at 127.0.0.1:3000");       // 代表监听3000端口,然后执行回调函数在控制台输出。
});

/**
 * app.get(): express中的一个中间件,用于匹配get请求,所谓中间件就是在该轮http请求中依次执行的一系列函数。
 * '/': 它匹配get请求的根路由 '/'也就是 127.0.0.1:3000/就匹配到他了
 * (req,res): ES6语法的箭头函数,你暂时可以理解为function(req,res){}。
 * req带表浏览器的请求对象,res代表服务器的返回对象
 */
app.get('/',(req,res)=>{
    res.redirect('/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 的路径都会去public文件夹下找XXX文件然后发送给浏览器。
 */
app.use('/',express.static(path.join(__dirname,'./public')));        //一句话就搞定。

/*socket*/
io.on('connection',(socket)=>{              //监听客户端的连接事件
    /**
     * 所有有关socket事件的逻辑都在这里写
     */
    usersNum ++;
    console.log(`当前有${usersNum}个用户连接上服务器了`);
    socket.on('login',(data)=>{
        //将该用户的信息存进数组中
        users.push({
            username: data.username,
            message: []
        });

        //然后触发loginSuccess事件告诉浏览器登陆成功了
        socket.emit('loginSuccess',data);   //将data原封不动的再发给该浏览器
    });

    /**
     * 监听sendMessage,我们得到客户端传过来的data里的message,并存起来。
     * 我使用了ES6的for-of循环,和ES5 的for-in类似。
     * for-in是得到每一个key,for-of 是得到每一个value
     */
    socket.on('sendMessage',(data)=>{
        for(let _user of users) {
            if(_user.username === data.username) {
                _user.message.push(data.message);
                //信息存储之后触发receiveMessage将信息发给所有浏览器
                io.emit('receiveMessage',data);
                break;
            }
        }
    });

    //断开连接后做的事情
    socket.on('disconnect',()=>{          //注意,该事件不需要自定义触发器,系统会自动调用
        usersNum --;
        console.log(`当前有${usersNum}个用户连接上服务器了`);
    })
});

现在打开浏览器,聊天试试吧




最简单的聊天室已经基本完成了,但是这里有一个小问题


发现了吗?在消息过多的时候,输入框就没法保持在底部了,下面我们用js来解决
    let setInputPosition = function () {
        let height = $(window).height()>$('#content p:last').offset().top+$('#content p:last').height()*2?$(window).height():$('#content p:last').offset().top+$('#content p:last').height()*2;
        _$chatinput.css({'top': height});
    };

具体的逻辑就是在每次显示的时候都会判断一下聊天内容的高度,然后定位输入框的位置,如果各位有更好的方法,欢迎评论留言哦
本章内容也已上传至github,在chat文件夹下的chat目录, https://github.com/neuqzxy/chat.git 欢迎浏览,觉得不错记得给个星星哦 吐舌头

猜你喜欢

转载自blog.csdn.net/NEUQ_zxy/article/details/76794551
今日推荐