博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
聊天系统很复杂?前端工程师也能完成!
阅读量:7115 次
发布时间:2019-06-28

本文共 12686 字,大约阅读时间需要 42 分钟。

简介

使用流行的 web 应用技术栈 —— 比如PHP —— 来编写聊天应用通常是很困难的。它包含了轮询服务器以检测变化,还要追踪时间戳,并且这种实现是比较慢的。

大多数实时聊天系统通常基于 WebSocket 来构建,。 WebSocket 为客户端和服务器提供了双向通信机制。

这意味着服务器可以 推送 消息给客户端。无论何时你发布一条消息,服务器都可以接收到消息并推送给其他连接到服务器的客户端。

web 框架

首先要制作一个 HTML 页面来提供表单和消息列表。我们使用了基于 Node.JS 的 web 框架 express 。 请确保安装了 Node.JS。

首先创建一个 package.json 来描述我们的项目。 推荐新建一个空目录。

express 已经安装好了。我们现在新建一个 index.js 文件来创建应用。

var app = require('express')();var http = require('http').Server(app);app.get('/', function(req, res){  res.send('

Hello world

');});http.listen(3000, function(){ console.log('listening on *:4000');}); 复制代码

这段代码作用如下:

Express 初始化 app 作为 HTTP 服务器的回调函数。

定义了一个路由 / 来处理首页访问。

使 http 服务器监听端口 4000。

HTML 服务器

目前在 index.js 中我们是通过 res.send 返回一个 HTML 字符串。 如果我们将整个应用的 HTML 代码都放到应用代码里,代码结构将变得很混乱。 替代的方法是新建一个 index.html 文件作为服务器响应。

现在我们用 sendFile 来重构之前的回调:

app.get('/', function(req, res){res.sendFile(__dirname + '/index.html');});复制代码

index.html 内容如下:

      Socket.IO chat            
    复制代码

    集成

    由两部分组成:

    • 一个服务端用于集成 (或挂载) 到 Node.JS HTTP 服务器:
    • 一个加载到浏览器中的客户端: socket.io-client

    这个两部分都会运用到

    npm install --save

    npm install --save socket.io-client

    var app = require('express')();var http = require('http').Server(app);var io = require('socket.io')(http);app.get('/', function(req, res){  res.sendFile(__dirname + '/index.html');});io.on('connection', function(socket){  console.log('a user connected');});http.listen(3000, function(){  console.log('listening on *:3000');});复制代码

    我们通过传入 http (HTTP 服务器) 对象初始化了 的一个实例。 然后监听 connection 事件来接收 sockets, 并将连接信息打印到控制台。

    在 index.html 的 标签中添加如下内容:

    复制代码

    这样就加载了 。 暴露了一个 io 全局变量,然后连接服务器。

    请注意我们在调用 io() 时没有指定任何 URL,因为它默认将尝试连接到提供当前页面的主机。

    重新加载服务器和网站,你将看到控制台打印出 “a user connected”。

    每个 socket 还会触发一个特殊的 disconnect 事件:

    io.on('connection', function(socket){  console.log('a user connected');  socket.on('disconnect', function(){    console.log('user disconnected');  });});    复制代码

    触发事件

    的核心理念就是允许发送、接收任意事件和任意数据。任意能被编码为 JSON 的对象都可以用于传输。二进制数据 也是支持的。

    这里的实现方案是,当用户输入消息时,客户端发送一个 chat message 事件,服务器接收一个 chat message 事件。index.html 文件中的 script 部分现在应该内容如下:

    复制代码

    广播

    接下来的目标就是让服务器将消息发送给其他用户。

    要将事件发送给每个用户, 提供了 io.emit 方法:

    io.emit('some event', { for: 'everyone' });

    为了简单起见,我们将消息发送给所有用户,包括发送者。

    io.on('connection', function(socket){  socket.on('chat message', function(msg){    io.emit('chat message', msg);  });});复制代码

    用法总结

    服务端

    1.连接

    监听客户端连接,回调函数会传递本次连接的socket

    io.on('connection',function(socket));复制代码

    2.广播

    (1)给所有客户端广播消息

    io.sockets.emit('String',data);复制代码

    (2)给除了自己以外的客户端广播消息

    socket.broadcast.emit("msg",{data:"hello,everyone"});复制代码

    (3)给指定的客户端发送消息

    io.sockets.socket(socketid).emit('String', data);复制代码

    3.发送的消息

    (1)监听客户端

    socket.on('String',function(data));复制代码

    (2)给该socket的客户端发送消息

    socket.emit('String', data);复制代码

    4.分组

    io.of('/some').on('connection', function (socket) {    socket.on('test', function (data) {        socket.broadcast.emit('event_name',{});    });});复制代码

    进阶——处理用户发送的数据

    一、redis

    什么是Redis?

    REmote DIctionary Server(Redis) 是一个由SalvatoreSanfilippo写的key-value(键值对)存储系统。

    Redis是一个开源的使用ANSI C语言编写、遵守BSD协议、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库,并提供多种语言的API。

    它通常被称为数据结构服务器,因为值(value)可以是字符串(String), 哈希(Map), 列表(list), 集合(sets) 和有序集合(sorted sets)等类型。

    Redis中的数据类型

    哈希(Map hashmap):散列表(Hash table,也叫哈希表),是根据键(Key)而直接访问在内存存储位置的数据结构。

    列表(list):列表是一种数据项构成的有限序列,即按照一定的线性顺序,排列而成的数据项的集合。(redis中使用双向链表实现)

    集合(sets):和中学时学习的概念是相似的。特点是集合中元素不能重复是唯一的。切内部是无序的

    有序集合(sorted sets):也是一种集合,但是内部数据是经过排序的。

    redis安装

    redis使用方法

    0、建立node-redis的client端连接 npm i redis --save

    // redis 链接var redis = require('redis');var client = redis.createClient('6379', '127.0.0.1');// redis 链接错误client.on("error", function(error) {    console.log(error);});// redis 验证 (reids.conf未开启验证,此项可不需要)// client.auth("foobared");module.exports = {    client:client}复制代码

    1、set的存取

    const {client} = require('./redis')client.set('key001', 'AAA', function (err, response) {    if (err) {        console.log("err:", err);    } else {        console.log(response);        client.get('key001', function (err, res) {            if (err) {                console.log("err:", err);            } else {                console.log(res);                client.end(true);            }        });    }});复制代码

    2、hash存取

    hash set的设值和抽取数据都有单个key和多个key两种方式:

    const {client} = require('./redis')client.hset('filed002', 'key001', 'wherethersisadoor', function (err, res) {    if (err) {        console.log(err);    } else {        console.log('res:', res);        client.hget('filed002', 'key001', function (err, getRslt) {            if (err) {                console.log(err);            } else {                console.log('getRslt:', getRslt);                client.end(true);            }        });    }});复制代码

    注意:当hget方法在指定field下找不到指定的key时,会传给回调函数null,而非空字符或undefined。

    ※ 设定多个key的值,取值时获取指定field下指定单个或多个key的值

    const {client} = require('./redis')var qe = {a: 2, b:3, c:4};client.hmset('field003', qe, function(err, response) {    console.log("err:", err);    console.log("response:", response);    client.hmget('field003', ['a', 'c'], function (err, res) {        console.log(err);        console.log(res);        client.end(true);    });});复制代码

    hmset方法的设定值可以是JSON格式的数据,但是redis中key的值是以字符串形式存储的,如果JSON数据层数超过一层,会出现值是'[object Object]'的情况。

    hmget方法的返回值是个数组,其中元素的顺序对应于参数的key数组中的顺序,如果参数数组中有在field内不存在的key,返回结果数组的对应位置会是null,也即无论是否能取到值,结果数组中的元素位置始终与参数的key数组中元素位置一一对应。

    获取hash中所有key的方法是client.keys(fieldname, callback); 需要注意的是如果hash中key的数目很多,这个方法的可能耗费很长时间。

    3.链表 适合存储社交网站的新鲜事 lpush key value [value ...] 向链表key左边添加元素 rpush key value [value...] 向链表key右边添加元素 lpop key 移除key链表左边第一个元素 rpop key 移除key链表右边第一元素

    const {client} = require('./redis')client.lpush('test', 12345, function(err, response) {    if(err){        console.log("err:", err);    }else{        console.log("response:", response);        client.rpop('test',function (err, res){            if(err){                console.log(err);            }else{                console.log(res);                client.end(true);            }        });    }});复制代码

    redis-cli

    socket.io中接入redis 并创建多个命名空间

    How to use

    const io = require('socket.io')(3000);const redis = require('socket.io-redis');io.adapter(redis({ host: 'localhost', port: 6379 }));复制代码

    将index.js修改为

    const app = require('express')();const http = require('http').Server(app);const io = require('socket.io')(http);const redis = require('socket.io-redis');const {client} = require('./test/redis')const moment = require('moment')app.get('/', function(req, res){    res.sendFile(__dirname + '/index.html');});io.adapter(redis({host: 'localhost', port: 6379}));var nameBox = ['/chatroom','/live','/vod','/wechat','/broadcast'];for(var item in nameBox){    var nsp = io.of(nameBox[item])    socketMain(nsp,nameBox[item])}function socketMain(nsp,roomName) {    nsp.on('connection',function (socket) {        console.log('a user connected')        socket.on('disconnect', function(){            console.log('user disconnected');        });        socket.on('chat message', function(msg){            var data = {
    "socketid":socket.id,"cid":roomName,"msg":msg,createTime:moment().unix()}; client.lpush('message',JSON.stringify(data),redis.print) console.log('message: ' + msg); }); })}http.listen(4000, function(){ console.log('listening on *:4000');});复制代码

    index.html

    var socket = io.connect("http://127.0.0.1:4000/live");复制代码
    接入redis
    client.lpush('message',JSON.stringify(msg),redis.print)复制代码

    二、另起一个服务端拿redis数据进行处理

    Question:两个服务怎么相互通信?
    Answer:使用socket.io-client 具体步骤如下:
    1.在数据处理程序中引入 socket.io-client var io = require('socket.io-client');2.用socket.io-client 模拟了一个,连接到主程序io中的客户端var socket = io.connect('ip+'/live'', {reconnect: true});3.通过这个模拟的客户端,与主程序通信socket.emit('redisCome', result);复制代码

    修改redis.js

    module.exports = {    client:client,    ip:'http://127.0.0.1:4000'}复制代码

    新建sclient.js

    const io = require('socket.io-client');const async = require('async');const moment = require('moment');const redis = require('redis');const {client,ip} = require('./test/redis');const domain = require('domain');const debug = require('debug')('socket-client:main');var origin = io.connect(ip+'/', {reconnect: true});var chatroom = io.connect(ip+'/chatroom', {reconnect: true});var live = io.connect(ip+'/live', {reconnect: true});var vod = io.connect(ip+'/vod', {reconnect: true});var wechat = io.connect(ip+'/wechat', {reconnect: true});var broadcast = io.connect(ip+'/broadcast', {reconnect: true});var namBox = {root:origin,chatroom:chatroom,live:live,vod:vod,wechat:wechat,broadcast:broadcast};var reqDomain = domain.create();reqDomain.on('error', function (err) {    console.log(err);    try {        var killTimer = setTimeout(function () {            process.exit(1);        }, 100);        killTimer.unref();    } catch (e) {        console.log('error when exit', e.stack);    }});reqDomain.run(function () {    compute();});process.on('uncaughtException', function (err) {    console.log(err);    try {        var killTimer = setTimeout(function () {            process.exit(1);        }, 100);        killTimer.unref();    } catch (e) {        console.log('error when exit', e.stack);    }});function compute() {    client.llen('message', function(error, count){        if(error){            console.log(error);        }else{            if(count){                //console.log('-------------has count',time);                popLogs();                process.nextTick(compute);            }else{                //console.log('-------------empty',time);                setTimeout(function(){                    compute();                },100);            }        }    });}function popLogs(){    var time = moment().unix();    console.log('-------------dealStart-------------',time);    client.rpop('message',function(err,result){        if(err){            console.log(err);        }else{            var result = JSON.parse(result);            try{                var cid = result.cid;                //console.log('place',result.place);            }catch(e){                console.log('empty data cid',result);                return;            }            console.log(' start '+' nsp: '+cid +' time: '+time);            if(namBox[cid]){                console.log(result);                namBox[cid].emit('redisCome',result);            }        }    });}复制代码

    修改index.js 增加redisCome监听事件

    /*接收redis发来的消息*/socket.on('redisCome',function (data) {    console.log('-------------redisCome',data.msg);    try{        var msg = data.msg    }catch(e){        var msg = '';    }    console.log(data);    nsp.emit('message.add',msg);});复制代码

    修改index.html

    socket.on('message.add',function (msg) {    $('#messages').append($('
  • ').text(msg));})复制代码
  • 三、增加用户发送信息校验

    增加信息的安全性,我们可以对用户发送的信息进行敏感词、sql注入攻击、xss攻击等进行过滤 使用async一步步操作流程

    修改sclient.js

    async.waterfall([    function (done) {        user.messageDirty({msg:result.msg},function(err,res){            //console.log('sql done'/*,res*/);            done(err,res);        });    },    function (res,done) {        user.messageValidate({msg:result.msg},function(err,res){            //console.log('key done'/*,res*/);            done(err,res);        });    }],function (err,res) {    if(err){        console.log('err!!!!',err,result);        namBox[cid].emit('messageError',err);    }else{        if(namBox[cid]) {            console.log(result);            namBox[cid].emit('redisCome', result);        }    }})复制代码

    修改index.js

    /*接收redis错误信息返回*/socket.on('messageError',function(err){    console.log('messageError');    try{        nsp.emit('message.error',err.msg);    }catch(e){    }});复制代码

    修改index.html

    mysql入库

    1.在本地安装mysql数据库 2.下载node mysql包

    npm install mysql --save复制代码

    3.连接数据库 建立连接池

    var mysql      = require('mysql');var pool = mysql.createPool({    host: 'localhost',    user:'root',    password:'123456',    database : 'danmaku'});var query = function(sql,options,callback){    pool.getConnection(function(err,conn){        if(err){            callback(err,null,null);        }else{            conn.query(sql,options,function(err,results,fields){                //释放连接                conn.release();                //事件驱动回调                callback(err,results,fields);            });        }    });};复制代码

    新建query.js

    var {query} = require("./test/redis");query("select * from demo", function(err,results,fields){    //do something    if(err){        console.log(err)    }else {        console.log(results)    }});复制代码

    新建insert.js

    var {query} = require("./test/redis");const moment = require('moment')query('insert into demo(message,createTime) values(?,?)',[123,moment().unix()],function(err,results,fields){    //do something    if(err){        console.log(err)    }else {        console.log(results)    }});复制代码

    mysql -u root -p use danmaku; select * from demo;

    4.在程序中添加入库步骤

    弹幕播放器

    项目地址

    转载地址:http://qywel.baihongyu.com/

    你可能感兴趣的文章
    印制电路板设计点滴
    查看>>
    Linux下搭建MySQL的主从复制(一)
    查看>>
    王高利:Linux__ssh连接缓慢,解决方法如下
    查看>>
    linux 之fuser使用介绍
    查看>>
    history命令记录历史执行时间
    查看>>
    物联网
    查看>>
    linux vi 编辑器的使用
    查看>>
    我的友情链接
    查看>>
    心态总结
    查看>>
    zabbix自定义监控3(2.4网页报警,邮件报警)
    查看>>
    我的友情链接
    查看>>
    Prime
    查看>>
    XML的DOM解析
    查看>>
    Android特效(1)----字幕滚动效果
    查看>>
    java获取当前类的绝对路径
    查看>>
    邮件服务器互发
    查看>>
    USB接口新规范USB3.0功能
    查看>>
    我的友情链接
    查看>>
    3Python全栈之路系列之RabbitMQ
    查看>>
    Cisco设备上设置DHCP实例
    查看>>