Nodejs实现多房间简易聊天室功能


Posted in NodeJs onJune 20, 2017

1、前端界面代码

前端不是重点,够用就行,下面是前端界面,具体代码可到github下载。

2、服务器端搭建

本服务器需要提供两个功能:http服务和websocket服务,由于node的事件驱动机制,可将两种服务搭建在同一个端口下。

1、包描述文件:package.json,这里用到了两个依赖项,mime:确定静态文件mime类型,socket.io:搭建websocket服务,然后使用npm install  安装依赖

{
 "name": "chat_room",
 "version": "1.0.0",
 "description": "this is a room where you can chat with your friends",
 "main": "index.js",
 "scripts": {
  "test": "echo \"Error: no test specified\" && exit 1"
 },
 "author": "sfs",
 "license": "ISC",
 "dependencies": {
  "socket.io":"2.0.3",
  "mime":"1.3.6"
 }
}

2、http服务器

http服务主要是给web浏览器提供静态文件,既浏览器发来一个请求,服务器返回一个响应。

const 
  http=require('http'),
  fs=require('fs'),
  path=require('path'),
  mime=require('mime'),
  chatServer=require('./lib/chat_server');

var cache={};//缓存静态文件内容
//发送错误响应
function send404(response){
  response.writeHead(404,{'Content-Type':'text/plain'});
  response.write('Error 4.4:文件未找到。');
  response.end();
}
//发送文件内容
function sendFile(response,filePath,fileContents){
  response.writeHead(
    200,
    {"content-Type":mime.lookup(path.basename(filePath))}
  );
  response.end(fileContents);
}
//查找文件
function serveStatic(response,cache,absPath){
  if(cache[absPath]){
    sendFile(response,absPath,cache[absPath]);
  }else{
    fs.exists(absPath,function(exists){
      if(exists){
        fs.readFile(absPath,function(err,data){
          if(err){
            send404(response);
          }else{
            cache[absPath]=data;
            sendFile(response,absPath,data);
          }
        });
      }else{
        send404(response);
      }
    });
  }
}
//入口
var server=http.createServer(function(request,response){
  var filePath=false;
  console.log(`new request for ${request.url}`);
  if(request.url==='/'){
    filePath='public/index.html';
  }else{
    filePath='public'+request.url;
  }

  var absPath='./'+filePath;
  serveStatic(response,cache,absPath);
});
server.listen(3000,function(){
  console.log("the server is listening on prot 3000.");
});
chatServer.listen(server); //websocket服务也绑定到该端口上

3、socket服务

socket.io提供了开箱既用的虚拟通道,所以不需要任务手动转发消息到已连接的的用户,可以使用 socket.broadcast.to(room).emit('message','hello'); room为某个聊天室id

const 
  socketio=require('socket.io');
var io,
  guestNumber=1, //用户编号
  nickNames={},  //socket id对应的nickname
  namesUsed={},  //所有已使用的nickname
  allRooms={},  //聊天室--人数
  currentRoom={}; //sockid--聊天室
module.exports.listen=function(server){
  io=socketio.listen(server);
  io.serveClient('log level',1);
  io.sockets.on('connection',function(socket){
    guestNumber=assignGuestName(socket,guestNumber,nickNames);
    joinRoom(socket,'Lobby');
    handleMessageBroadcasting(socket,nickNames);
    handleNameChangeAttempts(socket,nickNames,namesUsed);
    handleRoomJoining(socket);
    socket.on('rooms',function(){
      socket.emit('rooms',JSON.stringify(allRooms));
    });
    handleClientDisconnection(socket,nickNames,namesUsed);
  });
};
//新socket连入,自动分配一个昵称
function assignGuestName(socket,guesetNumber,nickNames){
  var name='Guest'+guestNumber;
  nickNames[socket.id]=name;
  socket.emit('nameResult',{
    success:true,
    name:name
  });
  namesUsed[name]=1;
  return guestNumber+1;
}
//加入某个聊天室
function joinRoom(socket,room){
  socket.join(room);
  var num=allRooms[room];
  if(num===undefined){
    allRooms[room]=1;
  }else{
    allRooms[room]=num+1;
  }
  currentRoom[socket.id]=room;
  socket.emit('joinResult',{room:room});
  socket.broadcast.to(room).emit('message',{
    text:nickNames[socket.id]+' has join '+room+'.'
  });
  var usersinRoom=io.sockets.adapter.rooms[room];
  if(usersinRoom.length>1){
    var usersInRoomSummary='Users currently in '+room+' : ';
    for(var index in usersinRoom.sockets){
      if(index!=socket.id){
        usersInRoomSummary+=nickNames[index]+',';
      }
    }
    socket.emit('message',{text:usersInRoomSummary}); 
  }
}
//修改昵称
function handleNameChangeAttempts(socket,nickNames,namesUsed){
  socket.on('nameAttempt',function(name){
    if(name.indexOf('Guest')==0){
      socket.emit('nameResult',{
        success:false,
        message:'Names cannot begin with "Guest".'
      });
    }else{
      if(namesUsed[name]==undefined){
        var previousName=nickNames[socket.id];
        delete namesUsed[previousName];
        namesUsed[name]=1;
        nickNames[socket.id]=name;
        socket.emit('nameResult',{
          success:true,
          name:name
        });
        socket.broadcast.to(currentRoom[socket.id]).emit('message',{
          text:previousName+' is now known as '+name+'.'
        });
      }else{
        socket.emit('nameResult',{
          success:false,
          message:'That name is already in use.' 
        });
      }
    }
  });                                    
}
//将某个用户的消息广播到同聊天室下的其他用户
function handleMessageBroadcasting(socket){
  socket.on('message',function(message){
    console.log('message:---'+JSON.stringify(message));
    socket.broadcast.to(message.room).emit('message',{
      text:nickNames[socket.id]+ ': '+message.text
    });
  });
}
//加入/创建某个聊天室
function handleRoomJoining(socket){
  socket.on('join',function(room){
    var temp=currentRoom[socket.id];
    delete currentRoom[socket.id];
    socket.leave(temp);
    var num=--allRooms[temp];
    if(num==0)
      delete allRooms[temp];
    joinRoom(socket,room.newRoom);
  });
}
//socket断线处理
function handleClientDisconnection(socket){
  socket.on('disconnect',function(){
    console.log("xxxx disconnect");
    allRooms[currentRoom[socket.id]]--;
    delete namesUsed[nickNames[socket.id]];
    delete nickNames[socket.id];
    delete currentRoom[socket.id];
  })
}

3、客户端实现socket.io

1、chat.js处理发送消息,变更房间,聊天命令。

var Chat=function(socket){
  this.socket=socket;//绑定socket
}
//发送消息
Chat.prototype.sendMessage=function(room,text){
  var message={
    room:room,
    text:text
  };
  this.socket.emit('message',message);
};
//变更房间
Chat.prototype.changeRoom=function(room){
  this.socket.emit('join',{
    newRoom:room
  });
};
//处理聊天命令
Chat.prototype.processCommand=function(command){
  var words=command.split(' ');
  var command=words[0].substring(1,words[0].length).toLowerCase();
  var message=false;
  switch(command){
    case 'join':
      words.shift();
      var room=words.join(' ');
      this.changeRoom(room);
      break;
    case 'nick':
      words.shift();
      var name=words.join(' ');
      this.socket.emit('nameAttempt',name);
      break;
    default:
      message='Unrecognized command.';
      break;
  }
  return message;
};

2、chat_ui.js 处理用户输入,根据输入调用chat.js的不同方法发送消息给服务器

function divEscapedContentElement(message){
  return $('<div></div>').text(message);
}
function divSystemContentElement(message){
  return $('<div></div>').html('<i>'+message+'</i>');
}
function processUserInput(chatApp,socket){
  var message=$('#send-message').val();
  var systemMessage;
  if(message.charAt(0)=='/'){
    systemMessage=chatApp.processCommand(message);
    if(systemMessage){
      $('#messages').append(divSystemContentElement(systemMessage));
    }
  }else{
    chatApp.sendMessage($('#room').text(),message);
    $('#messages').append(divSystemContentElement(message));
    $('#messages').scrollTop($('#messages').prop('scrollHeight'));
  }
  $('#send-message').val('');
}

3、init.js客户端程序初始化   创建一个websocket连接,绑定事件。

if(window.WebSocket){
  console.log('This browser supports WebSocket');
}else{
  console.log('This browser does not supports WebSocket');
}
var socket=io.connect();
$(document).ready(function(){
  var chatApp=new Chat(socket);
  socket.on('nameResult',function(result){
    var message;
    if(result.success){
      message='You are known as '+result.name+'.';
    }else{
      message=result.message;
    }
    console.log("nameResult:---"+message);
    $('#messages').append(divSystemContentElement(message));
    $('#nickName').text(result.name);
  });
  socket.on('joinResult',function(result){
    console.log('joinResult:---'+result);
    $('#room').text(result.room);
    $('#messages').append(divSystemContentElement('Room changed.'));
  });
  socket.on('message',function(message){
    console.log('message:---'+message);
    var newElement=$('<div></div>').text(message.text);
    $('#messages').append(newElement);
    $('#messages').scrollTop($('#messages').prop('scrollHeight'));
  });
  socket.on('rooms',function(rooms){
    console.log('rooms:---'+rooms);
    rooms=JSON.parse(rooms);
    $('#room-list').empty();
    for(var room in rooms){
      $('#room-list').append(divEscapedContentElement(room+':'+rooms[room]));
    }
    $('#room-list div').click(function(){
      chatApp.processCommand('/join '+$(this).text().split(':')[0]);
      $('#send-message').focus();
    });
  });
  setInterval(function(){
    socket.emit('rooms');
  },1000);
  $('#send-message').focus();
  $('#send-button').click(function(){
    processUserInput(chatApp,socket);
  });
});

完整代码,可到https://github.com/FleyX/ChatRoom 下载。

以上所述是小编给大家介绍的Nodejs实现多房间简易聊天室功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

NodeJs 相关文章推荐
nodejs的require模块(文件模块/核心模块)及路径介绍
Jan 14 NodeJs
nodejs 整合kindEditor实现图片上传
Feb 03 NodeJs
nodejs multer实现文件上传与下载
May 10 NodeJs
nodejs6下使用koa2框架实例
May 18 NodeJs
基于nodejs 的多页面爬虫实例代码
May 31 NodeJs
nodejs使用express获取get和post传值及session验证的方法
Nov 09 NodeJs
nodejs async异步常用函数总结(推荐)
Nov 17 NodeJs
nodejs取得当前执行路径的方法
May 13 NodeJs
Nodejs 发布自己的npm包并制作成命令行工具的实例讲解
May 15 NodeJs
nodejs基础之多进程实例详解
Dec 27 NodeJs
nodejs各种姿势断点调试的方法
Jun 18 NodeJs
浅谈JS和Nodejs中的事件驱动
May 05 NodeJs
NodeJS 实现手机短信验证模块阿里大于功能
Jun 19 #NodeJs
手把手教你把nodejs部署到linux上跑出hello world
Jun 19 #NodeJs
CentOS 安装NodeJS V8.0.0的方法
Jun 15 #NodeJs
详解Nodejs之npm&amp;package.json
Jun 15 #NodeJs
详解nodejs模板引擎制作
Jun 14 #NodeJs
Nodejs回调加超时限制两种实现方法
Jun 09 #NodeJs
nodeJS实现路由功能实例代码
Jun 08 #NodeJs
You might like
探讨php中header的用法详解
2013/06/07 PHP
PHP代码优化之成员变量获取速度对比
2014/02/28 PHP
php中error与exception的区别及应用
2014/07/28 PHP
jquery 合并内容相同的单元格(示例代码)
2013/12/13 Javascript
JavaScript的事件代理和委托实例分析
2015/03/25 Javascript
jQuery子窗体取得父窗体元素的方法
2015/05/11 Javascript
javascript实现仿腾讯游戏选择
2015/05/14 Javascript
基于JavaScript实现移除(删除)数组中指定元素
2016/01/04 Javascript
JavaScript实现数据类型的相互转换
2016/03/06 Javascript
轻松掌握JavaScript策略模式
2016/08/25 Javascript
javascript匿名函数中的'return function()'作用
2018/10/15 Javascript
微信小程序自定义弹窗实现详解(可通用)
2019/07/04 Javascript
[40:48]DOTA2上海特级锦标赛D组败者赛 Liquid VS COL第二局
2016/02/28 DOTA
Python中多线程及程序锁浅析
2015/01/21 Python
python中argparse模块用法实例详解
2015/06/03 Python
python实现的简单RPG游戏流程实例
2015/06/28 Python
基于python实现的抓取腾讯视频所有电影的爬虫
2016/04/22 Python
python利用拉链法实现字典方法示例
2017/03/25 Python
Python-接口开发入门解析
2019/08/01 Python
Python requests及aiohttp速度对比代码实例
2020/07/16 Python
python 如何对logging日志封装
2020/12/02 Python
js实现移动端H5页面手指滑动刻度尺功能
2017/11/16 HTML / CSS
localStorage、sessionStorage使用总结
2017/11/17 HTML / CSS
莫斯科的韩国化妆品店:Sifo
2019/12/04 全球购物
L’Artisan Parfumeur官网:法国香水品牌
2020/08/11 全球购物
药物学专业学生的自我评价
2013/10/27 职场文书
商务英语毕业生自荐信范文
2013/11/08 职场文书
大学生职业生涯设计书
2014/01/02 职场文书
绩效工资实施方案
2014/03/15 职场文书
竞选劳动委员演讲稿
2014/04/28 职场文书
村干部培训方案
2014/05/02 职场文书
任命书怎么写
2014/06/04 职场文书
商业用房租赁协议书
2014/10/13 职场文书
肖申克的救赎观后感
2015/06/02 职场文书
python requests模块的使用示例
2021/04/07 Python
python实现双向链表原理
2022/05/25 Python