前言

博客园的样式真心不会用啊,看着大大们的博客各种好看,心里无奈啊,只能慢慢摸索了。

最近的项目nodejs+wcf+app,app直接从wcf服务获取数据,nodejs作为单独的服务器为app提供图形服务和聊天室功能。主要架构如下

这一篇主要讲的是如何用nodejs+socketio实现一个基础的聊天室。其实这也是我第一个nodejs项目,真个知识体系还不太完整,遇到问题就度娘,有说错的地方请大家指正。

聊天室功能点概要

1.在线和离线人员管理

2.消息的发送,广播以及离线消息

3.音频文件,图片的发送

具体实现

首先整个聊天系统因为业务关系,容量是固定的基本不会超过1000人,实际情况在100人左右。如此轻量级的数据我选择用文本文件来记录所有的离线消息和人员列表。mongodb想用来着,留着下次处女作吧。

这里人员列表记录所有进入过聊天系统的人员,每次人员登录进入系统就将获得这个人员列表作为客户端的聊天对象。并且人员的上线和下线将触发广播in和out消息,让在线成员刷新人员列表。

消息的组成包括:from,to,body,type等,按照约定如果to为空则作为群消息进行广播发送,否则指定人员发送信息(如果该人员online属性为false则存入离线消息文件,待该人员进入聊天室的时候一起拉取离线消息)

文件的发送,实际上是文件上传和发送文件地址的过程。将type和body组合一下就可以了。

整个通讯直接用socketio,包括android端和ios端都直接调用socketid对应版本类库,完全没有学习成本。上手非常简单。

下面直接上代码:

/**
* Created by qyz on 14-3-18.
*/
var fs = require('fs');
var path = require('path');
//许可的后缀名
var AllowExt=[ "amr", "jpg", "jpeg", "gif", "png", "swf"];
var ImageExt=[ "jpg", "jpeg", "gif", "png"];
var exec = require('child_process').exec;
var path = require('path');
var fs = require('fs');
if(!fs.existsSync(__dirname + '/../public/chatfiles')){
fs.mkdirSync(__dirname + '/../public/chatfiles',0755);
} if(!fs.existsSync(__dirname + '/../public/chatfiles/emplist.json')){
fs.writeFileSync(__dirname + '/../public/chatfiles/emplist.json',"")
/*fs.open(__dirname + '/../public/chatfiles/emplist.json', 'w+', 0666, function(err, fd){
fs.close(fd);
});*/
}
var allempList=readAllEmpList();//人员列表 exports.connection=function(socket){
//实名注册事件
socket.on('login',function(msg){
var json=JSON.parse(msg);
socket.name=json.card;
isExists(json,socket); //加入连接列表
socket.broadcast.emit('in','{"data":'+msg+'}');//通知用户登入
socket.emit('emplist','{"data":'+ BroadCastPeopleList()+'}');//获取用户列表 OffLineMessage(socket,json); //拉取离线消息 });
//接收消息事件
socket.on('emplist', function (msg) {
socket.emit('emplist','{"data":'+ BroadCastPeopleList()+'}');//获取用户列表
});
//接收消息事件
socket.on('message', function (msg) {
OnMessage(socket,msg);
}); //断开连接事件
socket.on('disconnect', function () {
socket.broadcast.emit('out','{"data":'+socket.name+'}');
Exit(socket);
});
} ///判断列表中是否已经存在该socket,不存在则加入
function isExists(json,socket){
var bo = false;
for(var i=0;i<allempList.length;i++)
{
if(allempList[i].card== json.card)//如果存在则 认为在线
{
//判断如果卡号更改了人员或者部门则要刷新
if(allempList[i].name!=json.name||allempList[i].dept!=json.dept)
{
allempList[i].name=json.name;
allempList[i].dept=json.dept;
var arr=[];
for(var j=0;j<allempList.length;j++)
{
arr.push(new EmpListEasy(allempList[i]));
}
if(arr!=null)
{
fs.writeFileSync(__dirname + '/../public/chatfiles/emplist.json',JSON.stringify(arr));
}
}
allempList[i].socket=socket;
allempList[i].online=true;
bo = true;
break;
}
}
if(!bo){
allempList.push(new EmpList( json.card, json.name, json.dept,true,socket));
var arr=[];
for(var j=0;j<allempList.length;j++)
{
arr.push(new EmpListEasy(allempList[i]));
}
if(arr!=null)
{
fs.writeFileSync(__dirname + '/../public/chatfiles/emplist.json',JSON.stringify(arr));
}
}
console.log(JSON.stringify(json) +'创建连接');
} ///断开连接 删除列表
function Exit(socket){
console.log(socket.name+'断开连接');
for(var i=0;i<allempList.length;i++)
{
if(allempList[i].card== socket.name)
{
allempList[i].online=false;
allempList[i].socket=null;
break;
}
}
socket = null;
} ///广播发送所有人员名单
function BroadCastPeopleList(){
var arr=[];
for(var i=0;i<allempList.length;i++)
{
arr.push(new EmpListEasy(allempList[i]));
}
return JSON.stringify(arr);
} ///发送信息
function OnMessage(socket,msg){
var message = JSON.parse(msg);
if(message!=null){
var info=JSON.stringify(new MessageModel(message));
console.log('接收信息 ', info);
if(message.to=="")//广播发送信息
{
socket.broadcast.emit('message','{"data":'+info+'}');
}
else
{
for(var i=0;i<allempList.length;i++)
{
if(allempList[i].card==message.to)//找到对应的人,判断是离线还是在线,如果离线则保存,在线则发送
{
if(allempList[i].online)
{
allempList[i].socket.emit('message','{"data":'+info+'}');
}
else{
if(!fs.existsSync(__dirname + '/../public/chatfiles/'+allempList[i].card+'.json'))
{
fs.writeFile(__dirname + '/../public/chatfiles/'+allempList[i].card+'.json',info+'||', function(err){
if(err)
{
console.log(err);
}
});
}
else{
fs.appendFile(__dirname + '/../public/chatfiles/'+allempList[i].card+'.json',info+'||', function(err){
if(err)
{
console.log(err);
}
} );
}
}
break;
}
}
}
}
} //读取人员列表
function readAllEmpList(){
var fs = require('fs');
var path = require('path');
var arr=[];
var data= fs.readFileSync(__dirname + '/../public/chatfiles/emplist.json');
if(data!=null&&data!="" )
{
var json = JSON.parse(data);
if(json!=null && json.length>0)
{
for(var i=0;i<json.length;i++)
{
arr.push(new EmpList(json[i].card,json[i].name,json[i].dept,false,null));
}
}
}
return arr;
} //拉取离线消息
function OffLineMessage(socket,json){
if(fs.existsSync(__dirname + '/../public/chatfiles/'+json.card+'.json'))
{
var info= fs.readFileSync(__dirname + '/../public/chatfiles/'+json.card+'.json', 'utf-8');
if(info!="")
{
var strarr = info.split("||");
var arr=[];
for(var i=0;i<strarr.length;i++)
{
if(strarr[i]!=""){
arr.push(strarr[i]);
}
}
socket.emit('offline','{"data":'+JSON.stringify(arr).replace(/\\/g, "")+'}');//发送离线消息
fs.writeFile(__dirname + '/../public/chatfiles/'+json.card+'.json','',null);
// fs.unlinkSync(__dirname + '/../public/chatfiles/'+json.card+'.json');//删除离线文件
}
}
} /***************实体类********************/
function EmpListEasy(list){
this.card =list.card;
this.name =list.name;
this.dept = list.dept;
this.online=list.online;
}
function EmpList(card,name,dept,online,socket){
this.card = card;
this.name = name;
this.dept = dept;
this.online = online;
this.socket = socket;
} function MessageModel(message){
this.from=message.from;
this.to=message.to;
this.body=message.body;
this.time=new Date();
this.name=message.name;
this.dept=message.dept;
this.type=message.type;
this.filetype=message.filetype;
} /***************公共方法********************/
//删除array中项 根据索引
function removeArray(ob, index) {
if (isNaN(index) || index > ob.length) {
return false;
}
for (var i = 0, n = 0; i < ob.length; i++) {
if (ob[i] != ob[index]) {ob[n++] = ob[i];
}
}
ob.length -= 1;
}
//删除array中项 根据id
function removeArrayByID(ob, id) {
for (var i = 0; i < ob.length; i++) {
if (ob[i].id == id) {removeArray(ob, i);
break;
}
}
} exports.savefile=function(req,res){
var body = '';
var header = '';
var content_type = req.headers['content-type'];
var boundary = content_type.split(';')[1].split('=')[1];
var content_length = parseInt(req.headers['content-length']);
var headerFlag = true;
var filename = 'dummy.bin';
var filenameRegexp = /filename="(.*)"/m;
req.on('data', function(raw) {
var i = 0;
while (i < raw.length)
if (headerFlag) {
var chars = raw.slice(i, i+4).toString();
if (chars === '\r\n\r\n') {
headerFlag = false;
header = raw.slice(0, i+4).toString();
i = i + 4;
// get the filename
var result = filenameRegexp.exec(header);
if (result[1]) {
filename = result[1];
}
}
else {
i += 1;
}
}
else {
// parsing body including footer
body += raw.toString('binary', i, raw.length);
i = raw.length;
}
});
req.on('end', function() {
// removing footer '\r\n'--boundary--\r\n' = (boundary.length + 8)
body = body.slice(0, body.length - (boundary.length + 8))
if(!fs.existsSync(__dirname+'/../public/upload/'+CurentMonth()))
{
fs.mkdirSync(__dirname+'/../public/upload/'+CurentMonth());
}
var timepath=CurentMonth()+'/'+CurentDay();
if(!fs.existsSync(__dirname+'/../public/upload/'+timepath))
{
fs.mkdirSync(__dirname+'/../public/upload/'+timepath);
}
var ext = filename.split('.')[1];
if(AllowExt.indexOf(ext) == -1)//不允许的后缀名文件
{
var result = new Result();
result.state = 0;
result. info = "文件格式不正确";
result.data = null;
res.write(JSON.stringify(result));
res.end();
}
else
{
fs.writeFile(__dirname+'/../public/upload/'+timepath+'/' + filename, body, 'binary',function(){
var data = new Data();//返回body数据
data.localname=filename;
data.url='upload/'+timepath+'/' + filename;
data.surl='';
var result = new Result();//返回的数据包,包含头
result.state = 1;
result.info = "";
result.data = data;
if(ImageExt.indexOf(ext)!=-1)//图片文件则保存缩略图
{
data.surl='upload/'+timepath+'/s_' + filename;
//生成缩略图
exec(__dirname+'/../ImageMagick/convert -resize 100 '+__dirname+'/../public/'+data.url +' '+__dirname+'/../public/'+ data.surl , function(err){
if(err){
result.state = 0;
result. info = "生成缩略图失败:"+err;
result.data = null;
res.write(JSON.stringify(result));
res.end();
}
else{
res.write(JSON.stringify(result));
res.end();
}
});
}
else
{
res.write(JSON.stringify(result));
res.end();
} });
}
})
} //实体类
//获取当前日期作为文件夹名称
function CurentMonth(){
var now = new Date();
var year = now.getFullYear(); //年
var month = now.getMonth() + 1; //月 var clock = year ;
if(month < 10)
clock += "0";
clock += month ;
return(clock);
}
function CurentDay(){
var now = new Date();
return now.getDate(); //日
}
//返回包的实体类
function Result()
{
this.state=1;
this.info="";
this.data=null;
}
//返回包中的body数据
function Data(){
this.localname="";
this.url="";
this.surl="";
}

实现代码

其中上传图片生成缩略图部分,直接参考我上一篇博客。

nodejs 聊天室简单实现的更多相关文章

  1. 基于React,Redux以及wilddog的聊天室简单实现

    本文主要是使用ReactJs和Redux来实现一个聊天功能的页面,页面极其简单.使用React时间不长,还是个noob,有不对之处欢迎大家吐槽指正. 还要指出这里没有使用到websocket等技术来实 ...

  2. Socket.IO聊天室~简单实用

    小编心语:大家过完圣诞准备迎元旦吧~小编在这里预祝大家元旦快乐!!这一次要分享的东西小编也不是很懂啊,总之小编把它拿出来是觉地比较稀奇,而且程序也没有那么难,是一个比较简单的程序,大家可以多多试试~ ...

  3. go 聊天室简单版总结

    /* * 思路:在登录成功时将用户的id存进在线用户列表中的key value中链接的ws为空,并保存用户的信息. * 当跳转到聊天室时,将用户和聊天室链接的ws存进在线用户列表中的 * 问题:如何在 ...

  4. websocket+node建立聊天室简单使用

    1.建立新的文件夹dome 2.执行 npm init加载package.json文件 3.node不支持websocket所以npm install  ws 下载 ws插件 4.建立index.ht ...

  5. 使用nodejs+express+socketio+mysql搭建聊天室

    使用nodejs+express+socketio+mysql搭建聊天室 nodejs相关的资料已经很多了,我也是学习中吧,于是把socket的教程看了下,学着做了个聊天室,然后加入简单的操作mysq ...

  6. Node.js下基于Express + Socket.io 搭建一个基本的在线聊天室

    一.聊天室简单介绍 采用nodeJS设计,基于express框架,使用WebSocket编程之 socket.io机制.聊天室增加了 注册登录模块 ,并将用户个人信息和聊天记录存入数据库. 数据库采用 ...

  7. SignalR 聊天室实例详解(服务器端推送版)

    翻译自:http://www.codeproject.com/Articles/562023/Asp-Net-SignalR-Chat-Room  (在这里可以下载到实例的源码) Asp.Net Si ...

  8. 采用PHP实现”服务器推”技术的聊天室

      传统的B/S结构的应用程序,都是采用”客户端拉”结束来实现客户端和服务器端的数据交换. 本文将通过结合Ticks(可以参看我的另外一篇文章:关于PHP你可能不知道的-PHP的事件驱动化设计),来实 ...

  9. 利用socket.io+nodejs打造简单聊天室

    代码地址如下:http://www.demodashi.com/demo/11579.html 界面展示: 首先展示demo的结果界面,只是简单消息的发送和接收,包括发送文字和发送图片. ws说明: ...

随机推荐

  1. PHP图像操作:3D图、缩放、旋转、裁剪、加入水印(一)

    来源:http://www.ido321.com/875.html 1.利用php gd库的函数绘制3D扇形统计图 1: <?php 2: header("content-type&q ...

  2. Expression-Based Access Control

    Expression-Based Access Control Spring Security 3.0 introduced the ability to use Spring EL expressi ...

  3. input输入框禁止显示历史记录

    有时我们在设计网页时不想让表单保存用户输入历史记录,比如一些隐私数据 <input name="test" type="text" id="te ...

  4. UVA 10168 Summation of Four Primes(数论)

    Summation of Four Primes Input: standard input Output: standard output Time Limit: 4 seconds Euler p ...

  5. nutch中bin/crawl和bin/nutch crawl的用法(转)

    针对上一篇文章中出现的问题:Command crawl is deprecated, please use bin/crawl instead错误信息,今天在官网上查阅了一下,进行了总结. 官网lin ...

  6. Docker、DAOCloud

    Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化.容器是完全使用沙箱机制,相互之间不会有任何 ...

  7. RxJava API使用示例

    概述 RxJava API示例代码,可离线查看rxjava1.0大部分API的marble图,描述,示例代码,并支持示例代码实时输出及展示执行结果. 详细 代码下载:http://www.demoda ...

  8. Coreseek-带中文分词的Sphinx

    Sphinx并不支持中文分词, 也就不支持中文搜索, Coreseek = Sphinx + MMSEG(中文分词算法) 1.下载 1).到官网下载 2).解压后有三个文件夹 csft-3.2.14: ...

  9. xcode9.2 objective-c install (mac 10.12.6)

    1. xcode下载: https://download.developer.apple.com/Developer_Tools/Xcode_9.2/Xcode_9.2.xip 2. 点击默认安装即可 ...

  10. 扩展方法 DataTable的ToList<T>

    using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.T ...