前言

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

最近的项目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. activemq两种实现方式

    第一种:点对点 #发布者public class Producer { private static final String userName = ActiveMQXAConnectionFacto ...

  2. MySQL数据库如何与EXCEL的XLS格式相互转换

    1 将SQL导出为EXCEL方法,有如下数据库(my_impa),里面有两张表 2 如果是EXCEL格式,一定要勾选"将字段名称放在首行",否则待会儿导入的时候就需要你手工新建字段 ...

  3. .NET Framwork 之 托管代码的执行过程

    源代码代码第一次编译形成IL中间语言的托管代码,在运行时被Class Loader装载后进行JIT第二次编译形成托管的本地代码.在执行过程中,它会不断地检查当前我们执行的代码的安全性和规范性. Cla ...

  4. java中运算符与表达式

    运算符是用来完成一个动作的特定语言的语法记号. –赋值运算符 –增减运算符 –算术运算符 –关系运算符 –逻辑运算符 -位运算符 运算符 Java 加 + 减 - 乘 * 除 / 取模 % 1.整数运 ...

  5. 我装win8与win7双系统的血泪史

    前段时间教徒弟装系统,由于笔记本原带了win8,他不想换掉原来的系统.遂决定装个双系统.于是按照之前的一贯套路,但是出现了问题. 一. 首先遇到的问题是:如何进入BIOS,设置成U盘启动.Win XP ...

  6. ASCII、Unicode、UTF8编码类型的理解

    一.ASCII码        在计算机内部,所有的信息最终都表示为一个二进制的字符串.每一个二进制位(bit)有0和1两种状态,因此八个二进制位就可以组合出256种状态,这被称为一个字节(byte) ...

  7. 批量部署 自动化之 - [pssh](转)

    并行执行命令工具简介 作为运维工程师来讲,机器数量到一定级别的时候,批量运维和管理机器就是一件费神的事情,还好有很多可以批量并行执行命令的工具,比如 pssh , python fabrictaoba ...

  8. STL源码剖析(deque)

    deque是一个双向开口的容器,在头尾两端进行元素的插入跟删除操作都有理想的时间复杂度. deque使用的是分段连续线性空间,它维护一个指针数组(T** map),其中每个指针指向一块连续线性空间. ...

  9. Spring Boot(二)Application events and listeners

    一.自定义监听器: 1.创建: META-INF/spring.factories 2.添加: org.springframework.context.ApplicationListener=com. ...

  10. win10下Visual Studio 2015,C++ x64编译zmq

    PS.本人编译过程踩得坑,记录备忘 下载:(1)官网:http://zeromq.org/intro:get-the-software,有简明的编译方式,cmake的,这里不多赘述 (2)到GitHu ...