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