nodejs构建多房间简易聊天室

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浏览器提供静态文件,既浏览器发来一个请求,服务器返回一个响应。

 1 const 
 2     http=require('http'),
 3     fs=require('fs'),
 4     path=require('path'),
 5     mime=require('mime'),
 6     chatServer=require('./lib/chat_server');
 7 
 8 var cache={};//缓存静态文件内容
 9 //发送错误响应
10 function send404(response){
11     response.writeHead(404,{'Content-Type':'text/plain'});
12     response.write('Error 4.4:文件未找到。');
13     response.end();
14 }
15 //发送文件内容
16 function sendFile(response,filePath,fileContents){
17     response.writeHead(
18         200,
19         {"content-Type":mime.lookup(path.basename(filePath))}
20     );
21     response.end(fileContents);
22 }
23 //查找文件
24 function serveStatic(response,cache,absPath){
25     if(cache[absPath]){
26         sendFile(response,absPath,cache[absPath]);
27     }else{
28         fs.exists(absPath,function(exists){
29             if(exists){
30                 fs.readFile(absPath,function(err,data){
31                     if(err){
32                         send404(response);
33                     }else{
34                         cache[absPath]=data;
35                         sendFile(response,absPath,data);
36                     }
37                 });
38             }else{
39                 send404(response);
40             }
41         });
42     }
43 }
44 
45 
46 //入口
47 var server=http.createServer(function(request,response){
48     var filePath=false;
49     console.log(`new request for ${request.url}`);
50     if(request.url==='/'){
51         filePath='public/index.html';
52     }else{
53         filePath='public'+request.url;
54     }
55 
56     var absPath='./'+filePath;
57     serveStatic(response,cache,absPath);
58 });
59 server.listen(3000,function(){
60     console.log("the server is listening on prot 3000.");
61 });
62 chatServer.listen(server); //websocket服务也绑定到该端口上

  3、socket服务

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

  1 const 
  2     socketio=require('socket.io');
  3 
  4 var io,
  5     guestNumber=1,  //用户编号
  6     nickNames={},   //socket id对应的nickname
  7     namesUsed={},   //所有已使用的nickname
  8     allRooms={},    //聊天室--人数
  9     currentRoom={}; //sockid--聊天室
 10 
 11 module.exports.listen=function(server){
 12     io=socketio.listen(server);
 13     io.serveClient('log level',1);
 14     io.sockets.on('connection',function(socket){
 15         guestNumber=assignGuestName(socket,guestNumber,nickNames);
 16         joinRoom(socket,'Lobby');
 17         handleMessageBroadcasting(socket,nickNames);
 18         handleNameChangeAttempts(socket,nickNames,namesUsed);
 19         handleRoomJoining(socket);
 20         socket.on('rooms',function(){
 21             socket.emit('rooms',JSON.stringify(allRooms));
 22         });
 23         handleClientDisconnection(socket,nickNames,namesUsed);
 24     });
 25 };
 26 //新socket连入,自动分配一个昵称
 27 function assignGuestName(socket,guesetNumber,nickNames){
 28     var name='Guest'+guestNumber;
 29     nickNames[socket.id]=name;
 30     socket.emit('nameResult',{
 31         success:true,
 32         name:name
 33     });
 34     namesUsed[name]=1;
 35     return guestNumber+1;
 36 }
 37 //加入某个聊天室
 38 function joinRoom(socket,room){
 39     socket.join(room);
 40     var num=allRooms[room];
 41     if(num===undefined){
 42         allRooms[room]=1;
 43     }else{
 44         allRooms[room]=num+1;
 45     }
 46     currentRoom[socket.id]=room;
 47     socket.emit('joinResult',{room:room});
 48     socket.broadcast.to(room).emit('message',{
 49         text:nickNames[socket.id]+' has join '+room+'.'
 50     });
 51 
 52     var usersinRoom=io.sockets.adapter.rooms[room];
 53     if(usersinRoom.length>1){
 54         var usersInRoomSummary='Users currently in '+room+' : ';
 55         for(var index in usersinRoom.sockets){
 56             if(index!=socket.id){
 57                 usersInRoomSummary+=nickNames[index]+',';
 58             }
 59         }
 60         socket.emit('message',{text:usersInRoomSummary}); 
 61     }
 62 }
 63 //修改昵称
 64 function handleNameChangeAttempts(socket,nickNames,namesUsed){
 65     socket.on('nameAttempt',function(name){
 66         if(name.indexOf('Guest')==0){
 67             socket.emit('nameResult',{
 68                 success:false,
 69                 message:'Names cannot begin with "Guest".'
 70             });
 71         }else{
 72             if(namesUsed[name]==undefined){
 73                 var previousName=nickNames[socket.id];
 74                 delete namesUsed[previousName];
 75                 namesUsed[name]=1;
 76                 nickNames[socket.id]=name;
 77                 socket.emit('nameResult',{
 78                     success:true,
 79                     name:name
 80                 });
 81                 socket.broadcast.to(currentRoom[socket.id]).emit('message',{
 82                     text:previousName+' is now known as '+name+'.'
 83                 });
 84             }else{
 85                 socket.emit('nameResult',{
 86                     success:false,
 87                     message:'That name is already in use.'  
 88                 });
 89             }
 90         }
 91     });                                                                        
 92 }
 93 //将某个用户的消息广播到同聊天室下的其他用户
 94 function handleMessageBroadcasting(socket){
 95     socket.on('message',function(message){
 96         console.log('message:---'+JSON.stringify(message));
 97         socket.broadcast.to(message.room).emit('message',{
 98             text:nickNames[socket.id]+ ': '+message.text
 99         });
100     });
101 }
102 //加入/创建某个聊天室
103 function handleRoomJoining(socket){
104     socket.on('join',function(room){
105         var temp=currentRoom[socket.id];
106         delete currentRoom[socket.id];
107         socket.leave(temp);
108         var num=--allRooms[temp];
109         if(num==0)
110             delete allRooms[temp];
111         joinRoom(socket,room.newRoom);
112     });
113 }
114 //socket断线处理
115 function handleClientDisconnection(socket){
116     socket.on('disconnect',function(){
117         console.log("xxxx disconnect");
118         allRooms[currentRoom[socket.id]]--;
119         delete namesUsed[nickNames[socket.id]];
120         delete nickNames[socket.id];
121         delete currentRoom[socket.id];
122     })
123 }

3、客户端实现socket.io

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

 1 var Chat=function(socket){
 2     this.socket=socket;//绑定socket
 3 }
 4 //发送消息
 5 Chat.prototype.sendMessage=function(room,text){
 6     var message={
 7         room:room,
 8         text:text
 9     };
10     this.socket.emit('message',message);
11 };
12 //变更房间
13 Chat.prototype.changeRoom=function(room){
14     this.socket.emit('join',{
15         newRoom:room
16     });
17 };
18 //处理聊天命令
19 Chat.prototype.processCommand=function(command){
20     var words=command.split(' ');
21     var command=words[0].substring(1,words[0].length).toLowerCase();
22     var message=false;
23 
24     switch(command){
25         case 'join':
26             words.shift();
27             var room=words.join(' ');
28             this.changeRoom(room);
29             break;
30         case 'nick':
31             words.shift();
32             var name=words.join(' ');
33             this.socket.emit('nameAttempt',name);
34             break;
35         default:
36             message='Unrecognized command.';
37             break;
38     }
39     return message;
40 }; 

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

 1 function divEscapedContentElement(message){
 2     return $('<div></div>').text(message);
 3 }
 4 function divSystemContentElement(message){
 5     return $('<div></div>').html('<i>'+message+'</i>');
 6 }
 7 function processUserInput(chatApp,socket){
 8     var message=$('#send-message').val();
 9     var systemMessage;
10     if(message.charAt(0)=='/'){
11         systemMessage=chatApp.processCommand(message);
12         if(systemMessage){
13             $('#messages').append(divSystemContentElement(systemMessage));
14         }
15     }else{
16         chatApp.sendMessage($('#room').text(),message);
17         $('#messages').append(divSystemContentElement(message));
18         $('#messages').scrollTop($('#messages').prop('scrollHeight'));
19     }
20     $('#send-message').val('');
21 }

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

 1 if(window.WebSocket){
 2     console.log('This browser supports WebSocket');
 3 }else{
 4     console.log('This browser does not supports WebSocket');
 5 }
 6 var socket=io.connect();
 7 $(document).ready(function(){
 8     var chatApp=new Chat(socket);
 9     socket.on('nameResult',function(result){
10         var message;
11         if(result.success){
12             message='You are known as '+result.name+'.';
13         }else{
14             message=result.message;
15         }
16         console.log("nameResult:---"+message);
17         $('#messages').append(divSystemContentElement(message));
18         $('#nickName').text(result.name);
19     });
20 
21     socket.on('joinResult',function(result){
22         console.log('joinResult:---'+result);
23         $('#room').text(result.room);
24         $('#messages').append(divSystemContentElement('Room changed.'));
25     });
26 
27     socket.on('message',function(message){
28         console.log('message:---'+message);
29         var newElement=$('<div></div>').text(message.text);
30         $('#messages').append(newElement);
31         $('#messages').scrollTop($('#messages').prop('scrollHeight'));
32     });
33 
34     socket.on('rooms',function(rooms){
35         console.log('rooms:---'+rooms);
36         rooms=JSON.parse(rooms);
37         $('#room-list').empty();
38         for(var room in rooms){
39             $('#room-list').append(divEscapedContentElement(room+':'+rooms[room]));
40         }
41         $('#room-list div').click(function(){
42             chatApp.processCommand('/join '+$(this).text().split(':')[0]);
43             $('#send-message').focus();
44         });
45     });
46 
47     setInterval(function(){
48         socket.emit('rooms');
49     },1000);
50 
51     $('#send-message').focus();
52     $('#send-button').click(function(){
53         processUserInput(chatApp,socket);
54     });
55 });

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏coding for love

在线商城项目16-头部前端逻辑修改

这是不合理的,我们应该根据登录态来做一个区别显示。未登录情况下显示login和购物车图标。已登录情况下显示用户名,logout,购物车图标。

661
来自专栏LinXunFeng的专栏

iOS - Swift UISearchController的取消按钮

1252
来自专栏落影的专栏

iOS开发笔记(二)

前言 开发做笔记是好习惯,总结分享是巩固记忆。 遇到问题,思考其背后的原因、原理。 AFNetworking 1、progress回调block,不在主线程;...

3487
来自专栏ShaoYL

iOS 蓝牙的GameKit用法

2835
来自专栏BinarySec

mmap及linux地址空间随机化失效漏洞

Linux下动态库是通过mmap建立起内存和文件的映射关系。其定义如下void* mmap(void* start,size_t length,int prot...

2821
来自专栏Python、Flask、Django

Go语言学习之 - 简单的并发程序

1081
来自专栏谈补锅

通知 - NSNotificationCenter

1、每一个应用程序都有一个通知中心(NSNotificationCenter)实例,专门负责协助不同对象之间的消息通信;

1214
来自专栏一“技”之长

NSAlert组件应用总结 原

    在桌面软件开发中,当用户进行非法的操作或有风险的操作时,时长需要弹出警告框来提示用户。在OS X系统上,NSAlert是专门的警告框组件。其提供了简洁的...

1164
来自专栏DannyHoo的专栏

底牌项目中的选择牌谱上传功能--深刻理解UITableView复用

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u010105969/article/details/...

871
来自专栏更流畅、简洁的软件开发方式

【自然框架】表单控件 之 一个表单修改多个表里的记录

      FormView 确实挺方便的,不过他也有几个小问题,只把FormView拖到页面里是不行的,还得再拽几个文本框、下拉列表框这一类的控件,还得布局。...

2286

扫码关注云+社区

领取腾讯云代金券