node.js搭建簡單伺服器,用於前端測試websocket連結方法和效能測試

來源:互聯網
上載者:User

標籤:前端測試   nbsp   帶來   命令   目錄   微軟   連結   老版本   資訊   

 

WebSocket簡介

談到Web即時推送,就不得不說WebSocket。在WebSocket出現之前,很多網站為了實現即時推送技術,通常採用的方案是輪詢 (Polling)和Comet技術,Comet又可細分為兩種實現方式,一種是長輪詢機制,一種稱為流技術,這兩種方式實際上是對輪詢技術的改進,這些 方案帶來很明顯的缺點,需要由瀏覽器對伺服器發出HTTP request,大量消耗伺服器頻寬和資源。面對這種狀況,HTML5定義了WebSocket協議,能更好的節省伺服器資源和頻寬並實現真正意義上的實 時推送。

WebSocket協議本質上是一個基於TCP的協議,它由通訊協定和編程API組成,WebSocket能夠在瀏覽器和伺服器之間建立雙向串連, 以基於事件的方式,賦予瀏覽器即時通訊能力。既然是雙向通訊,就意味著伺服器端和用戶端可以同時發送並響應請求,而不再像HTTP的請求和響應。

為了建立一個WebSocket串連,用戶端瀏覽器首先要向伺服器發起一個HTTP請求,這個請求和通常的HTTP請求不同,包含了一些附加頭信 息,其中附加頭資訊”Upgrade: WebSocket”表明這是一個申請協議升級的HTTP請求,伺服器端解析這些附加的頭資訊然後產生應答資訊返回給用戶端,用戶端和伺服器端的 WebSocket串連就建立起來了,雙方就可以通過這個串連通道自由的傳遞資訊,並且這個串連會持續存在直到用戶端或者伺服器端的某一方主動的關閉連 接。

一個典型WebSocket用戶端要求標頭:

前面講到WebSocket是HTML5中新增的一種通訊協定,這意味著一部分老版本瀏覽器(主要是IE10以下版本)並不具備這個功能, 通過百度統計的公開資料顯示,IE8 目前仍以33%的市場份額佔據榜首,好在chrome瀏覽器市場份額逐年上升,現在以超過26%的市場份額位居第二,同時微軟前不久宣布停止對IE6的技 術支援並提示使用者更新到新版本瀏覽器,這個曾經讓無數前端工程師為之頭疼的瀏覽器有望退出曆史舞台,再加上幾乎所有的智能手機瀏覽器都支援HTML5,所 以使得WebSocket的實戰意義大增,但是無論如何,我們實際的項目中,仍然要考慮低版本瀏覽器的相容方案:在支援WebSocket的瀏覽器中採用 新技術,而在不支援WebSocket的瀏覽器裡啟用Comet來接收發送訊息。

WebSocket實戰

本文將以多人線上聊天應用作為執行個體情境,我們先來確定這個聊天應用的基本需求。

需求分析

1、相容不支援WebSocket的低版本瀏覽器。
2、允許用戶端有相同的使用者名稱。
3、進入聊天室後可以看到當前線上的使用者和線上人數。
4、使用者上線或退出,所有線上的用戶端應該即時更新。
5、使用者發送訊息,所有用戶端即時收取。

在實際的開發過程中,為了使用WebSocket介面構建Web應用,我們首先需要構建一個實現了 WebSocket規範的服務端,服務端的實現不受平台和開發語言的限制,只需要遵從WebSocket規範即可,目前已經出現了一些比較成熟的 WebSocket服務端實現,比如本文使用的Node.js+Socket.IO。為什麼選用這個方案呢?先來簡單介紹下他們兩。

Node.js

Node.js採用C++語言編寫而成,它不是Javascript應用,而是一個Javascript的運行環境,據Node.js創始人 Ryan Dahl回憶,他最初希望採用Ruby來寫Node.js,但是後來發現Ruby虛擬機器的效能不能滿足他的要求,後來他嘗試採用V8引擎,所以選擇了 C++語言。

Node.js支援的系統包括*nux、Windows,這意味著程式員可以編寫系統級或者伺服器端的Javascript代碼,交給 Node.js來解釋執行。Node.js的Web開發架構Express,可以協助程式員快速建立web網站,從2009年誕生至今,Node.js的 成長的速度有目共睹,其發展前景獲得了技術社區的充分肯定。

Socket.IO

Socket.IO是一個開源的WebSocket庫,它通過Node.js實現WebSocket服務端,同時也提供用戶端JS庫。Socket.IO支援以事件為基礎的即時雙向通訊,它可以工作在任何平台、瀏覽器或行動裝置。

Socket.IO支援4種協議:WebSocket、htmlfile、xhr-polling、jsonp-polling,它會自動根據瀏覽 器選擇適合的通訊方式,從而讓開發人員可以聚焦到功能的實現而不是平台的相容性,同時Socket.IO具有不錯的穩定性和效能。

編碼實現

本文一開始的的插圖就是效果示範圖:可以點擊這裡查看線上示範,整個開發過程非常簡單,下面簡單記錄了開發步驟:

安裝Node.js

根據自己的作業系統,去Node.js官網下載安裝即可。如果成功安裝。在命令列輸入node -vnpm -v應該能看到相應的版本號碼。

node -v  v0.10.26  npm -v  1.4.6  

 

搭建WebSocket服務端

這個環節我們儘可能的考慮真實生產環境,把WebSocket後端服務搭建成一個線上可以用網域名稱訪問的服務,如果你是在本地開發環境,可以換成本地ip地址,或者使用一個虛擬網域名稱指向本地ip。

先進入到你的工作目錄,比如/workspace/wwwroot/plhwin/realtime.plhwin.com,建立一個名為package.json的檔案,內容如下:

{  "name": "realtime-server",  "version": "0.0.1",  "description": "my first realtime server",  "dependencies": {}}

 

接下來使用npm命令安裝expresssocket.io

npm install --save expressnpm install --save socket.io

 

安裝成功後,應該可以看到工作目錄下產生了一個名為node_modules的檔案夾,裡面分別是expresssocket.io,接下來可以開始編寫服務端的代碼了,建立一個檔案:index.js

var app = require(‘express‘)();var http = require(‘http‘).Server(app);var io = require(‘socket.io‘)(http);app.get(‘/‘, function(req, res){res.send(‘<h1>Welcome Realtime Server</h1>‘);});http.listen(3000, function(){console.log(‘listening on *:3000‘);});

 

命令列運行node index.js,如果一切順利,你應該會看到返回的listening on *:3000字樣,這說明服務已經成功搭建了。此時瀏覽器中開啟http://localhost:3000應該可以看到正常的歡迎頁面。

如果你想要讓服務運行線上上伺服器,並且可以通過網域名稱訪問的話,可以使用Nginx做代理,在nginx.conf中添加如下配置,然後將網域名稱(比如:realtime.plhwin.com)解析到伺服器IP即可。

 server  {    listen       80;    server_name  realtime.plhwin.com;    location / {      proxy_pass http://127.0.0.1:3000;    }  }

 

完成以上步驟,http://realtime.plhwin.com:3000的後端服務就正常搭建了。

服務端代碼實現

前面講到的index.js運行在服務端,之前的代碼只是一個簡單的WebServer歡迎內容,讓我們把WebSocket服務端完整的實現代碼加入進去,整個服務端就可以處理用戶端的請求了。完整的index.js代碼如下:

var app = require(‘express‘)();var http = require(‘http‘).Server(app);var io = require(‘socket.io‘)(http);app.get(‘/‘, function(req, res){res.send(‘<h1>Welcome Realtime Server</h1>‘);});//線上使用者var onlineUsers = {};//當前線上人數var onlineCount = 0;io.on(‘connection‘, function(socket){console.log(‘a user connected‘);//監聽新使用者加入socket.on(‘login‘, function(obj){//將新加入使用者的唯一標識當作socket的名稱,後面退出的時候會用到socket.name = obj.userid;//檢查線上列表,如果不在裡面就加入if(!onlineUsers.hasOwnProperty(obj.userid)) {onlineUsers[obj.userid] = obj.username;//線上人數+1onlineCount++;}//向所有用戶端廣播使用者加入io.emit(‘login‘, {onlineUsers:onlineUsers, onlineCount:onlineCount, user:obj});console.log(obj.username+‘加入了聊天室‘);});//監聽使用者退出socket.on(‘disconnect‘, function(){//將退出的使用者從線上列表中刪除if(onlineUsers.hasOwnProperty(socket.name)) {//退出使用者的資訊var obj = {userid:socket.name, username:onlineUsers[socket.name]};//刪除delete onlineUsers[socket.name];//線上人數-1onlineCount--;//向所有用戶端廣播使用者退出io.emit(‘logout‘, {onlineUsers:onlineUsers, onlineCount:onlineCount, user:obj});console.log(obj.username+‘退出了聊天室‘);}});//監聽使用者發布聊天內容socket.on(‘message‘, function(obj){//向所有用戶端廣播發布的訊息io.emit(‘message‘, obj);console.log(obj.username+‘說:‘+obj.content);});  });http.listen(3000, function(){console.log(‘listening on *:3000‘);});

 

用戶端代碼實現

進入用戶端工作目錄/workspace/wwwroot/plhwin/demo.plhwin.com/chat,建立一個index.html:

<!DOCTYPE html><html>    <head>        <meta charset="utf-8">        <meta name="format-detection" content="telephone=no"/>        <meta name="format-detection" content="email=no"/><meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=0" name="viewport">        <title>多人聊天室</title>        <link rel="stylesheet" type="text/css" href="./style.css" />        <!--[if lt IE 8]><script src="./json3.min.js"></script><![endif]-->        <script src="http://realtime.plhwin.com:3000/socket.io/socket.io.js"></script>    </head>    <body>        <div id="loginbox">            <div style="width:260px;margin:200px auto;">                請先輸入你在聊天室的暱稱                <br/>                <br/>                <input type="text" style="width:180px;" placeholder="請輸入使用者名稱" id="username" name="username" /><input type="button" style="width:50px;" value="提交" onclick="CHAT.usernameSubmit();"/>            </div>        </div>        <div id="chatbox" style="display:none;">            <div style="background:#3d3d3d;height: 28px; width: 100%;font-size:12px;">                <div style="line-height: 28px;color:#fff;">                    <span style="text-align:left;margin-left:10px;">Websocket多人聊天室</span>                    <span style="float:right; margin-right:10px;"><span id="showusername"></span> | <a href="javascript:;" onclick="CHAT.logout()" style="color:#fff;">退出</a></span>                </div>            </div>            <div id="doc">                <div id="chat">                    <div id="message" class="message"><div id="onlinecount" style="background:#EFEFF4; font-size:12px; margin-top:10px; margin-left:10px; color:#666;"></div>                    </div>                    <div class="input-box">                        <div class="input"><input type="text" maxlength="140" placeholder="請輸入聊天內容,按Ctrl提交" id="content" name="content">                        </div>                        <div class="action">                            <button type="button" id="mjr_send" onclick="CHAT.submit();">提交</button>                        </div>                    </div>                </div>            </div>        </div>        <script type="text/javascript" src="./client.js"></script>    </body></html>

 

上面的html內容本身沒有什麼好說的,我們主要看看裡面的4個檔案請求:
1、realtime.plhwin.com:3000/socket.io/socket.io.js
2、style.css
3、json3.min.js
4、client.js

第1個JS是Socket.IO提供的用戶端JS檔案,在前面安裝服務端的步驟中,當npm安裝完socket.io並搭建起WebServer後,這個JS檔案就可以正常訪問了。

第2個style.css檔案沒什麼好說的,就是樣式檔案而已。

第3個JS只在IE8以下版本的IE瀏覽器中載入,目的是讓這些低版本的IE瀏覽器也能處理json,這是一個開源的JS,詳見:http://bestiejs.github.io/json3/

第4個client.js是完整的用戶端的商務邏輯實現代碼,它的內容如下:

(function () {var d = document,w = window,p = parseInt,dd = d.documentElement,db = d.body,dc = d.compatMode == ‘CSS1Compat‘,dx = dc ? dd: db,ec = encodeURIComponent;w.CHAT = {msgObj:d.getElementById("message"),screenheight:w.innerHeight ? w.innerHeight : dx.clientHeight,username:null,userid:null,socket:null,//讓瀏覽器捲軸保持在最低部scrollToBottom:function(){w.scrollTo(0, this.msgObj.clientHeight);},//退出,本例只是一個簡單的重新整理logout:function(){//this.socket.disconnect();location.reload();},//提交聊天訊息內容submit:function(){var content = d.getElementById("content").value;if(content != ‘‘){var obj = {userid: this.userid,username: this.username,content: content};this.socket.emit(‘message‘, obj);d.getElementById("content").value = ‘‘;}return false;},genUid:function(){return new Date().getTime()+""+Math.floor(Math.random()*899+100);},//更新系統訊息,本例中在使用者加入、退出的時候調用updateSysMsg:function(o, action){//當前線上使用者列表var onlineUsers = o.onlineUsers;//當前線上人數var onlineCount = o.onlineCount;//新加入使用者的資訊var user = o.user;//更新線上人數var userhtml = ‘‘;var separator = ‘‘;for(key in onlineUsers) {        if(onlineUsers.hasOwnProperty(key)){userhtml += separator+onlineUsers[key];separator = ‘、‘;}    }d.getElementById("onlinecount").innerHTML = ‘當前共有 ‘+onlineCount+‘ 人線上,線上列表:‘+userhtml;//添加系統訊息var html = ‘‘;html += ‘<div class="msg-system">‘;html += user.username;html += (action == ‘login‘) ? ‘ 加入了聊天室‘ : ‘ 退出了聊天室‘;html += ‘</div>‘;var section = d.createElement(‘section‘);section.className = ‘system J-mjrlinkWrap J-cutMsg‘;section.innerHTML = html;this.msgObj.appendChild(section);this.scrollToBottom();},//第一個介面使用者提交使用者名稱usernameSubmit:function(){var username = d.getElementById("username").value;if(username != ""){d.getElementById("username").value = ‘‘;d.getElementById("loginbox").style.display = ‘none‘;d.getElementById("chatbox").style.display = ‘block‘;this.init(username);}return false;},init:function(username){/*用戶端根據時間和隨機數產生uid,這樣使得聊天室使用者名稱稱可以重複。實際項目中,如果是需要使用者登入,那麼直接採用使用者的uid來做標識就可以*/this.userid = this.genUid();this.username = username;d.getElementById("showusername").innerHTML = this.username;this.msgObj.style.minHeight = (this.screenheight - db.clientHeight + this.msgObj.clientHeight) + "px";this.scrollToBottom();//串連websocket後端伺服器this.socket = io.connect(‘ws://realtime.plhwin.com:3000‘);//告訴伺服器端有使用者登入this.socket.emit(‘login‘, {userid:this.userid, username:this.username});//監聽新使用者登入this.socket.on(‘login‘, function(o){CHAT.updateSysMsg(o, ‘login‘);});//監聽使用者退出this.socket.on(‘logout‘, function(o){CHAT.updateSysMsg(o, ‘logout‘);});//監聽訊息發送this.socket.on(‘message‘, function(obj){var isme = (obj.userid == CHAT.userid) ? true : false;var contentDiv = ‘<div>‘+obj.content+‘</div>‘;var usernameDiv = ‘<span>‘+obj.username+‘</span>‘;var section = d.createElement(‘section‘);if(isme){section.className = ‘user‘;section.innerHTML = contentDiv + usernameDiv;} else {section.className = ‘service‘;section.innerHTML = usernameDiv + contentDiv;}CHAT.msgObj.appendChild(section);CHAT.scrollToBottom();});}};//通過“斷行符號”提交使用者名稱d.getElementById("username").onkeydown = function(e) {e = e || event;if (e.keyCode === 13) {CHAT.usernameSubmit();}};//通過“斷行符號”提交資訊d.getElementById("content").onkeydown = function(e) {e = e || event;if (e.keyCode === 13) {CHAT.submit();}};})();

 

至此所有的編碼開發工作全部完成了,在瀏覽器中開啟http://demo.plhwin.com/chat/就可以看到效果了。

上面所有的用戶端和服務端的代碼可以從Github上獲得,地址:https://github.com/plhwin/nodejs-socketio-chat

git clone https://github.com/plhwin/nodejs-socketio-chat.git 

下載本地後有兩個檔案夾 client 和 serverclient檔案夾是用戶端源碼,可以放在Nginx/Apache的WebServer中,也可以放在Node.js的WebServer中。後面的server檔案夾裡的代碼是websocket服務端代碼,放在Node.js環境中,使用npm安裝完 express 和 socket.io 後,node index.js 啟動後端服務就可以了。

本例只是一個簡單的Demo,留下2個有關項目擴充的思考:

1、假設是一個線上客服系統,裡面有許多的公司使用你的服務,每個公司自己的使用者可以通過一個專屬URL地址進入該公司的聊天室,聊天是一對一的,每個公司可以建立多個客服人員,每個客服人員可以同時和用戶端的多個使用者聊天。

2、又假設是一個線上WebIM系統,實作類別似,qq的功能,用戶端可以看到好友線上狀態,線上列表,添加好友,移除朋友,建立群組等,訊息的發送除了支援基本的文字外,還能支援表情、圖片和檔案。

來自:http://www.plhwin.com/2014/05/28/nodejs-socketio/

node.js搭建簡單伺服器,用於前端測試websocket連結方法和效能測試

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.