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 下载。