HTML5規(guī)范在傳統(tǒng)的web交互基礎(chǔ)上為我們帶來(lái)了眾多的新特性,隨著web技術(shù)被廣泛用于web APP的開(kāi)發(fā),,這些新特性得以推廣和使用,,而websocket作為一種新的web通信技術(shù)具有巨大意義。 什么是socket,?什么是websocket,??jī)烧哂惺裁磪^(qū)別?websocket是僅僅將socket的概念移植到瀏覽器中的實(shí)現(xiàn)嗎,? 我們知道,,在網(wǎng)絡(luò)中的兩個(gè)應(yīng)用程序(進(jìn)程)需要全雙工相互通信(全雙工即雙方可同時(shí)向?qū)Ψ桨l(fā)送消息),需要用到的就是socket,,它能夠提供端對(duì)端通信,,對(duì)于程序員來(lái)講,他只需要在某個(gè)應(yīng)用程序的一端(暫且稱(chēng)之為客戶(hù)端)創(chuàng)建一個(gè)socket實(shí)例并且提供它所要連接一端(暫且稱(chēng)之為服務(wù)端)的IP地址和端口,,而另外一端(服務(wù)端)創(chuàng)建另一個(gè)socket并綁定本地端口進(jìn)行監(jiān)聽(tīng),,然后客戶(hù)端進(jìn)行連接服務(wù)端,服務(wù)端接受連接之后雙方建立了一個(gè)端對(duì)端的TCP連接,,在該連接上就可以雙向通訊了,,而且一旦建立這個(gè)連接之后,通信雙方就沒(méi)有客戶(hù)端服務(wù)端之分了,,提供的就是端對(duì)端通信了,。我們可以采取這種方式構(gòu)建一個(gè)桌面版的im程序,讓不同主機(jī)上的用戶(hù)發(fā)送消息,。從本質(zhì)上來(lái)說(shuō),,socket并不是一個(gè)新的協(xié)議,它只是為了便于程序員進(jìn)行網(wǎng)絡(luò)編程而對(duì)tcp/ip協(xié)議族通信機(jī)制的一種封裝,。 websocket是html5規(guī)范中的一個(gè)部分,,它借鑒了socket這種思想,為web應(yīng)用程序客戶(hù)端和服務(wù)端之間(注意是客戶(hù)端服務(wù)端)提供了一種全雙工通信機(jī)制,。同時(shí),,它又是一種新的應(yīng)用層協(xié)議,websocket協(xié)議是為了提供web應(yīng)用程序和服務(wù)端全雙工通信而專(zhuān)門(mén)制定的一種應(yīng)用層協(xié)議,通常它表示為:ws://echo.websocket.org/?encoding=text HTTP/1.1,,可以看到除了前面的協(xié)議名和http不同之外,,它的表示地址就是傳統(tǒng)的url地址。 可以看到,,websocket并不是簡(jiǎn)單地將socket這一概念在瀏覽器環(huán)境中的移植,,本文最后也會(huì)通過(guò)一個(gè)小的demo來(lái)進(jìn)一步講述socket和websocket在使用上的區(qū)別。 websocket的通信原理和機(jī)制 既然是基于瀏覽器端的web技術(shù),,那么它的通信肯定少不了http,websocket本身雖然也是一種新的應(yīng)用層協(xié)議,,但是它也不能夠脫離http而單獨(dú)存在。具體來(lái)講,,我們?cè)诳蛻?hù)端構(gòu)建一個(gè)websocket實(shí)例,,并且為它綁定一個(gè)需要連接到的服務(wù)器地址,當(dāng)客戶(hù)端連接服務(wù)端的時(shí)候,,會(huì)向服務(wù)端發(fā)送一個(gè)類(lèi)似下面的http報(bào)文 可以看到,,這是一個(gè)http get請(qǐng)求報(bào)文,注意該報(bào)文中有一個(gè)upgrade首部,,它的作用是告訴服務(wù)端需要將通信協(xié)議切換到websocket,如果服務(wù)端支持websocket協(xié)議,,那么它就會(huì)將自己的通信協(xié)議切換到websocket,同時(shí)發(fā)給客戶(hù)端類(lèi)似于以下的一個(gè)響應(yīng)報(bào)文頭 返回的狀態(tài)碼為101,表示同意客戶(hù)端協(xié)議轉(zhuǎn)換請(qǐng)求,,并將它轉(zhuǎn)換為websocket協(xié)議。以上過(guò)程都是利用http通信完成的,,稱(chēng)之為websocket協(xié)議握手(websocket Protocol handshake),,進(jìn)過(guò)這握手之后,客戶(hù)端和服務(wù)端就建立了websocket連接,,以后的通信走的都是websocket協(xié)議了,。所以總結(jié)為websocket握手需要借助于http協(xié)議,建立連接后通信過(guò)程使用websocket協(xié)議,。同時(shí)需要了解的是,,該websocket連接還是基于我們剛才發(fā)起http連接的那個(gè)TCP連接。一旦建立連接之后,,我們就可以進(jìn)行數(shù)據(jù)傳輸了,,websocket提供兩種數(shù)據(jù)傳輸:文本數(shù)據(jù)和二進(jìn)制數(shù)據(jù)。 基于以上分析,,我們可以看到,,websocket能夠提供低延遲,高性能的客戶(hù)端與服務(wù)端的雙向數(shù)據(jù)通信,。它顛覆了之前web開(kāi)發(fā)的請(qǐng)求處理響應(yīng)模式,,并且提供了一種真正意義上的客戶(hù)端請(qǐng)求,服務(wù)器推送數(shù)據(jù)的模式,特別適合實(shí)時(shí)數(shù)據(jù)交互應(yīng)用開(kāi)發(fā),。 在websocket之前,,我們?cè)趙eb上要得到實(shí)時(shí)數(shù)據(jù)交互都采用了哪些方式? 1)定期輪詢(xún)的方式: 客戶(hù)端按照某個(gè)時(shí)間間隔不斷地向服務(wù)端發(fā)送請(qǐng)求,,請(qǐng)求服務(wù)端的最新數(shù)據(jù)然后更新客戶(hù)端顯示,。這種方式實(shí)際上浪費(fèi)了大量流量并且對(duì)服務(wù)端造成了很大壓力。 2)comet技術(shù) comet并不是一種新的通信技術(shù),,它是在客戶(hù)端請(qǐng)求服務(wù)端這個(gè)模式上的一種hack技術(shù),,通常來(lái)講,它主要分為以下兩種做法 (1)基于長(zhǎng)輪詢(xún)的服務(wù)端推送技術(shù) 具體來(lái)講,,就是客戶(hù)端首先給服務(wù)端發(fā)送一個(gè)請(qǐng)求,,服務(wù)端收到該請(qǐng)求之后如果數(shù)據(jù)沒(méi)有更新則并不立即返回,服務(wù)端阻塞請(qǐng)求的返回,,直到數(shù)據(jù)發(fā)生了更新或者發(fā)生了連接超時(shí),,服務(wù)端返回?cái)?shù)據(jù)之后客戶(hù)端再次發(fā)送同樣的請(qǐng)求,如下所示: 2)基于流式數(shù)據(jù)傳輸?shù)拈L(zhǎng)連接 通常的做法是在頁(yè)面中嵌入一個(gè)隱藏的iframe,然后讓這個(gè)iframe的src屬性指向我們請(qǐng)求的一個(gè)服務(wù)端地址,,并且為了數(shù)據(jù)更新,,我們將頁(yè)面上數(shù)據(jù)更新操作封裝為一個(gè)js函數(shù),將函數(shù)名當(dāng)做參數(shù)傳遞到這個(gè)地址當(dāng)中,, 服務(wù)端收到請(qǐng)求后解析地址取出參數(shù)(客戶(hù)端js函數(shù)調(diào)用名),,每當(dāng)有數(shù)據(jù)更新的時(shí)候,返回對(duì)客戶(hù)端函數(shù)的調(diào)用,,并且將要跟新的數(shù)據(jù)以js函數(shù)的參數(shù)填入到返回內(nèi)容當(dāng)中,,例如返回“ 可以看到comet技術(shù)是針對(duì)客戶(hù)端請(qǐng)求服務(wù)器響應(yīng)模型而模擬出的一個(gè)服務(wù)端推送數(shù)據(jù)實(shí)時(shí)更新技術(shù)。而且由于瀏覽器兼容性不能夠廣泛應(yīng)用,。 當(dāng)然并不是說(shuō)這些技術(shù)沒(méi)有用,,就算websocket已經(jīng)作為規(guī)范被提出并實(shí)現(xiàn),但是對(duì)于老式瀏覽器,,我們依然需要將它降級(jí)為以上方式來(lái)實(shí)現(xiàn)實(shí)時(shí)交互和服務(wù)端數(shù)據(jù)推送,。 到此為止,我們明白了websocket的原理,,下面通過(guò)一個(gè)簡(jiǎn)單的聊天應(yīng)用來(lái)再次加深下對(duì)websocket的理解,。 該應(yīng)用需求很簡(jiǎn)單,就是在web選項(xiàng)卡中打開(kāi)兩個(gè)網(wǎng)頁(yè),,模擬兩個(gè)web客戶(hù)端實(shí)現(xiàn)聊天功能,。 首先是客戶(hù)端如下: client.html <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8"> <title></title> <style> *{ margin: 0; padding: 0; } .message{ width: 60%; margin: 0 10px; display: inline-block; text-align: center; height: 40px; line-height: 40px; font-size: 20px; border-radius: 5px; border: 1px solid #B3D33F; } .form{ width:100%; position: fixed; bottom: 300px; left: 0; } .connect{ height: 40px; vertical-align: top; /* padding: 0; */ width: 80px; font-size: 20px; border-radius: 5px; border: none; background: #B3D33F; color: #fff; } </style> </head> <body> <ul id="content"></ul> <form class="form"> <input type="text" placeholder="請(qǐng)輸入發(fā)送的消息" class="message" id="message"/> <input type="button" value="發(fā)送" id="send" class="connect"/> <input type="button" value="連接" id="connect" class="connect"/> </form> <script></script> </body> </html> 客戶(hù)端js代碼 var oUl=document.getElementById('content'); var oConnect=document.getElementById('connect'); var oSend=document.getElementById('send'); var oInput=document.getElementById('message'); var ws=null; oConnect.onclick=function(){ ws=new WebSocket('ws://localhost:5000'); ws.onopen=function(){ oUl.innerHTML+="<li>客戶(hù)端已連接</li>"; } ws.onmessage=function(evt){ oUl.innerHTML+="<li>"+evt.data+"</li>"; } ws.onclose=function(){ oUl.innerHTML+="<li>客戶(hù)端已斷開(kāi)連接</li>"; }; ws.onerror=function(evt){ oUl.innerHTML+="<li>"+evt.data+"</li>"; }; }; oSend.onclick=function(){ if(ws){ ws.send(oInput.value); } } 這里使用的是w3c規(guī)范中關(guān)于HTML5 websocket API的原生API,,這些api很簡(jiǎn)單,就是利用new WebSocket創(chuàng)建一個(gè)指定連接服務(wù)端地址的ws實(shí)例,,然后為該實(shí)例注冊(cè)onopen(連接服務(wù)端),onmessage(接受服務(wù)端數(shù)據(jù)),,onclose(關(guān)閉連接)以及ws.send(建立連接后)發(fā)送請(qǐng)求。上面說(shuō)了那么多,,事實(shí)上可以看到html5 websocket API本身是很簡(jiǎn)單的一個(gè)對(duì)象和它的幾個(gè)方法而已,。 服務(wù)端采用nodejs,這里需要基于一個(gè)nodejs-websocket的nodejs服務(wù)端的庫(kù),,它是一個(gè)輕量級(jí)的nodejs websocket server端的實(shí)現(xiàn),,實(shí)際上也是使用nodejs提供的net模塊寫(xiě)成的。 server.js var app=require('http').createServer(handler); var ws=require('nodejs-websocket'); var fs=require('fs'); app.listen(80); function handler(req,res){ fs.readFile(__dirname+'/client.html',function(err,data){ if(err){ res.writeHead(500); return res.end('error '); } res.writeHead(200); res.end(data); }); } var server=ws.createServer(function(conn){ console.log('new conneciton'); conn.on("text",function(str){ broadcast(server,str); }); conn.on("close",function(code,reason){ console.log('connection closed'); }) }).listen(5000); function broadcast(server, msg) { server.connections.forEach(function (conn) { conn.sendText(msg); }) } 首先利用http模塊監(jiān)聽(tīng)用戶(hù)的http請(qǐng)求并顯示client.html界面,,然后創(chuàng)建一個(gè)websocket服務(wù)端等待用戶(hù)連接,,在接收到用戶(hù)發(fā)送來(lái)的數(shù)據(jù)之后將它廣播到所有連接到的客戶(hù)端。 下面我們打開(kāi)兩個(gè)瀏覽器選項(xiàng)卡模擬兩個(gè)客戶(hù)端進(jìn)行連接,, 客戶(hù)端一連接: 請(qǐng)求響應(yīng)報(bào)文如下: 可以看到這次握手和我們之前講的如出一轍,, 客戶(hù)端二的連接過(guò)程和1是一樣的,這里為了查看我們使用ff瀏覽器,,兩個(gè)客戶(hù)端的連接情況如下: 發(fā)送消息情況如下: 可以看到,,雙方發(fā)送的消息被服務(wù)端廣播給了每個(gè)和自己連接的客戶(hù)端。 從以上我們可以看到,,要想做一個(gè)點(diǎn)對(duì)點(diǎn)的im應(yīng)用,,websocket采取的方式是讓所有客戶(hù)端連接服務(wù)端,服務(wù)器將不同客戶(hù)端發(fā)送給自己的消息進(jìn)行轉(zhuǎn)發(fā)或者廣播,,而對(duì)于原始的socket,,只要兩端建立連接之后,就可以發(fā)送端對(duì)端的數(shù)據(jù),,不需要經(jīng)過(guò)第三方的轉(zhuǎn)發(fā),這也是websocket不同于socket的一個(gè)重要特點(diǎn),。 最后,,本文為了說(shuō)明html5規(guī)范中的websocket在客戶(hù)端采用了原生的API,實(shí)際開(kāi)發(fā)中,,有比較著名的兩個(gè)庫(kù)和sockjs,它們都對(duì)原始的API做了進(jìn)一步封裝,,提供了更多功能,都分為客戶(hù)端和服務(wù)端的實(shí)現(xiàn),,實(shí)際應(yīng)用中,,可以選擇使用。 |
|
來(lái)自: nxhujiee > 《◆易語(yǔ)言》