少年,进来学习开发socket聊天室吧【下】

1、回顾上一集内容

  • 在本地安装和配置node环境
  • 用node创建http服务器,配置路由,安装所需的模块
  • 前后端都引入socket.io
  • 根据socket.io提供的api进行事件的传递

上一集内容已经实现了用户加入到一个公共的空间,并且把对应的socketID给打印到前端页面上。
这一集内容将会带大家一起实现可以实时发送文字流的socket聊天室,为了给后续的课程做铺垫,这里会先讲一下用户登录功能的实现。

2、用户登录

数据库方面笔者这里用的mongodb,所以下面的介绍也是围绕mongodb来介绍的

1)安装mongodb以及robomongo

下载地址

mongodb:https://www.mongodb.com/download-center
robomongodb(mongodb可视化工具):https://robomongo.org/

配置(以win7为例)
  • 配置全局环境
    笔者的mongo安装路径:D:/mongoDB/
    将D:/mongoDB/bin添加到全局环境,按下图从左到右红框点击顺序,将路径添加到path变量值后。
少年,进来学习开发socket聊天室吧【下】_第1张图片
Paste_Image.png
  • 读取配置并自动启动
    新建文件夹
D:/mongodbData/ --- 存放log和db,日志数据和数据库数据
D:/mongodbData/log --- 存放日志数据
D:/mongodbData/db --- 存放数据库数据
D:/mongodbData/mongodb.cfg --- 存放数据库启动的配置

更改配置(写入mongodb.cfg)

dbpath=D:/mongodbData/db
logpath=D:/mongodbData/log/mongodb.log
auth=true #开启认证

安装mongodb为window Service服务(安装完在services.msc可以找到mongodb的服务)

$ mongod --config "D:/mongodbData/mongodb.cfg" --install --serverName mongodb

尝试

$ net start mongodb #启动
$ net stop mongodb #关闭
  • 用户认证
    由于上面cfg文件中auth=true,开启了用户认证,所以连接数据库后必须通过认证才能执行敏感操作。未登录时进行敏感操作会出现下图情况。所以我们需要先创建顶级用户
少年,进来学习开发socket聊天室吧【下】_第2张图片
Paste_Image.png

依次输入下面的命令

$ mongo #连接mongo服务器
$ use admin #使用admin数据库
$ db.createUser({ #创建用户root,拥有root权限
   user:"root",
  password:"123456",
  roles:[{role:"root",db:"admin"}]
})
$ db.auth("root","123456") #通过认证
$ use demo #新建mydb数据库
$ db.createUser({ #创建普通用户ze,拥有读写权限
   user:"ze",
  pwd:"123456",
  roles:[{role:"readWrite",db:"demo"}]
})

下一次使用命令行登录时

$ mongo -uze -p123456 demo

使用robomongo进行登录时

少年,进来学习开发socket聊天室吧【下】_第3张图片
Paste_Image.png

2)node安装mongodb依赖

$ npm i mongoose --save-dev #一个mongodb的框架

在原先项目目录新建mongo.js,写入

var mongo = require('mongoose');
mongo.connect('mongodb://ze:123456@localhost:27017/demo');
var db = mongo.connection;
db.on('error', console.error.bind(console, 'connection error:'));
db.on('connected', function (callback) {
  console.log("db has already opened");
});
db.on('disconnected', function (callback) {
  console.log("db has already exited");
});

var Schema = mongo.Schema; // 配置每一条记录的模型
var UserSchema = new Schema({          
    account : { type: String },   //账号
    password: {type: String}   // 密码
});

module.exports = mongo.model('userlist',UserSchema);

在socket.js中引用

var mongo = require('./mongo'); 

命令行启动socket.js,出现下图则已经成功连接数据库

Paste_Image.png

3)以登录功能为例做分析

  • 前端(登录会存用户名到cookie)
var data = {
    username: $( "input#username" ).val(),
    password: $( "input#password" ).val()
};
$.post( "/login", data, function( data, status ) {
  switch( data.result ) {
    case 1: // 与后端协定好,1代表登录成功
      // 将用户名存入cookie
      // 连接到socket服务器,同时发送login事件
      var socket = io.connect();
      // Your_username是从cookie获取
      socket.emit( "login", Your_username );
    default:
      // 打印无法成功登录的信息;
      alert( data.msg );
  }
});
// 捕获login事件后,执行回调
socket.on("login",function( names ){
    var str = "";
    names.forEach( function( item, index) {
      str += item + "
"; }) $( "#curUser" ).html( str ); })
  • 后端(路由设置和socket事件响应)
    路由设置(登录)
server.post('/login', function(req, res) {
    var data = []; // 存放post数据
    req.on("data", function(chunk) {
        data.push(chunk); // 前端post方式发过来的数据都是转成buffer格式再发给后端的
    });
    req.on("end", function() {
        // console.log( data ); --- 可以看到buffer类型数据
        // 获取post请求信息
        var postData = Buffer.concat(data).toString();
        // console.log( postData ); --- 这里通过格式转换成了url参数格式
        var loginInfo = querystring.parse(postData);
        // console.log( loginInfo ); --- 这里就是将url参数转换成json格式
        var usr = {
            account: loginInfo.account,
            password: loginInfo.password
        };
        // mongoose的查找方法,result返回一个数组,包含查找的信息
        mongo.find(usr, function(err, result) {
            var response;
            if (err) {
                console.log(err);
            } else {
                if (result.length) {
                    response = {
                        result: 1,
                        msg: "登录成功",
                        account: loginInfo.account
                    };
                } else {
                    response = {
                        result: -1,
                        msg: "用户或者密码不正确"
                    };
                }
                res.end(JSON.stringify(response));
            };
        });
    });
});

socket事件响应

var names = [];
io.on('connection', function(socket) {
    socket.on('login',function(name){
        names.push( name );
        io.sockets.emit('login',names); 
    })
});

3、文字聊天功能实现

  • 这个功能实现的关键在于后端,所有的信息都是通过后端进行接收和转发的,举个例子,clientA想向clientB发一条信息,那么需要告诉后端,clientB的地址以及发送的信息。关于clientB的地址,我们可以在每个客户端登录到socket服务器的时候就将客户端的socketName和socket地址存起来,那么只需要知道socketName就可以遍历出socket地址,就可以进行点对点信息交换了。
  • 接下来演示一下前后端的关键代码,这里演示的是信息群发,不针对某个客户端,只要登录到socket服务器的客户端都能接收到
前端输入信息,点击发送:
// 定义信息呈现出来的模板
var msgTpl = "
  • {username}{msg}
  • "; // 点击发送 $( ".btn-send" ).on( "click", function( e ) { // 信息输入框 var msg = $( "input.message" ); // username可以从cookie获取,也可以存入一个变量 socket.emit( "message", username, msg.val() ); updateMsg( username, msg.val() ); // 发送完将输入框的值清空 msg.val( "" ); }); function updateMsg( username, msg ) { var msgContainer = $( ".msgContainer" ); var msg = { username: username, msg: msg }; // 最近看到的用正则全局替换模板,比之前的html拼接帅多了 for( var key in msg ) { var reg = new RegExp( "\\{" + key + "\\}", "g" ); msgTpl = msgTpl.replace( reg, msg.key ); } msgContainer.append( msgTpl ); } // 当收到别的客户端发的信息时执行的回调 socket.on( "message", function( username, msg ) { updateMsg( username, msg ); })
    后端进行消息转发:
    socket.on('message', function(username, msg) {
        // socket.broadcast --- 连接同一个socket服务器,并且同一个namespace,除了自己以外的其他用户
        socket.broadcast.emit('message', username, msg );
    });
    

    注:对于namespace和room有疑问的参考下方链接
    http://blog.csdn.net/lijiecong/article/details/50781417

    4、小结和预告

    • 用户登录,前后端和数据库如何交接
    • 消息转发,其实就是socket事件驱动,类似的实现方法还有h5的新接口eventSource(SSE),一样可以实现事件驱动来推送信息,但是一般不做聊天用。
    • 预告下一期介绍基于webRTC的视频聊天室。大家都用flash做视频聊天,可是我又不会actionScript,能用JS写个视频聊天室吗?能!

    ————
    前端·小泽
    给点信心自己,就一定能做到

    你可能感兴趣的:(少年,进来学习开发socket聊天室吧【下】)