基于Node.js与Socket.io搭建聊天室及Angular客户端实现

作者:岩茶

时间:2026-10-29

浏览:314

本文详解如何利用Node.js搭建静态服务器,并通过Socket.io实现WebSocket通信以构建多人聊天室服务端。同时介绍使用Angular框架开发单页应用(SPA)客户端的方法,涵盖路由配置、双向数据绑定及实时消息广播逻辑,提供完整的实现步骤与注意事项。

文章目录

利用Node搭建静态服务器

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

这部分构成了项目的底层支撑,用于处理HTML、CSS、GIF、JPG、PNG、JavaScript、JSON及纯文本等静态资源的访问请求。其中包含一个用于映射MIME类型的文件配置。

mime.js


/**
 * 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"
};

首先解释从输入网址到页面加载完成的整个过程。当用户在浏览器地址栏输入URL时,

随后会触发一系列操作。首先是DNS解析,将域名转换为对应的IP地址。接着,浏览器与远程Web服务器通过TCP三次握手建立TCP/IP连接。该过程涉及三个报文:同步报文、同步-应答报文和应答报文,在客户端与服务器间传递。握手由客户端发起通信尝试,服务器应答并接受请求,最后客户端发送请求已接受的报文。TCP/IP连接建立后,浏览器通过该连接向服务器发送HTTP GET请求。

远程服务器定位资源并使用HTTP响应返回,状态码200表示响应成功。此时Web服务器提供资源服务,客户端开始下载包含HTML、CSS、JavaScript及图像文件的资源。随后构建渲染树和DOM树,期间可能遇到CSS阻塞和JS阻塞。因此,底层需要一个静态服务器支持。这里原生构造一个静态服务器,不使用Express框架。

实际上,每次资源文件请求都是一次GET请求。下面解释客户端(浏览器或Linux下的curl)发送GET请求时服务端的处理过程。服务端根据GET请求对应的资源文件路径,通过文件读写方式获取指定路径下的资源,然后返回给客户端。

Node中提供readFile和readFileSync API进行文件读写,但更好的方式是采用流(Stream)读取文件。采用流方式的优点是可以利用缓存和gzip压缩。

那么如何实现缓存呢?通常客户端首次请求时,服务端读取资源文件并返回。再次请求相同文件时,仍需发送请求到服务端。服务端根据Expires、cache-control、If-Modified-Since等HTTP头信息判断资源是否已缓存。若已缓存,服务端不再访问实际文件路径,直接返回缓存资源。

server.js


/**
 * 聊天室服务端
 * 功能:实现了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);
   }

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


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

//监听3000端口


server.listen(PORT, function() {
 console.log("Server is listening on port " + PORT + "!");
});

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


chatServer.listen(server);

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

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

为什么采用websocket?

目前主流聊天室多采用Ajax实现客户端与服务端通信,这是一种轮询机制。所谓轮询,即客户端每隔一段时间发送请求询问服务端是否有新聊天数据,若有则返回给客户端。

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

websocket采用ws协议,而非http或https协议。采用websocket的另一个好处是减少大量数据流量。前文已介绍传统资源请求过程需三次握手协议,且每次请求头空间较大,非常消耗流量。而Websocket中互相沟通的Header很小,大约只有2 Bytes。


/**
 * 聊天服务。
 */
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);
 });
};

利用Angular搭建聊天室客户端

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

为什么使用Angular?

作为前端MVC框架,Angular.js备受关注。它具备模块化、双向数据绑定、指令系统和依赖注入特性。而且Angular内置jquerylite,这让熟悉jQuery语法的开发者容易上手。

个人觉得,angular在构建单页应用和CRUD项目方面优势明显。我们的聊天室正是基于SPA(single page application)的目标构建。

index.html


<!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>

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

先谈谈单页应用的原理。所谓单页,并非整个页面无刷新。审查Google Chrome控制台会发现,angular内部仍采用Ajax异步请求资源。因此只是局部刷新。但这种方式相对于以前删除和修改DOM节点已有很大进步。

构建单页应用,我们需要借助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和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的使用做了详细介绍:https://www.jb51.net/article/71361.htm

《用node.js和Websocket做个多人聊天室吧》http://www.html5china.com/HTML5features/WebSocket/20111206_3096.html

三、注意事项

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

后来看了这个:http://stackoverflow.com/questions/7352164/update-all-clients-using-socket-io

改为以下才起作用:


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

四、效果图

利用Node搭建静态服务器

利用Node搭建静态服务器

利用Node搭建静态服务器

利用Node搭建静态服务器

五、源码下载

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

ps:

1、在命令行运行


node main.js

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

2、推荐node.js的IDE WebStorm

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

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章