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=‘javascript:void(0)‘) Clear
  .row
   .col-md-10
    input#txt_msg.form-control(type=‘text‘ style=‘height:40px;resize:none‘ maxlength=200 placeholder=‘Input message here.‘)
   .col-md-2.right-align-text
    button#btn_send.k-button.k-primary(type=‘button‘ style=‘height:40px;width:100%‘)
     span.glyphicon.glyphicon-send
     span(style=‘margin-left:5px‘) Send
 .col-md-3
  .panel.panel-primary
   .panel-heading
    h3.panel-title(style=‘font-size:13px;‘) Members
   .panel-body.panel-inner-height(style=‘overflow:auto;‘)
    #div_users.panel-content(style=‘word-break: break-word;‘)
   .panel-footer
    .left-margin-10 
     span.text-color#total Member Count:0

#chat_historyWindow(style=‘display:none‘)
  #chat_historyContent.panel-content(style=‘word-wrap:break-word;word-break: break-word;‘)
span#notify

block scripts
 script(type=‘text/javascript‘ src=‘/javascripts/local/other/chat.js‘)

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

安装好之后,在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文件。

在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提示。

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

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的聊天。

看到了吧,聊天聊的很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

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

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

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

时间: 2024-10-18 13:40:41

Node.js 切近实战(十一) 之实时通讯的相关文章

Node.js 切近实战(二) 之图书管理系统

上一篇Node.Js切近实战讲述了如何在VS上搭建Node.Js开发环境,相信看过那篇博客的同学,你已经对Node.Js有了好感,多了解些技术还是很有必要的.所谓实战见真功,还是要实战才行.我看博客园上一些专家荣誉的人写的博客,全是理论,没有实战,没有实战就没有发言权.熟能生巧,实战出理论. 我们看一下项目结构 典型的NodeJs三层架构,controller,model,view,这里每个部分什么职能,我就不多说了. 首先我们看一下登录界面,先上图,我看博客的时候,会首先看博客中有没有图,比如

Node.js 切近实战(七) 之Excel在线(文件&文件组)

最近西安的天气真他妈的热,感觉还是青海的天气美,最高温28度.上周逛了青海湖,感觉还是意犹未尽,其实我还是很喜欢去一趟西藏的,但是考虑到花费也没人陪我,我暂时放弃这个念头.计划去一下重庆或者甘南,也许是现实的. OK,废话不多说,今天我们来看一下Excel在线部分的文件和文件组.首先我们来看一下页面,调一下胃口.俗话说无图无真相,先看图. 没错,还是Telerik Kendo UI,其实我面试的时候当听到别人说自己用的是EasyUI和ExtJs的时候,我就不那么上心,但是如果有人用的是Kendo

Node.js 切近实战(九) 之Excel在线(在线编辑)

最近实在是太想去西藏了,我自己总是喜欢人少的旅游地,喜欢一望无垠,喜欢蓝天白云大草原. 之前有一节我给大家讲过文件列表,如下,今天我们要讲的就是Excel在线编辑. 当我们双击文件图标的时候会跳转到一个Excel修改界面,如下. ok,这里我们使用的依然是Telerik Kendo UI中的SpreadSheet,看一下这个Spread Sheet是如何用的. 我们定义一个spreadsheet的div,我们看一下这个div怎么生成sheet. $("#spreadsheet").ke

Node.js 切近实战(十) 之Excel在线(共享文件)

本篇文章我就不罗嗦了,主要讲的是共享文件列表,主要功能就是查看别人共享的文件. 打开该界面,用户可以在左侧看到共享文件的人员的信息,点击该人可以查看该人共享了哪些文件.在Grid里面有查看文件修改记录,编辑,标记为星标文件等功能.OK,我们先看一下UI代码. div(style='padding:5px;')  include ../common/search.jade .row-margin  hr.panel-line   #splitter(style='height:750px;')  

Node.js 切近实战(一) 之环境搭建

哥们先来吐槽一下,最近面试别人的一些经历.有的小伙干了五年,这五年干的是同样的活,三层架构,ASP.NET MVC,Jquery.看简历还能用,结果面试中才知道这五年自己没写过泛型类,泛型方法,委托自定义事件也都没用过,还说抽象类中必须都是抽象方法才可以.还有一小伙,干了十年,泛型委托一概不清楚.最近有个干了9年的,也呆过几家外企,感觉还挺牛,来了之后果然牛,敲着个二郎腿,声音比我还大,搞得好像他在面试我.9年了,只会最常用的一些东西,asp.net mvc,asp.net webforms,W

Node.js 切近实战(十二) 之Linux部署

之前的话我们的项目都是跑在windows上,今天我们要将我们的程序跑到linxu机器上.在看linux部署之前,我们先看一下node.js类似于asp.net mvc的过滤器或者叫拦截器.在app.js中我们加入如下代码 var beforeRequest = function (req, res, next) {     if (req.originalUrl == '/'          || req.originalUrl == '/login'          || req.orig

Node.js 切近实战(二) 之图书管理系统(图书信息录入)

上一节我们讲了图书管理系统的登录,相信大家对jade模板和angular js也有了了解,今天的话我们来看一下图书信息录入.在这里我们准备使用Nosql数据库,本篇博客中使用的是MongoDB.OK.Mongo DB的安装我就不多说了,那么Node.Js平台使用mongoDB我们使用什么扩展包好呢,我觉得还是mongoose比较合适,封装了好多针对mongodb的方法,简单易用. 首先我们来看一下图书model的设计bookInfo.js. var mongoose = require('mon

Node.js 切近实战(八) 之Excel在线(文件权限)

最近美国又他妈的皮痒了,在南海找事,还说什么中国必须接受南海仲裁结果,我去你大爷的,你以为你是谁啊.说实话只要我们要决一死战的勇气,还管什么华盛顿航母,佛吉尼亚潜艇,大不了大家一起死,不,全世界一起死.怎么个死法,中国惹急了先给俄罗斯来几颗核弹,然后俄罗斯反击中国的同时,也会给欧洲扔几颗核弹,给美国扔很多核弹,然后欧洲英法会给其他国家扔核弹,美国给世界扔核弹,俄罗斯只给北冰洋扔就行了,中国给美国和太平洋扔就行了,这样世界就不复存在了. 今天我们来看一下文件权限管理,这个其实是对共享出去的文件的一

Node.js 切近实战(二) 之图书管理系统(图书查询)

最近又当上了Master,负责带项目,有时候,遇到的问题我很郁闷.比如一个Story,需求中说的是将单个修改改为批量修改,举个例子,商品信息修改,之前是用一个商品id修改,但是现在改成多个商品id修改.我的意思是直接将文本框宽度高度加大,支持回车换行就行了,然后再将API修改为支持批量查询.这个界面上上面是一个Grid,下面是一个表单,选择Grid的数据后,会加载到下面表单.只能加载一条下去,就因为这个,有人提出如果加载一个下去,那么大个文本框只显示一个选中的商品id,视觉上无法接受.说是要将选