图片 1

io搭建聊天室及多人聊天室,Nodejs实现多房间简易聊天室功能

1、前端界面代码

1、前端界面代码

一,利用Node搭建静态服务器

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

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

       这个是这个项目的底层支撑部分。用来支持静态资源文件像html, css,
gif, jpg, png, javascript, json, plain
text
等等静态资源的访问。这里面是有一个mime类型的文件映射。

2、服务器端搭建

图片 1

mime.js

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

 

/**
 * mime类型的 map
 * @ author Cheng Liufeng
 * @ date 2014/8/30
 * 当请求静态服务器文件的类型 html, css, gif, jpg, png, javascript, json, plain text, 我们会在此文件进行映射
 */
exports.types = {
 "css": "text/css",
 "gif": "image/gif",
 "html": "text/html",
 "ico": "image/x-icon",
 "jpeg": "image/jpeg",
 "jpg": "image/jpeg",
 "js": "text/javascript",
 "json": "application/json",
 "pdf": "application/pdf",
 "png": "image/png",
 "svg": "image/svg+xml",
 "swf": "application/x-shockwave-flash",
 "tiff": "image/tiff",
 "txt": "text/plain",
 "wav": "audio/x-wav",
 "wma": "audio/x-ms-wma",
 "wmv": "video/x-ms-wmv",
 "xml": "text/xml"
};

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

2、服务器端搭建

  这里面我先解释一下从输入网址到页面出现的过程。
当用户在浏览器地址栏里面输入一个url的时候。

{
 "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"
 }
}

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

接下来会发生一系列的过程。首先是DNS解析,
将域名转换成对应的IP地址,之后浏览器与远程Web服务器通过TCP三次握手协商来建立一个TCP/IP连接。该握手包括一个同步报文,开一个同步-应答报文和一个应答报文,这三个报文在浏览器和服务器之间传递。该握手首先由客户端尝试建立起通信,而后服务器应答并接受客户端的请求,最后由客户端发出该请求已经被接受的报文。一旦TCP/IP连接建立,浏览器会通过该连接向远程服务器发送HTTP的GET请求。

  2、http服务器

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

远程服务器找到资源并使用HTTP响应返回该资源,值为200的HTTP响应状态表示一个正确的响应。此时,Web服务器提供资源服务,客户端开始下载资源。下载的资源包括了html文件,css文件,javascript文件,image文件。然后开始构建一颗渲染树和一颗DOM树,期间会有css阻塞和js阻塞。所以底层是需要一个静态服务器支撑。这里面我原生构造一个静态服务器,不采用express框架。

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

图片 2图片 3

      
事实上每一次资源文件请求的过程是一次次GET请求。下面我解释一下客户端(浏览器端或者采用linux下采用curl方式)的GET请求所对应的服务端处理过程。一次Get请求发送到服务端后,服务端可以根据GET请求对应一个资源文件的路径。知道了这个路径后,我们就可以采用文件读写的方式获取指定路径下的资源,然后返回给客户端。

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服务也绑定到该端口上
{
  "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"
  }
}

       
我们知道Node里面的文件读写的API有readFile和readFileSync,但是更好的方式是采用流的方式去读取文件,采用流的方式的优点是可以采用缓存和gzip压缩。 

  3、socket服务

View Code

      
OK,那么如何实现缓存呢?通常情况下,客户端第一次去请求的时候,服务端会读取资源文件,返回给客户端。但是第二次再去请求同样的文件时,这个时候还是需要发送一次请求到服务端。服务端会根据Expires,
cache-control,
If-Modified-Since等Http头信息判断这个资源是否已经缓存过。如果有缓存,服务端则不会再次访问资源文件的实际路径。直接返回缓存的资源。

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

  2、http服务器

server.js

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];
  })
}

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

/**
 * 聊天室服务端
 * 功能:实现了Node版的静态服务器
 * 实现了缓存,gzip压缩等
 * @ author Cheng Liufeng
 * @ date 2014/8/30
 */
 // 设置端口号
var PORT = 3000;
// 引入模块
var http = require('http');
var url = require('url');
var fs = require('fs');
var path = require('path');
var zlib = require('zlib');
// 引入文件
var mime = require('./mime').types;
var config = require('./config');
var chatServer = require('./utils/chat_server');
var server = http.createServer(function (req, res) {
 res.setHeader("Server","Node/V8");
 // 获取文件路径
 var pathName = url.parse(req.url).pathname;
 if(pathName.slice(-1) === "/"){
 pathName = pathName + "index.html"; //默认取当前默认下的index.html
 }
 // 安全处理(当使用Linux 的 curl命令访问时,存在安全隐患)
 var realPath = path.join("client", path.normalize(pathName.replace(/\.\./g, "")));
 // 检查文件路径是否存在
 path.exists(realPath, function(exists) {
 // 当文件不存在时的情况, 输出一个404错误
 if (!exists) {
  res.writeHead(404, "Not Found", {'Content-Type': 'text/plain'});
  res.write("The request url" + pathName +" is not found!");
  res.end();
 } else {   // 当文件存在时的处理逻辑
  fs.stat(realPath, function(err, stat) {
    // 获取文件扩展名
  var ext = path.extname(realPath);
  ext = ext ? ext.slice(1) : "unknown";
  var contentType = mime[ext] || "text/plain";
  // 设置 Content-Type
  res.setHeader("Content-Type", contentType);
  var lastModified = stat.mtime.toUTCString();
  var ifModifiedSince = "If-Modified-Since".toLowerCase();
  res.setHeader("Last-Modified", lastModified);
  if (ext.match(config.Expires.fileMatch)) {
   var expires = new Date();
   expires.setTime(expires.getTime() + config.Expires.maxAge * 1000);
   res.setHeader("Expires", expires.toUTCString());
   res.setHeader("Cache-Control", "max-age=" + config.Expires.maxAge);
  }
  if (req.headers[ifModifiedSince] && lastModified == req.headers[ifModifiedSince]) {
   res.writeHead(304, "Not Modified");
   res.end();
  } else {
   // 使用流的方式去读取文件
   var raw = fs.createReadStream(realPath);
   var acceptEncoding = req.headers['accept-encoding'] || "";
   var matched = ext.match(config.Compress.match);
   if (matched && acceptEncoding.match(/\bgzip\b/)) {
   res.writeHead(200, "Ok", {'Content-Encoding': 'gzip'});
   raw.pipe(zlib.createGzip()).pipe(res);
   } else if (matched && acceptEncoding.match(/\bdeflate\b/)) {
   res.writeHead(200, "Ok", {'Content-Encoding': 'deflate'});
   raw.pipe(zlib.createDeflate()).pipe(res);
   } else {
   res.writeHead(200, "Ok");
   raw.pipe(res);
   }

3、客户端实现socket.io

图片 4图片 5

//下面是普通的读取文件的方式,不推荐

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

 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服务也绑定到该端口上
//   fs.readFile(realPath, "binary", function(err, data) {
//   if(err) {
//    // file exists, but have some error while read
//    res.writeHead(500, {'Content-Type': 'text/plain'});
//    res.end(err);
//   } else {
//    // file exists, can success return
//    res.writeHead(200, {'Content-Type': contentType});
//    res.write(data, "binary");
//    res.end();
//   }
//   });
  }
  });
 }
 });
});
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;
};

View Code

//监听3000端口

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

  3、socket服务

server.listen(PORT, function() {
 console.log("Server is listening on port " + PORT + "!");
});
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('');
}

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

// 让socket.io服务器和http服务器共享一个端口

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

图片 6图片 7

chatServer.listen(server);
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);
  });
});
  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 }

  二,服务端利用WebSocket构建聊天室服务端

完整代码,可到 下载。

View Code

        为什么采用websocket?

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

3、客户端实现socket.io

        
我们知道现在主流的聊天室还是采用ajax去实现客户端和服务端的通信。采用的是一种轮询的机制。所谓轮询,就是客户端每隔一段时间就去发送一次请求,询问服务端,看看服务端有没有新的聊天数据,如果有新的数据,就返回给客户端。 

您可能感兴趣的文章:

  • 浅析nodejs实现Websocket的数据接收与发送
  • 基于html5和nodejs相结合实现websocket即使通讯
  • 用nodejs搭建websocket服务器
  • nodejs+websocket实时聊天系统改进版
  • 使用DNode实现php和nodejs之间通信的简单实例
  • nodejs
    socket服务端和客户端简单通信功能
  • nodejs实现的一个简单聊天室功能分享
  • 使用Angular和Nodejs、socket.io搭建聊天室及多人聊天室
  • Nodejs之TCP服务端与客户端聊天程序详解
  • nodejs基于WS模块实现WebSocket聊天功能的方法

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

        Websocket则完全不同。
websocket是基于长链接。就是客户端和服务端一旦建立链接之后,这个链接就会一直存在。
是一种全双工的通信。 这个时候的机制有点类似发布-订阅模式。
客户端会订阅一些事件,一旦服务端有新的数据出现,会主动推送给客户端。

图片 8图片 9

      
websocket采用的是ws协议,不是http协议或者https协议。另外采用websocket的另一个好处就是可以减少很多数据流量。文章开头,我已经介绍了传统的一次资源请求过程,需要三次握手协议,而且每次请求头所占空间比较大,这样会很费流量。而Websocket里面的互相沟通的Header是很小的-大概只有
2 Bytes。

 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 }; 
/**
 * 聊天服务。
 */
var socketio = require('socket.io');
var io;
var guestNumber = 1;   //初始用户名编号
var nickNames = {};   // 昵称列表
var namesUsed = [];   //使用过的用户名
var currentRoom = {};   //当前聊天室
function assignGuestName(socket, guestNumber, nickNames, namesUsed) {
 var name = 'Guest' + guestNumber;
 nickNames[socket.id] = name;
 socket.emit('nameResult', {
 success: true,
 name: name
 });
 namesUsed.push(name);
 return guestNumber + 1;
}
function joinRoom(socket, room) {
 socket.join(room);
 currentRoom[socket.id] = room;
 socket.emit('joinResult', {room: room});
 socket.broadcast.to(room).emit('message', {
 text: nickNames[socket.id] + 'has joined ' + room + '.'
 });
}
function handleMessageBroadcasting(socket) {
 socket.on('message', function(message) {
 socket.broadcast.to(message.room).emit('message', {
  text: nickNames[socket.id] + ':' + message.text
 });
 });
}
exports.listen = function(server) {
 io = socketio.listen(server);
 io.set('log level', 1);
 // 定义每个用户的连接处理
 io.sockets.on('connection', function(socket) {
 // 分配一个用户名
 guestNumber = assignGuestName(socket, guestNumber, nickNames, namesUsed);
 // 将用户加入聊天室Lobby里
 joinRoom(socket, 'Lobby');
 //处理聊天信息
 handleMessageBroadcasting(socket, nickNames);
 //handleNameChangeAttempts(socket, nickNames, namesUsed);
 //handleRoomJoining(socket);
 //handleClientDisconnection(socket, nickNames, namesUsed);
 });
};

View Code

三,利用Angular搭建聊天室客户端

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

        为什么使用Angular?

图片 10图片 11

        
作为一款前端MVC框架,Angular.js无疑是引人注目的。模块化,双向数据绑定,指令系统,依赖注入。而且Angular内置jquerylite,这让熟悉jQuery语法的同学很容易上手。

 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 }

当然,个人认为, angular在构建一个单页应用和crud项目方面有很大的优势。
我们这个聊天室就是基于SPA(single page application)的目的。

View Code

index.html

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

<!DOCTYPE html>
<html ng-app="chatApp">
<head>
 <meta name="viewport" content="width=device-width, user-scalable=no">
</head>
<body ng-controller="InitCtrl">
 <div ng-view></div>
 <script src="lib/angular.js"></script>
 <script src="lib/angular-route.js"></script>
 <script src="lib/socket.io.js"></script>
 <script src="app.js"></script>
 <script src="controllers/InitCtrl.js"></script>
</body>
</html>

图片 12图片 13

怎样构建一个单页应用?单页应用的原理?

 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 });

       
先谈谈单页应用的原理。所谓单页,并不是整个页面无刷新。当你审查一下google
chrome的console控制台的时候,你会发现,angular内部还是采用了ajax去异步请求资源。所以只是局部刷新。但是这种方式相对于以前的DOM节点的删除和修改已经有很大的进步了。

View Code

    
构建单页应用,我们需要借助于angular-route.js。这个angular子项目可以帮助我们定义路由和对应的逻辑处理控制器。利用它,我们可以实现一个单页应用。

完整代码,可到 下载。

app.js 

  

/**
 * 客户端(目前只支持浏览器,将来会扩展到移动端)程序入口文件
 * 创建一个模块,并且命名为chatApp
 * 配置路由,实现单页应用(single page application)
 */
 var chatApp = angular.module("chatApp", ['ngRoute']);
 // 路由配置
 chatApp.config(function($routeProvider) {
 $routeProvider.when('/', {
  templateUrl : 'views/init.html',
  controller: 'InitCtrl'
 })
 .when('/init', {
  templateUrl : 'views/init.html',
  controller: 'InitCtrl'
 });
 });

客户端聊天界面的代码逻辑如下

InitCtrl.js

/**
 * # InitCtrl
 */
angular.module('chatApp').controller('InitCtrl', function($scope) {
 var socket = io.connect('http://127.0.0.1:3000');
 socket.on('nameResult', function(result) {
 var message;
 if (result.success) {
  message = 'you are now known as ' + result.name + '.'; 
  console.log('message=', message);
  document.getElementById('guestname').innerHTML = message;
 } else {
  message = result.message;
 }
 });
 socket.on('joinResult', function(result) {
 document.getElementById('room').innerHTML = result.room;
 });
 $scope.sendMessage = function() {
 var message = {
  room: 'Lobby',
  text: document.getElementById('user_input').value
 };
 socket.emit('message', message);
 };
 socket.on('message', function(message) {
 var p = document.createElement('p');
 p.innerHTML = message.text;
 document.getElementById('message').appendChild(p);
 });
});

基于node.js和socket.io搭建多人聊天室

刚学node.js,想着做点东西练练手。网上的东西多而杂,走了不少弯路,花了一天时间在调代码上。参考网上的一篇文章,重写了部分代码,原来的是基于基于node-websocket-server框架的,我没用框架,单单是socket.io

一、基本功能

1、用户随意输入一个昵称即可登录

2、登录成功后

1) 对正在登录用户来说,罗列所有在线用户列表,罗列最近的历史聊天记录

2) 对已登录的用户来说,通知有新用户进入房间,更新在线用户列表

3、退出登录

1)支持直接退出

2)
当有用户退出,其他所有在线用户会收到信息,通知又用户退出房间,同时更新在线用户列表

4、聊天

1) 聊天就是广播,把信息广播给所有连接在线的用户

5、一些出错处理

1) 暂时简单处理了系统逻辑错误、网络出错等特殊情况的出错提示

问题:功能不完善,有bug(退出后,新用户重新登录,还是原来的用户)
。抽空完善吧

二、技术介绍

socket.io(官网:http://socket.io/)是一个跨平台,多种连接方式自动切换,做即时通讯方面的开发很方便,而且能和expressjs提供的传统请求方式很好的结合,即可以在同一个域名,同一个端口提供两种连接方式:request/response,
websocket(flashsocket,ajax…)。

这篇文章对socket.io的使用做了详细介绍://www.jb51.net/article/71361.htm

《用node.js和Websocket做个多人聊天室吧》

三、注意事项

(1)客户端这样引用socket.io.js:

<script src="/socket.io/socket.io.js"></script>

可能会加载失败(我在这里耗了不少时间)

可以改为:

<script src="http://ip:port/socket.io/socket.io.js"></script>

(对应服务器的ip地址和端口号,比如说localhost和80端口)

(2)实现广播的时候,参考官网的写法,竟然不起作用,如:

var io = require('socket.io').listen(80);

io.sockets.on('connection', function (socket) {
 socket.broadcast.emit('user connected');
 socket.broadcast.json.send({ a: 'message' });
});

后来看了这个:

改为以下才起作用:

io.sockets.emit('users_count', clients);

四、效果图

图片 14

图片 15

图片 16

图片 17

五、源码下载

  Nodejs多人聊天室(点击此处下载源码)

ps:

1、在命令行运行

node main.js

然后在浏览器中打开index.html,如果浏览器(ff、Chrome)不支持,请升级到支持WebSocket的版本.

2、推荐node.js的IDE WebStorm

以上内容就是本文基于Angular和Nodejs搭建聊天室及多人聊天室的实现,希望大家喜欢。

您可能感兴趣的文章:

  • 浅析nodejs实现Websocket的数据接收与发送
  • 基于html5和nodejs相结合实现websocket即使通讯
  • 用nodejs搭建websocket服务器
  • nodejs+websocket实时聊天系统改进版
  • nodejs基于WS模块实现WebSocket聊天功能的方法
  • nodejs结合socket.io实现websocket通信功能的方法
  • nodejs
    socket服务端和客户端简单通信功能
  • nodejs
    socket实现的服务端和客户端功能示例
  • NodeJS简单实现WebSocket功能示例