Node.js 切近实战(十一) 之实时通讯

曾经在幽幽暗暗反反复复中追问,才知道平平淡淡从从容容才是真,听着歌曲,写博客,感觉就来了。

今天我们主要看一下Socket.IO实时通讯,先看一下界面。

.row
 .col-md-9
  .panel.panel-primary
   .panel-heading
    h3.panel-title(style='font-size:13px;') Chat Message
   .panel-body#div_msgbody(style='min-height:590px;max-height:590px;overflow:auto;max-width:750px;')
    #div_msg.panel-content(style='word-wrap:break-word;word-break: break-word;')
   .panel-footer
    #div_footer(style='height:36px;line-height:36px')    
     .row
      .col-md-8(style='color:#3f51b5;font-weight:bold') Chat History:
        input#chat_history
      .col-md-4.right-align-text 
       a#link_clear(href=#>

wKioL1eDsL6xz1MqAABsQWR8_7I057.png 

这就是聊天界面,左边是聊天内容,右边是参加聊天的用户。要实现这个聊天,之前我们在博客中提到了SingalR,可以用于ASP.NET,WinForm以及WPF。今天我们要使用Node.js平台上的Socket.IO.js。首先要在项目中引用这个扩展包。

wKiom1eDscyQdWxlAACfiVxC6a8872.png

安装好之后,在Package.json中就会自动加入这个包,管理起来。

"dependencies": {
    "array-splice": "^0.1.1",
    "body-parser": "~1.8.4",
    "busboy": "^0.2.12",
    "cassandra-driver": "^3.0.0",
    "cookie-parser": "~1.3.3",
    "debug": "~2.0.0",
    "express": "~4.9.8",
    "express-session": "1.12.1",
    "gridfs-stream": "^1.1.1",
    "jade": "^1.11.0",
    "log4js": "^0.6.29",
    "mongoose": "~4.2.3",
    "morgan": "^1.6.1",
    "request": "^2.67.0",
    "serve-favicon": "~2.1.3",
    "socket.io": "^1.3.7",
    "string.prototype.endswith": "^0.2.0",
    "string.prototype.startswith": "^0.2.0"
  }

我在这里还使用的是老版本,哈哈,OK,老版本新版本都能用。我们进入主题,在第一篇环境搭建中,我就说了我们的启动入口是www文件。

wKioL1eDspryvKkZAAAOS6Gho1U804.png

在www文件中,我们初始化了SocketIO的一些东西。

var chatUserCount = 0;
var chatUsers = {};
var server = app.listen(app.get('port'), function() {
    debug('Express server listening on port ' + server.address().port);
});

var io = require('socket.io')(server);

io.on('connection', function (socket) {
    socket.on('joinchat', function (obj) {
	    console.log('a user connected:'+obj.UserName);
        socket.name = obj.UserName;
        if (!chatUsers.hasOwnProperty(obj.UserName)) {
            chatUsers[obj.UserName] = obj;
            chatUserCount++;
        }

	io.emit('joinchat', { chatUsers: chatUsers ,chatUserCount: chatUserCount,joinedUser: obj});
    });
    
    socket.on('leftchat', function () {
            console.log('a user left');
            if (chatUsers.hasOwnProperty(socket.name)) {
                var obj = chatUsers[socket.name];
                delete chatUsers[socket.name];
                chatUserCount--;
        
                io.emit('leftchat', { chatUsers: chatUsers, chatUserCount: chatUserCount, leftUser: obj });
            }
        });

    socket.on('disconnect', function () {
         if (chatUsers.hasOwnProperty(socket.name)) {
            var obj = chatUsers[socket.name];
            delete chatUsers[socket.name];
            chatUserCount--;

            io.emit('leftchat', { chatUsers: chatUsers, chatUserCount: chatUserCount, leftUser: obj });
        }
    });

    socket.on('message', function (obj) {
        io.emit('message', obj);
    });

    socket.on('error', function(exception) {
	console.log('SOCKET ERROR');
	socket.destroy();
    });
});

在这里当客户端有用户进入聊天时,就会发射joinchat事件,后台就会触发joinchat事件。当客户端和服务端建立连接时,服务端就会发射广播joinchat,所有连接的客户端都会收到这个广播,悄无声息刷新界面。当客户端用户失去连接(关闭浏览器)时,就会自动发射disconnect事件,服务端就会触发disconnect事件,并将结果广播到各个客户端,客户端自动刷新页面。当用户unload该页面时,会触发leftchat。当客户端发信息时,就会触发message事件,将该用户的消息发送到其他人。这个聊天界面的过程就是这样,很简单。

接下来我们来看一下客户端代码。

var popupNotification = $("#notify").kendoNotification({
    autoHideAfter: 2000,
    height: 60,
    stacking: "down"
}).data("kendoNotification");

var socket = io();
var loginUser = sessionStorage.getItem("LoginUser");
if (loginUser == null) {
  window.location.href = "/";
  return;
}
var userObj = eval("(" + loginUser + ")");
    
sessionStorage.removeItem('chatUser');
socket.emit("joinchat", userObj);
socket.on('joinchat', function (data) {
    if (!sessionStorage.getItem('chatUser')) {
        sessionStorage.setItem('chatUser', JSON.stringify({ "user": [] }));
    }
    
    var usersObj = JSON.parse(sessionStorage.getItem('chatUser'));
    if (usersObj.user.indexOf(data.joinedUser.UserID) == -1) {
        usersObj.user.push(data.joinedUser.UserID);
        sessionStorage.setItem('chatUser', JSON.stringify(usersObj));
        setchartdetail(data);
        
        if (data.joinedUser.UserID != userObj.UserID) {
            popupNotification.show('<span style="color:red">' + data.joinedUser.FullName + ' joined in.</span>', 'info');
        }
    }
});

socket.on('leftchat', function (data) {
    var usersObj = JSON.parse(sessionStorage.getItem('chatUser'));
    var index = usersObj.user.indexOf(data.leftUser.UserID);
    
    usersObj.user.splice(index, 1);
    sessionStorage.setItem('chatUser', JSON.stringify(usersObj));
    setchartdetail(data);
    popupNotification.show('<span style="color:red">' + data.leftUser.FullName + ' left.</span>', 'warning');
});

当用户进入这个页面时,我们发射joinchat,并将当前登录用户信息发送到服务端,服务端再将该用户信息以及计算好的用户总数广播到各个客户端。注意这里我们为了提醒用户,用到了kendoNotification,效果如下,当有人进入或者离开时,会出现popup提示。

wKiom1eDuAvCnttIAABY1yPLRUA192.png

当用户离开时,如上,当用户进入时,如下

wKioL1eDuDbi3mmiAABxPpJdZlM722.png

OK,接下来我们看一下最主要的部分,聊天。

$("#btn_send").click(function () {
    sendmsg();
})

$("#txt_msg").keydown(function (e) {
    if (e.keyCode == 13) {
        sendmsg();
    }
});

function sendmsg() {
    var msg = $.trim($("#txt_msg").val());
    if (msg) {
        var msgObj = { user: userObj, msg: msg };
        socket.emit("message", msgObj);
    }
    
    $("#txt_msg").val("");
}

上面就是点击SEND按钮或者文本框回车发送消息的代码,后台接到message广播给客户端。

socket.on('message', function (data) {
    var msgObj = data;
    
    var userAvatar = '/images/userlogin.png';
    
    if (msgObj.user.UserName == userObj.UserName) {
        $("#div_msg").append("<div class='row-margin'>" 
        + "<img src='" + userAvatar + "' style='height:40px;width:40px;right:-660px;position:relative'/>" 
        + "<span style='right:-530px;position:relative'>" + msgObj.user.FullName + "</span>" 
        + "<div class='demo clearfix fr'>" 
        + "<span class='triangle'></span>" 
        + "<div class='article' style='word'>" + msgObj.msg 
        + "</div></div></div>");
        
        db.transaction(function (tx) {
            tx.executeSql('INSERT INTO ChatRecords(userId,sendUserId,fullname,content) VALUES("' + userObj.UserID + '","' + msgObj.user.UserID + '","' + msgObj.user.FullName + '","' + msgObj.msg + '")');
        });
    }
    else {
        $("#div_msg").append("<div class='row-margin'>" 
        + "<img src='" + userAvatar + "' style='height:40px;width:40px;position:relative'/>" 
        + "<span style='left:10px;position:relative'>" + msgObj.user.FullName + "</span>" 
        + "<div class='demo clearfix'>" 
        + "<span class='triangle'></span>" 
        + "<div class='article'>" + msgObj.msg 
        + "</div></div></div>");
        
        db.transaction(function (tx) {
            tx.executeSql('INSERT INTO ChatRecords(userId,sendUserId,fullname,content) VALUES("' + userObj.UserID + '","' + msgObj.user.UserID + '","' + msgObj.user.FullName + '","' + msgObj.msg + '")');
        });
    }
    
    var objDiv = document.getElementById("div_msgbody");
    objDiv.scrollTop = objDiv.scrollHeight;
});

其实这里不过是一个拼message的过程,如果发送者是本人,则消息靠右显示,否则靠左显示。看看James和lilei的聊天。

wKioL1eDvFLChW-nAAEI2Pjo2wY690.png

wKioL1eDvISxYZWoAAEF5YOxGPs308.png

看到了吧,聊天聊的很Happy。OK,上面大家是不是看到了一段类似sql的代码,不错,就是将聊天信息存到本地WebSQL sqlite数据库。

db.transaction(function (tx) {
            tx.executeSql('INSERT INTO ChatRecords(userId,sendUserId,fullname,content) VALUES("' + userObj.UserID + '","' + msgObj.user.UserID + '","' + msgObj.user.FullName + '","' + msgObj.msg + '")');
        });

在使用之前我们需要首先连接数据库创建表。

var db = openDatabase('ChatHistory', '2.0', 'chat records', 10 * 1024 * 1024);
db.transaction(function (tx) {
    tx.executeSql('CREATE TABLE IF NOT EXISTS ChatRecords (id INTEGER PRIMARY KEY AUTOINCREMENT,userId TEXT NOT NULL DEFAULT "",sendUserId TEXT NOT NULL DEFAULT "",fullname TEXT NOT NULL DEFAULT "",content TEXT NOT NULL DEFAULT "",indate DATETIME default CURRENT_TIMESTAMP)');
});

确实和sqlSever的语法有点像,我们看一下存储到本地webSQL的聊天记录,google Chrome,按F12

wKiom1eDvhOAWuv8AAFYusu4uNo881.png

看到了吧,聊天记录已经被存储下来,由于我是一台机器,一个浏览器开两个tab页,所以这里的聊天记录就是两份,一个是发送人的,一个是接收人的。大家注意这里还有张表,sqlite_sequence,我们的主键id定义为自增列,所以这张表存储的是我们的自增列(id)的最大值。

wKioL1eDvwDz8K2YAABqyhD4c2k819.png

最大是54,和我们表ChatRecords中的最大值相等。


OK,本篇文章到这里就要和大家说再见了,下节我们会讲拦截器,log4js,服务端自动编译刷新。

本文出自 “技术创造价值” 博客,请务必保留此出处http://leelei.blog.51cto.com/856755/1825565

本页内容版权归属为原作者,如有侵犯您的权益,请通知我们删除。
预备工作: OS:Centos7.1 DATABASE:mysql-5.6.30.tar.gz 1. 创建mysql帐号 创建用户和用户组 [root@localhost~]#groupaddmysql[root@localhost~]#useradd-gmysqlmysql[root@localhost~]#passwdmysql 2. 验证安装包 解压mysql源码包 mysql-5.6.30.tar.gz [root@localhost~]#mkdir-p/opt/mysql-5.6/[root@l
CND的简单了解: 内容分发网络(CDN)是一种新型网络构建方式,它是为能在传统的IP网发布宽带丰富媒体而特别优化的网络覆盖层;而从广义的角度,CDN代表了一种基于质量与秩序的网络服务模式。 CDN是构建在网络之上的内容分发网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。CDN的关键技术主要有内容存储和分发技术。 CDN的基本原理是广泛采用各种缓存服务器,将这些缓存服务器分布到用户访问相对集中的地区或网

zabbix如何监控WEB应用性能 - 2016-07-18 14:07:33

HTTP服务目前最流行的互联网应用之一,如何监控服务的健康状态对系统运维来说至关重要。 Zabbix本身提供了对WEB应用程序的监控,比如监控WEB程序的Download Speed,Response Time和Response Code等性能指标,但是配置起来比较繁琐和复杂。下面通过 python pycurl模块来获取HTTP响应时间,下载速度,状态吗等性能指标。然后通过zabbix trapper的方式来监控WEB应用的性能。 Zabbix trapper监控是客户端收集监控数据,然后以zabbix

haproxy实现mysql的读负载均衡 - 2016-07-18 14:07:19

接前一篇博客keepalived+Master-Master-Slave实现双主高可用及读写分离 地址 http://lee90.blog.51cto.com/10414478/1825478 上一篇博文里面,我们只用了一个slave节点。 实际生产环境中,我们肯定有多个 slave 节点负责读数据库。 假设我们还有一个配置好的 slave , IP 为 192.168.2.15 。那么现在的情况是: Master1 : 192.168.2.13 Master2 : 192.168.2.14 VIP :
这节来看看如何使用 PowerShell 在AWS里面创建CloudFront Distributions. CloudFront是AWS提供的CDN服务,允许创建一个分布点指向S3 或者Web server,各地的DNS会自动解析到最近的边缘服务器上,以便实现最佳访问速度。 具体的图像界面操作可以参考 http://beanxyz.blog.51cto.com/5570417/1532813 下面看看PowerShell如何操作。 首先需要有一个S3 bucket(前面已经创建过了),然后我上传一个图片
最近西安的天气真他妈的热,感觉还是青海的天气美,最高温28度。上周逛了青海湖,感觉还是意犹未尽,其实我还是很喜欢去一趟西藏的,但是考虑到花费也没人陪我,我暂时放弃这个念头。计划去一下重庆或者甘南,也许是现实的。 OK,废话不多说,今天我们来看一下Excel在线部分的文件和文件组。首先我们来看一下页面,调一下胃口。俗话说无图无真相,先看图。 没错,还是Telerik Kendo UI,其实我面试的时候当听到别人说自己用的是EasyUI和ExtJs的时候,我就不那么上心,但是如果有人用的是Kendo UI f

shell快速迁移海量文件 - 2016-07-11 22:07:13

业务需求:需要把一个目录下的1000多万个文件迁移到远程机器 思路:用wget来把文件一个一个的迁移过去,因为文件数量比较大,如果一下在循环操作,会非常慢。所以分批操作,采用化整为零的方法。 #!/bin/shhome=/usr/local/www/skate/image63delbackcd$homeif[`pwd`==$home];thena="1100000020000003000000400000050000006000000700000080000009000000"forbin$adoc=`e

自动升级OPENSSH shell脚本 - 2016-07-11 19:07:23

由于管理着两百多台Linux服务器,一个人搞这么多机器的安全加固比较累,因此在学习了shell脚本之后果断的写了一些常用脚本做一些系统日常维护,本文OPENSSH的升级是博主本人几乎每两三个月就要做一次升级的,没办法绿盟安全扫描系统总是扫描到相关的高危漏洞,再就是OPENSSH版本更新的也比较频繁,因此不偷懒几乎没法活了。废话不说了,在这里贴下脚本,已经在线上服务器上执行了上百次了,应该不会有什么问题。 #!/bin/bash########################################

python for循环remove同一个list - 2016-07-11 19:07:23

下午在用python将Linux的conf配置文件转化成字典dict时遇到了一个奇怪的问题,原先conf配置文件中没有注释行(以#开头的行),后来为了避免这种情况,添加了一个对以#开头的行删除的操作。 实践结果颠覆了已有的认知,直接上代码示例。 代码片段1 #!/usr/bin/python#encoding:utf-8#-*-coding:utf8-*-importrelist_to_test=['#','#conf','NAME="Ubuntu"','VERSION="14.04.3LTS,Trust
豆子继续来看看PowerShell 如何管理 AWS的虚拟网络 VPC。 网上我没有找到快速上手的文档,相关的命令主要来自官方api文档和get-command的搜索。 http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Route_Tables.html#route-tables-api-cli VPC的配置向导里面默认给了4种场景的配置方案,下面我以第一种最简单的场景为例,用PowerShell配置一下。 场景如下,简单的说,我需要配置一