搜尋
首頁web前端js教程使用Angular和Nodejs、socket.io搭建聊天室及多人聊天室_javascript技巧

一,利用Node搭建靜態伺服器

       這個是這個專案的底層支撐部分。用來支援靜態資源檔案像html, css, gif, jpg, png, javascript, json, plain text等等靜態資源的存取。這裡面是有一個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的HTTP回應狀態表示正確的回應。此時,Web伺服器提供資源服務,客戶端開始下載資源。下載的資源包括了html文件,css文件,javascript文件,image文件。然後開始建立一顆渲染樹和一顆DOM樹,期間會有css阻塞和js阻塞。所以底層是需要一個靜態伺服器支撐。這裡面我原生建構一個靜態伺服器,不採用express框架。

       事實上每項資源文件請求的過程是次GET請求。下面我解釋一下客戶端(瀏覽器端或採用linux下採用curl方式)的GET請求所對應的服務端處理過程。一次Get請求傳送到服務端後,服務端可以根據GET請求對應一個資源檔案的路徑。知道了這個路徑後,我們就可以採用檔案讀寫的方式來取得指定路徑下的資源,然後回到客戶端。

        我們知道Node裡面的文件讀寫的API有readFile和readFileSync,但是更好的方式是採用流的方式去讀取文件,採用流的方式的優點是可以採用緩存和gzip壓縮。 

       OK,那麼如何實現快取呢?通常情況下,客戶端第一次去請求的時候,服務端會讀取資源文件,傳回給客戶端。但是第二次再去請求同樣的文件時,這個時候還是需要發送一次請求到服務端。服務端會根據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?

         我們知道現在主流的聊天室還是採用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?

         作为一款前端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的console控制台的时候,你会发现,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,想着做点东西练练手。网上的东西多而杂,走了不少弯路,花了一天时间在调代码上。参考网上的一篇文章,重写了部分代码,原来的是基于基于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的使用做了详细介绍:http://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);

四、效果图

五、源码下载

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

ps:

1、在命令行运行

node main.js

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

2、推荐node.js的IDE WebStorm

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

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
從網站到應用程序:JavaScript的不同應用從網站到應用程序:JavaScript的不同應用Apr 22, 2025 am 12:02 AM

JavaScript在網站、移動應用、桌面應用和服務器端編程中均有廣泛應用。 1)在網站開發中,JavaScript與HTML、CSS一起操作DOM,實現動態效果,並支持如jQuery、React等框架。 2)通過ReactNative和Ionic,JavaScript用於開發跨平台移動應用。 3)Electron框架使JavaScript能構建桌面應用。 4)Node.js讓JavaScript在服務器端運行,支持高並發請求。

Python vs. JavaScript:比較用例和應用程序Python vs. JavaScript:比較用例和應用程序Apr 21, 2025 am 12:01 AM

Python更適合數據科學和自動化,JavaScript更適合前端和全棧開發。 1.Python在數據科學和機器學習中表現出色,使用NumPy、Pandas等庫進行數據處理和建模。 2.Python在自動化和腳本編寫方面簡潔高效。 3.JavaScript在前端開發中不可或缺,用於構建動態網頁和單頁面應用。 4.JavaScript通過Node.js在後端開發中發揮作用,支持全棧開發。

C/C在JavaScript口譯員和編譯器中的作用C/C在JavaScript口譯員和編譯器中的作用Apr 20, 2025 am 12:01 AM

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。1)C 用于解析JavaScript源码并生成抽象语法树。2)C 负责生成和执行字节码。3)C 实现JIT编译器,在运行时优化和编译热点代码,显著提高JavaScript的执行效率。

JavaScript在行動中:現實世界中的示例和項目JavaScript在行動中:現實世界中的示例和項目Apr 19, 2025 am 12:13 AM

JavaScript在現實世界中的應用包括前端和後端開發。 1)通過構建TODO列表應用展示前端應用,涉及DOM操作和事件處理。 2)通過Node.js和Express構建RESTfulAPI展示後端應用。

JavaScript和Web:核心功能和用例JavaScript和Web:核心功能和用例Apr 18, 2025 am 12:19 AM

JavaScript在Web開發中的主要用途包括客戶端交互、表單驗證和異步通信。 1)通過DOM操作實現動態內容更新和用戶交互;2)在用戶提交數據前進行客戶端驗證,提高用戶體驗;3)通過AJAX技術實現與服務器的無刷新通信。

了解JavaScript引擎:實施詳細信息了解JavaScript引擎:實施詳細信息Apr 17, 2025 am 12:05 AM

理解JavaScript引擎內部工作原理對開發者重要,因為它能幫助編寫更高效的代碼並理解性能瓶頸和優化策略。 1)引擎的工作流程包括解析、編譯和執行三個階段;2)執行過程中,引擎會進行動態優化,如內聯緩存和隱藏類;3)最佳實踐包括避免全局變量、優化循環、使用const和let,以及避免過度使用閉包。

Python vs. JavaScript:學習曲線和易用性Python vs. JavaScript:學習曲線和易用性Apr 16, 2025 am 12:12 AM

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

Python vs. JavaScript:社區,圖書館和資源Python vs. JavaScript:社區,圖書館和資源Apr 15, 2025 am 12:16 AM

Python和JavaScript在社區、庫和資源方面的對比各有優劣。 1)Python社區友好,適合初學者,但前端開發資源不如JavaScript豐富。 2)Python在數據科學和機器學習庫方面強大,JavaScript則在前端開發庫和框架上更勝一籌。 3)兩者的學習資源都豐富,但Python適合從官方文檔開始,JavaScript則以MDNWebDocs為佳。選擇應基於項目需求和個人興趣。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器

SublimeText3 英文版

SublimeText3 英文版

推薦:為Win版本,支援程式碼提示!

mPDF

mPDF

mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

MinGW - Minimalist GNU for Windows

MinGW - Minimalist GNU for Windows

這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。