定時刷新的不足與改進
青云譜網(wǎng)站制作公司哪家好,找創(chuàng)新互聯(lián)公司!從網(wǎng)頁設計、網(wǎng)站建設、微信開發(fā)、APP開發(fā)、自適應網(wǎng)站建設等網(wǎng)站項目制作,到程序開發(fā),運營維護。創(chuàng)新互聯(lián)公司于2013年成立到現(xiàn)在10年的時間,我們擁有了豐富的建站經(jīng)驗和運維經(jīng)驗,來保證我們的工作的順利進行。專注于網(wǎng)站建設就選創(chuàng)新互聯(lián)公司。
web開發(fā)中可能遇到這樣的場景:網(wǎng)頁里的某一塊區(qū)域里寫了一些內(nèi)容,但這些內(nèi)容不是固定的,即使看網(wǎng)頁的人沒有做任何操作,它們也會隨時間不斷變化。股票行情、活動或游戲的榜單都是比較常見的例子。
對此,一般的做法是用setTimeout()或setInverval()定時執(zhí)行任務,任務內(nèi)容是Ajax訪問一次服務器,并在成功拿到返回數(shù)據(jù)后去更新頁面。
這種定時刷新的做法會有這樣一些感覺不足的地方:
造成這些不足的原因歸結起來,主要還是由于服務器的響應總是被動的。HTTP協(xié)議限制了一次通信總是由客戶端發(fā)起請求,再由服務器端來返回響應。
因此,如果讓服務器端也可以主動發(fā)送信息到客戶端,就可以很大程度改進這些不足。WebSocket就是一個實現(xiàn)這種雙向通信的新協(xié)議。
WebSocket是基于HTTP的功能追加協(xié)議
WebSocket最初由html5提出,但現(xiàn)在已經(jīng)發(fā)展為一個獨立的協(xié)議標準。WebSocket可以分為協(xié)議(Protocol)和API兩部分,分別由IETF和W3C制定了標準。
先來看看WebSocket協(xié)議的建立過程。
為了實現(xiàn)WebSocket通信,首先需要客戶端發(fā)起一次普通HTTP請求(也就是說,WebSocket的建立是依賴HTTP的)。請求報文可能像這樣:
GET ws://websocket.example.com/ HTTP/1.1 Host: websocket.example.com Upgrade: websocket Connection: Upgrade Origin: http://example.com Sec-WebSocket-Key:pAloKxsGSHtpIHrJdWLvzQ== Sec-WebSocket-Version:13
其中HTTP頭部字段Upgrade: websocket和Connection: Upgrade很重要,告訴服務器通信協(xié)議將發(fā)生改變,轉(zhuǎn)為WebSocket協(xié)議。支持WebSocket的服務器端在確認以上請求后,應返回狀態(tài)碼為101 Switching Protocols的響應:
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: nRu4KAPUPjjWYrnzxDVeqOxCvlM=
其中字段Sec-WebSocket-Accept是由服務器對前面客戶端發(fā)送的Sec-WebSocket-Key進行確認和加密后的結果,相當于一次驗證,以幫助客戶端確信對方是真實可用的WebSocket服務器。
驗證通過后,這個握手響應就確立了WebSocket連接,此后,服務器端就可以主動發(fā)信息給客戶端了。此時的狀態(tài)比較像服務器端和客戶端接通了電話,無論是誰有什么信息想告訴對方,開口就好了。
一旦建立了WebSocket連接,此后的通信就不再使用HTTP了,改為使用WebSocket獨立的數(shù)據(jù)幀(這個幀有辦法看到,見后文)。
整個過程像這樣:
簡單的應用示例
應用WebSocket有這樣幾件事要做:
服務器端
以Node的服務器為例,我們使用ws這個組件,這樣搭建一個支持WebSocket的服務器端:
var request = require("request"); var dateFormat = require("dateformat"); var WebSocket = require("ws"), WebSocketServer = WebSocket.Server, wss = new WebSocketServer({ port: 8080, path: "/guest" }); // 收到來自客戶端的連接請求后,開始給客戶端推消息 wss.on("connection", function(ws) { ws.on("message", function(message) { console.log("received: %s", message); }); sendGuestInfo(ws); }); function sendGuestInfo(ws) { request("http://uinames.com/api?region=china", function(error, response, body) { if (!error && response.statusCode === 200) { var jsonObject = JSON.parse(body), guest = jsonObject.name + jsonObject.surname, guestInfo = { guest: guest, time: dateFormat(new Date(), "HH:MM:ss") }; if (ws.readyState === WebSocket.OPEN) { // 發(fā),送 ws.send(JSON.stringify(guestInfo)); // 用隨機來“裝”得更像不定時推送一些 setTimeout(function() { sendGuestInfo(ws); }, (Math.random() * 5 + 3) * 1000); } } }); }
這個例子使用了姓名生成站點uinames的API服務,來生成{guest: "人名", time: "15:26:01"}這樣的數(shù)據(jù)。函數(shù)sendGuestInfo()會不定時執(zhí)行,并把包含姓名和時間的信息通過send()方法發(fā)送給客戶端。另外,注意send()方法需要以字符串形式來發(fā)送json數(shù)據(jù)。
這就像是服務器自己在做一些事,然后在需要的時候會通知客戶端一些信息。
客戶端
客戶端我們使用原生javascript來完成(僅支持WebSocket的瀏覽器):
var socket = new WebSocket("ws://localhost:8080/guest"); socket.onopen = function(openEvent) { console.log("WebSocket conntected."); }; socket.onmessage = function(messageEvent) { var data = messageEvent.data, dataObject = JSON.parse(data); console.log("Guest at " + dataObject.time + ": " + dataObject.guest); }; socket.onerror = function(errorEvent) { console.log("WebSocket error: ", errorEvent); }; socket.onclose = function(closeEvent) { console.log("WebSocket closed."); };
WebSocket的URL格式是ws://與wss://。因此,需要注意下URL地址的寫法,這也包括注意WebSocket服務器端的路徑(如這里的/guest)等信息。因為是本地的示例所以這里是localhost。
客戶端代碼的流程很簡單:創(chuàng)建WebSocket對象,然后指定onopen、onmessage等事件的回調(diào)即可。其中onmessage是客戶端與服務器端通過WebSocket通信的關鍵事件,想要在收到服務器通知后做點什么,寫在onmessage事件的回調(diào)函數(shù)里就好了。
效果及分析
通過node server(假定服務器端的文件名為server.js)啟動WebSocket服務器后,用瀏覽器打開一個引入了前面客戶端代碼的html(直接文件路徑file:///就可以),就可以得到像這樣的結果:
聯(lián)系前面客戶端的代碼可以想到,實際從創(chuàng)建WebSocket對象的語句開始,連接請求就會發(fā)送,并很快建立起WebSocket連接(不出錯誤的話),此后就可以收到來自服務器端的通知。如果此時客戶端還想再告訴服務器點什么,這樣做:
socket.send("Hello, server!");
服務器就可以收到:
當然,這也是因為前面服務器端的代碼內(nèi)同樣設置了message事件的回調(diào)。在這個客戶端和服務器都是javascript的例子中,無論是服務器端還是客戶端,都用send()發(fā)送信息,都通過message事件設置回調(diào),形式上可以說非常一致。
其他可用的數(shù)據(jù)類型
WebSocket的send()可以發(fā)送的消息,除了前面用的字符串類型之外,還有兩種可用,它們是Blob和ArrayBuffer。
它們都代表二進制數(shù)據(jù),可用于原始文件數(shù)據(jù)的發(fā)送。比如,這是一個發(fā)送Blob類型數(shù)據(jù)以完成向服務器上傳圖片的例子:
var fileEl = document.getElementById("image_upload"); var file = fileEl.files[0]; socket.send(file);
然后服務器端可以這樣把文件保存下來:
var fs = require("fs"); wss.on("connection", function(ws) { ws.on("message", function(message) { fs.writeFile("upload.png", message, "binary", function(error) { if (!error) { console.log("File saved."); } }); }); });
在客戶端接收二進制數(shù)據(jù)時,需注意WebSocket對象有一個屬性binaryType,初始值為"blob"。因此,如果接收的二進制數(shù)據(jù)是ArrayBuffer,應在接收之前這樣做:
socket.binaryType = "arraybuffer";
其他WebSocket服務器端
其他語言來做WebSocket服務器是怎樣的呢?下面是一個php的WebSocket服務器的例子(使用Ratchet):
<?php use Ratchet\ConnectionInterface; use Ratchet\MessageComponentInterface; require __DIR__ . '/vendor/autoload.php'; class GuestServer implements MessageComponentInterface { public function onOpen(ConnectionInterface $conn) { $conn->send('The server is listening to you now.'); } public function onMessage(ConnectionInterface $conn, $msg) { $conn->send($this->generateGuestInfo()); } public function onClose(ConnectionInterface $conn) { } public function onError(ConnectionInterface $conn, \Exception $e) { $conn->close(); } private function generateGuestInfo() { $jsonString = file_get_contents('http://uinames.com/api?region=china'); $jsonObject = json_decode($jsonString, true); $guest = $jsonObject['name'] . $jsonObject['surname']; $guestInfo = array( 'guest' => $guest, 'time' => date('H:i:s', time()), ); return json_encode($guestInfo); } } $app = new Ratchet\App('localhost', 8080); $app->route('/guest', new GuestServer(), array('*')); $app->run(); ?>
這個例子也同樣是由服務器返回{guest: "人名", time: "15:26:01"}的json數(shù)據(jù),不過由于php不像Node那樣可以用setTimeout()很容易地實現(xiàn)異步定時任務,這里改為在客戶端發(fā)送一次任意信息后,再去uinames取得信息并返回。
也可以看到,php搭建的WebSocket服務器仍然是近似的,主要通過WebSocket的open、message等事件來實現(xiàn)功能。
在Chrome開發(fā)工具中查看WebSocket數(shù)據(jù)幀
Chrome開發(fā)工具中選擇Network,然后找到WebSocket的那個請求,里面可以選擇Frames。在Frames里看到的,就是WebSocket的數(shù)據(jù)幀了:
可以看到很像聊天記錄,其中用淺綠色標注的是由客戶端發(fā)送給服務器的部分。
結語
總的來說,把服務器和客戶端拉到了一個聊天窗口來辦事,這確實是很棒的想法。
即使只從形式上說,WebSocket的事件回調(diào)感覺也比定時任務用起來要更親切一些。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持創(chuàng)新互聯(lián)。