標籤:named 社區 name this minimum 更新 inpu 隨機 names
一、WebSocket簡單介紹:
談到Web即時推送,就不得不說WebSocket。在WebSocket出現之前,非常多網站為了實現即時推送技術。通常採用的方案
是輪詢(Polling)和Comet技術,Comet又可細分為兩種實現方式,一種是長輪詢機制。一種稱為流技術。這兩種方式實際上是對
輪詢技術的改進。這些方案帶來非常明顯的缺點,須要由瀏覽器對server發出HTTP request。大量消耗server頻寬和資源。面對
這樣的狀況。HTML5定義了WebSocket協議,能更好的節省server資源和頻寬並實現真正意義上的即時推送。
WebSocket協議本質上是一個基於TCP的協議。它由通訊協定和編程API組成,WebSocket可以在瀏覽器和server之間建立
雙向串連,以基於事件的方式,賦予瀏覽器即時通訊能力。既然是雙向通訊。就意味著server端和client能夠同一時候發送並響應請
求。而不再像HTTP的請求和響應。
為了建立一個WebSocket串連。client瀏覽器首先要向server發起一個HTTP請求,這個請求和通常的HTTP請求不同。包括
了一些附加頭資訊。當中附加頭資訊”Upgrade: WebSocket”表明這是一個申請協議升級的HTTP請求,server端解析這些附加的
頭資訊然後產生應答資訊返回給client。client和server端的WebSocket串連就建立起來了。兩方就能夠通過這個串連通道自由
的傳遞資訊。而且這個串連會持續存在直到client或者server端的某一方主動的關閉串連。
一個典型WebSocketclient要求標頭:
注意:WebSocket是HTML5中新增的一種通訊協定,這意味著一部分老版本號碼瀏覽器(主要是IE10下面版本號碼)並不具備這個功能。
通過百度統計的公開資料顯示,IE8眼下仍以33%的市場份額佔領榜首,好在chrome瀏覽器市場份額逐年上升,如今以超過26%的
市場份額位居第二,同一時候微軟前不久宣布停止對IE6的支援人員並提示使用者更新到新版本號碼瀏覽器。這個以前讓無數前端project師為之頭
疼的瀏覽器有望退出曆史舞台,再加上差點兒全部的智能手機瀏覽器都支援HTML5,所以使得WebSocket的實戰意義大增。可是不管
怎樣,我們實際的項目中,仍然要考慮低版本號碼瀏覽器的相容方案:在支援WebSocket的瀏覽器中採用新技術,而在不支援WebSocke
t的瀏覽器裡啟用Comet來接收發送訊息。
瀏覽器支援列表:
二、WebSocket實戰:
本文將以多人線上聊天應用作為執行個體情境。我們先來確定這個聊天應用的基本需求。
需求分析:
1、相容不支援WebSocket的低版本號碼瀏覽器。
2、同意client有同樣的username。
3、進入聊天室後能夠看到當前線上的使用者和線上人數。
4、使用者上線或退出,全部線上的client應該即時更新。
5、使用者發送訊息,全部client即時收取。
在實際的開發過程中。為了使用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,這意味著程式猿能夠編寫系統級或者server端的Javascript代碼。交給 Node.js來
解釋運行。Node.js的Web開發架構Express。能夠協助程式猿高速建立web網站,從2009年誕生至今,Node.js的 成長的速度
有目共睹,其發展前景獲得了技術社區的充分肯定。
Socket.IO:
Socket.IO是一個開源的WebSocket庫,它通過Node.js實現WebSocket服務端,同一時候也提供clientJS庫。
Socket.IO支援以
事件為基礎的即時雙向通訊。它能夠工作在不論什麼平台、瀏覽器或行動裝置。
Socket.IO支援4種協議:WebSocket、htmlfile、xhr-polling、jsonp-polling。它會自己主動依據瀏覽 器選擇適合的通訊方式,
從而讓開發人員能夠聚焦到功能的實現而不是平台的相容性。同一時候Socket.IO具有不錯的穩定性和效能。
終於效果:
開發步驟
(1)、安裝Node.js
依據作業系統,去Node.js官網下載安裝。假設安裝成功。在命令列輸入node -v和npm -v應該能看到對應的版本。
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" >
(2)、搭建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命令安裝express和socket.io
npm install --save expressnpm install --save socket.io
成功安裝後。應該能夠看到工作資料夾下產生了一個名為node_modules的檔案夾,裡面各自是express和socket.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應該能夠看到正常的歡迎頁面。
假設你想要讓服務執行線上上server。而且能夠通過網域名稱訪問的話,能夠使用Nginx做代理。在nginx.conf中加入例如以下配置,然後將網域名稱
(比方:realtime.plhwin.com)解析到serverIP就可以。
server { listen 80; server_name realtime.plhwin.com; location / { proxy_pass http://127.0.0.1:3000; } }
完畢以上步驟,http://realtime.plhwin.com:3000的後端服務就正常搭建了。
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" >
(3)、服務端代碼實現
前面講到的index.js執行在服務端,之前的代碼僅僅是一個簡單的WebServer歡迎內容,讓我們把WebSocket服務端完整的實現代碼增加進去,
整個服務端就能夠處理client的請求了。完整的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++;}//向全部client廣播使用者增加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--;//向全部client廣播使用者退出io.emit('logout', {onlineUsers:onlineUsers, onlineCount:onlineCount, user:obj});console.log(obj.username+'退出了聊天室');}});//監聽使用者公布聊天內容socket.on('message', function(obj){//向全部client廣播公布的訊息io.emit('message', obj);console.log(obj.username+'說:'+obj.content);}); });http.listen(3000, function(){console.log('listening on *:3000');});
四、client代碼實現
進入client工作資料夾/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提供的clientJS檔案,在前面安裝服務端的步驟中,當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後端serverthis.socket = io.connect('ws://realtime.plhwin.com:3000');//告訴server端實使用者登入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/就能夠看到效果了。
上面全部的client和服務端的代碼能夠從Github上獲得,地址:https://github.com/plhwin/nodejs-socketio-chat
git clone https://github.com/plhwin/nodejs-socketio-chat.git
下載本地後有兩個目錄 client 和 server,client目錄是用戶端原始碼,能夠放在Nginx/Apache的WebServer中,
也能夠放在Node.js的WebServer中。後面的server目錄裡的代碼是websocket服務端代碼。放在Node.js環境中
,使用npm安裝完 express 和 socket.io 後,node index.js 啟動後端服務就能夠了。
本例僅僅是一個簡單的Demo,留下2個有關項目擴充的思考:
1、如果是一個線上客服系統,裡面有很多的公司使用你的服務,每一個公司自己的使用者能夠通過一個專屬URL地址
進入該公司的聊天室,聊天是一對一的,每一個公司能夠建立多個客服人員,每一個客服人員能夠同一時候和client的多個使用者聊天。
2、又如果是一個線上WebIM系統,實作類別似。qq的功能,client能夠看到好友線上狀態,線上列表。加入好友,
移除朋友。建立群組等。訊息的發送除了支援主要的文字外。還能支援表情、圖片和檔案。
來自:http://www.plhwin.com/2014/05/28/nodejs-socketio/
HTML5新特性之WebSocket