麗楓,鄭力新,王佳斌
(華僑大學 工學院,福建 泉州 362021)
摘要:隨著互聯網技術的不斷發展,Web技術在各個領域得到了不同程度的運用,人們對于Web應用的實時性提出了更高的要求,HTML5WebSocket協議因此得到了廣泛的關注。通過對基于HTTP的傳統Web實時通信方案進行分析,針對其中的不足與缺點,深入介紹了基于HTML5 WebSocket協議的實時通信機制以及相對于傳統方案的優勢,并通過使用Node.js的Express框架和HTML5 WebSocket協議的第三方應用程序編程接口Socket.io類庫實現了一個基于WebSocket協議的Web應用。經實驗表明,所描述的研究能成功地在客戶端和服務器端完成基于HTML5 WebSocket協議的實時通信過程并建立連接。
0引言
隨著互聯網技術的高速發展,人們對Web應用的實時性要求越來越高,傳統的Web實時通信方案已經無法滿足一些現實應用的需求。在長期的Web應用過程中該傳統方案逐漸露出資源浪費、實時性不高等問題,這些問題的出現對一些實時性要求較高的Web應用(如在線游戲、在線證券、設備監控等)造成了不好的用戶體驗。除此之外,這些不足還會制約Web實時通信的性能,對通信效率造成影響。面對這種情況,HTML5規范中定義了WebSocket協議來實現更好的用戶體驗和實時通信功能,并針對傳統的Web實時通信方案在實際運用中產生的資源浪費問題進行改善,提高通信效率。
目前,WebSocket協議的實現主要分為客戶端和服務器端兩部分。對于其客戶端而言,許多的主流瀏覽器(包括個人電腦和移動終端)如谷歌、火狐、IE等都在不同的版本上支持WebSocket客戶端應用程序編程接口。而對于其服務器端而言,也有許多常見的應用服務器如WebSphere、WebLogic、Tomcat等在不同的版本上支持WebSocket服務器端應用程序編程接口。綜上所述,本文從傳統的Web實時通信方案出發,針對其在Web應用中所體現的不足與缺點,深入研究WebSocket協議在Web實時通信方面的原理與優勢,并根據該協議的通信機制進行實現。
1傳統的Web實時通信方案
1.1輪詢
在早期的Web應用中,所采用的Web實時通信方案是輪詢。在使用輪詢時,客戶端需要頻繁地向服務器端發送HTTP請求來保持客戶端和服務器端的同步以便不斷地刷新客戶端所要呈現的信息。在這個過程中,客戶端無法確定合適的時間間隔向服務器端發送HTTP請求。若間隔的時間太短,客戶端頻繁的請求將會給服務器端造成巨大的壓力;若間隔的時間太長,就無法滿足客戶端和服務器端實時通信的要求。由于客戶端在頻繁地發送請求時服務器端的數據可能還未進行更新,導致服務器端返回的大部分應答包中的數據域為空,因而產生了很多無謂的網絡傳輸,浪費了大量的帶寬資源和其他網絡資源。對于圖1客戶端與服務器端的交互圖每次的HTTP請求而言,過長的HTTP頭信息也會占用不必要的帶寬資源。因此,這是一種缺乏靈活性又低效的Web實時通信方案。其中客戶端和服務器端的交互過程如圖1(a)所示。
1.2Comet技術
目前,Comet技術[1]的實現方式包括基于異步JavaScript和可擴展標記語言(Asynchronous JavaScript and Extensible Markup Language,AJAX)的長輪詢方式和基于Iframe的流方式。這兩種方式針對輪詢都做出了較大的改進。
1.2.1基于AJAX的長輪詢方式
基于AJAX的長輪詢方式[2]通過采用AJAX技術讓客戶端向服務器端發送HTTP請求,進而與服務器端建立連接,且該連接會在服務器端保持一段時間。若服務器端檢測到有新數據產生,那么它會將這些數據通過連接發送至客戶端,然后關閉連接;若服務器端在連接存在期間都沒有產生新的數據發送至客戶端,那么它將會向客戶端發送一個超時信息,然后關閉連接。無論服務器端的數據是否還在更新,在連接關閉之后,客戶端都需要重新向服務器端發送HTTP請求來建立連接。其中客戶端和服務器端的交互過程如圖1(b)所示。
雖然這種方式能夠對客戶端的部分頁面進行更新,減少服務器端發送的數據量,降低客戶端請求的頻率,減少無效的網絡傳輸,但當服務器端更新數據的速度較快時,基于AJAX的長輪詢方式將變成普通的輪詢,不僅會降低其性能,而且還會對服務器端造成較大的處理壓力。除此之外,為了保持HTTP連接長時間處于打開狀態,服務器端也需要消耗一定的服務器資源。因此,使用基于AJAX的長輪詢方式會產生資源浪費的問題。
1.2.2基于Iframe的流方式
基于Iframe的流方式[3]通過客戶端頁面上內嵌的一個Iframe標簽向服務器端發送HTTP請求,服務器端在響應該請求后與客戶端建立一條長連接。連接建立后,服務器端通過不斷地更新該連接的狀態以保持其不過期。當服務器端檢測到有新數據產生時,它會將新數據通過該連接發送給客戶端;當客戶端和服務器端之間的通信出現問題導致連接出現錯誤或者關閉時,客戶端會立即發出連接請求與服務器端重新建立連接,否則該連接會一直持續,不會關閉。其中客戶端和服務器端的交互過程如圖1(c)所示。
雖然這種方式有利于減少客戶端的請求次數,減輕客戶端和服務器端之間的網絡負擔,避免因頻繁的建立連接和關閉連接所帶來的資源浪費,但由于基于Iframe的流方式在連接過程中始終只維持一個長連接,因此客戶端頁面會一直處于加載過程中而無法顯示頁面加載完成,從而影響用戶體驗。且當有多個客戶端同時向服務器端發送HTTP請求時,由于服務器端長期只維持一個連接,因此會導致服務器端在這種高并發狀態下的處理能力降低,造成大量的服務器資源和其他網絡資源被消耗。
由于基于AJAX的長輪詢方式和基于Iframe的流方式在通信過程中一直采用HTTP作為通信協議,因此每次的HTTP請求和應答所攜帶的完整的HTTP頭信息不僅增加了實時更新信息時的數據傳輸量,還造成帶寬資源的浪費。此外,為了維持和協調通信過程中HTTP連接隨時處于可用狀態,服務器端也需要消耗資源。對于HTTP連接的建立和關閉過程而言,服務器端新產生的數據有可能會因為無法及時發送到客戶端而導致客戶端的數據丟失。由于這兩種方式對Web應用中的實時信息和非實時信息的請求/響應方式都未發生改變,因此,當實時信息的請求較為頻繁時,可能會造成服務器端較大的處理壓力,從而影響非實時信息的呈現。其中基于HTTP的Web實時應用模型如圖2所示。
2傳統的Web實時通信方案
WebSocket協議[45]是HTML5規范中的一種新的通信協議,是能夠在客戶端和服務器端進行異步通信的一種方法。它支持客戶端與服務器端通過全雙工通信的方式實現實時通信,本質上是一個基于傳輸控制協議的協議。因此,WebSocket連接的建立過程與傳輸控制協議連接的建立過程有些相似,客戶端和服務器端需要通過“握手”來建立WebSocket連接。
首先由客戶端向服務器端發送一個HTTP請求,該請求不同于一般的HTTP請求,它包含了一些附加的HTTP頭信息,其中一條信息“Upgrade:WebSocket”表明這是一個申請將當前HTTP協議升級為WebSocket協議的HTTP請求。若服務器端收到該請求后能正確解讀其HTTP頭信息,那么它會返回一個基于HTTP的應答報文給客戶端,此時連接建立成功[6],之后,客戶端和服務器端便可以通過該連接主動向對方發送或者接收數據,直到其中一方主動關閉該連接。其中客戶端和服務器端的交互過程如圖3所示。
通過WebSocket協議,客戶端和服務器端之間只要做一個“握手”的動作就可以建立一條雙向通信的通道。這不僅讓服務器端可以主動與客戶端互發信息,而且還避免了因客戶端頻繁請求而造成的網絡資源浪費、實時通信效率低、服務器處理壓力大等問題[7]。由于WebSocket連接采用WebSocket協議作為通信協議,因此在傳輸過程中數據幀的頭部信息所占的字節數將大大降低,從而有效地減小了通信過程中傳輸的數據量和網絡負載,節約了帶寬資源。在基于WebSocket協議的實時通信方案中,Web應用中的實時部分和非實時部分被加以區分。客戶端使用WebSocket協議獲取實時內容,使用HTTP協議獲取非實時內容。而服務器端則采用兩種不同的模塊來處理實時的WebSocket請求和非實時的HTTP請求,其應用模型如圖4所示。
通過上述模型可以看出,該實時通信方案使服務器端的結構更加明確,不僅讓WebSocket協議和HTTP協議各司其職、互不干擾,而且還降低了系統的耦合性,在最大程度上發揮了兩個模塊的功能。此外,由于采用以傳輸控制協議為基礎的WebSocket協議來處理實時服務,因此可以保證傳輸數據過程中的穩定性和及時性,在較大程度上提高了實時通信的性能。相對于傳統方案來說,該方案不僅減小了對服務器資源的浪費,也減輕了服務器端的處理壓力。
3基于WebSocket的Web實時通信應用實例
本文采用基于Node.js[8]的Express框架和Socket.io類庫來實現基于WebSocket的Web實時通信應用。其中,Node.js是一個JavaScript運行平臺,可用于構建響應速度快、容易擴展的網絡程序。但由于Node.js中只提供了大量的低端功能,因此文中將使用Express框架進行Web實時通信應用的開發。Express是一個能夠在Node.js中使用的 Web應用程序開發框架,它提供的一系列強大的特性,能夠讓Web應用程序的開發變得更加方便、快速。
Socket.io是一個開源、跨平臺且支持客戶端和服務器端進行實時雙向通信的WebSocket庫[9-10]。它包括客戶端的JavaScript庫和服務器端的Node.js模塊。它能夠根據不同的客戶端自動在一些實時通信機制中選擇合適的一個來實現Web實時應用。當使用支持HTML5技術的瀏覽器客戶端進行實時通信時,Socket.io會選譯效率最高、消耗服務器資源最少的WebSocket協議來實現實時通信,并在瀏覽器客戶端發生變化時自動選擇其他方式進行通信。因此,Socket.io能有效解決跨平臺的實時通信問題。
3.1在線聊天室的設計
在線聊天室的設計分為客戶端與服務器端兩個部分,其實時通信過程如圖5所示。
3.2在線聊天室的實現
在線聊天室的實現也分為客戶端和服務器端兩個部分。其中客戶端通過使用HTML5、層疊樣式表以及JavaScript來實現用戶名的驗證功能、消息顯示功能和數據傳送功能。服務器端通過JavaScript來實現與客戶端的實時通信功能、廣播功能以及在線用戶列表的管理功能。圖8用戶登錄成功時客戶端與服務器端的交互圖3.2.1客戶端的實現過程
當有新的客戶端用戶加入聊天室時,已在聊天室的用戶將會接收到新用戶加入聊天室的消息且用戶列表會被即時更新以顯示新加入的用戶名。新用戶所在頁面也會被更新以顯示所有在線用戶。當有客戶端用戶在聊天室發送聊天消息時,該消息會被即時廣播給所有在線用戶。當有客戶端用戶退出聊天室時,其他在線用戶將會接收到該用戶退出聊天室的消息且用戶列表會被實時更新以移除下線用戶的用戶名。下線用戶所在的頁面也會進行相應的調整。若用戶在聊天過程中直接退出聊天室頁面,則所有在線用戶都會收到該用戶退出聊天室的消息。客戶端的具體實現流程如圖6所示。
3.2.2服務器端的實現過程
當有多個客戶端用戶存在時,服務器端的主要功能包括管理所有在線用戶的用戶列表以及廣播它們之間的聊天消息。服務器端的具體實現流程如圖7所示。
3.2.3客戶端和服務器端的交互過程
本文主要針對用戶成功登錄進聊天室的情況進行介紹。當用戶成功登錄在線聊天室時,客戶端和服務器端通過觸發事件進行實時交互,其具體交互過程如圖8所示。
4結論
傳統的Web實時通信方案是在長期的應用實踐中發展出來的,其中比較常用的是基于AJAX的長輪詢方式和基于Iframe的流方式。但由于這兩種方案都是采用基于HTTP的通信方式,因此當Web實時應用采用這兩種方案時會產生難以解決的問題。而WebSocket協議的出現適時地提供了一種新的Web實時通信方案,它能夠更加快捷有效地構建出簡單高效的Web實時應用。因此,本文通過分析傳統的Web實時通信方案的不足之處,不僅從理論層面分析了基于WebSocket的Web實時通信方案的優勢,而且還通過使用HTML5、層疊樣式表和JavaScript編寫了具體的應用實例簡單的實現了該方案。隨著WebSocket協議的不斷發展,基于WebSocket的Web實時通信方案將會被廣泛應用。
參考文獻
[1] 蔡驥然,曹海傳.B/S架構下基于OPC與Comet技術的實時監控系統[J].計算機應用,2012,32(z2):214216.
[2] 文愛平,文德民.基于IE瀏覽器的Ajax Comet架構[J].電腦知識與技術,2010,6(17):46464648.
[3] 張家愛,孫飛.Comet技術在Web開發中的研究與應用[J].煤炭技術,2011,30(12):153154.
[4] 陸晨,馮向陽,蘇厚勤.HTML5 WebSocket握手協議的研究與實現[J].計算機應用與軟件,2015,32(1):128131,178.
[5] 李代立,陳榕.WebSocket在Web實時通信領域的研究[J].電腦知識與技術,2010,6(28):79237925,7935.
[6] 周東仿,孟寧.基于WebSocket的網絡設備自發現機制[J].計算機工程與設計,2013,34(2):392396,438.
[7] 溫照松,易仁偉,姚寒冰.基于WebSocket的實時Web應用解決方案[J].電腦知識與技術,2012,8(16):38263828.
[8] 王金龍,宋斌,丁銳.Node.js:一種新的Web應用構建技術[J].現代電子技術,2015,38(6):7073.
[9] 李廣文.基于Socket.io的互動教學即時反饋系統的設計與實現[J].中國現代教育裝備,2012(18):1012.
[10] 黃經贏.基于Socket.io+Node.js+Redis構建高效即時通訊系統[J].現代計算機(專業版),2014(19):6264,69.
隨著ChatGPT的熱度不斷提升,web即時通訊也再一次回到到大眾的眼中。 那種對話式的交互方式,以及自回歸的文本展現模式,給人一種更加親切、更加流暢的交流體驗。
在此總結一下web即時通訊中幾種常見的實現方式。
那這種交互方式,該如何實現呢? 在SSE之前,我們一般有如下幾種實現方式,Ajax輪詢、Comet和websocket;
Ajax輪詢是指在客戶端通過JavaScript定時發送HTTP請求到服務器,服務器在獲取到新數據時返回給客戶端。
setInterval(function() {
// 發送Ajax請求
$.ajax({
url: 'your_server_url',
method: 'GET',
success: function(response) {
// 處理響應數據
},
error: function(xhr, status, error) {
// 處理請求錯誤
}
});
}, interval_time);
success: function(response) {
// 處理響應數據
// ...
// 重新發起下一次Ajax請求
}
這種方式的缺點在于每次請求都是一次完整的HTTP連接,頻繁的HTTP請求會帶來較大的網絡負載;同時服務器端產生的數據變化也不能及時的發送到客戶端,而是需要等到下次請求發出時才能被獲取,為了能夠讓客戶端即時獲取服務端的數據變化,就需要提高輪詢的頻率;
而連接的創建和釋放在完整的一次HTTP請求中,占用了相當的比例,當輪詢頻率增加時,創建和釋放連接消耗的時間相當可觀,為了解決這一問題,就有了Comet。
Comet使用了一種稱為"服務器推送"的方法,通過建立長期的HTTP連接來實現從服務器到客戶端的數據推送。Comet的實現主要有兩種方式,基于Ajax的長輪詢(long-polling)方式和基于 Iframe 及 htmlfile 的流(http streaming)方式。
function longPolling() {
$.ajax({
url: 'your_server_url',
method: 'GET',
timeout: 30000, // 設置超時時間
success: function(response) {
// 處理服務器返回的數據
// ...
// 再次發起長輪詢請求
longPolling();
},
error: function(xhr, status, error) {
// 處理請求錯誤
// ...
// 再次發起長輪詢請求
longPolling();
}
});
}
// 發起初始請求
longPolling();
通過Ajax的長輪詢方式實現的即時通訊可以實現實時通訊的效果。但它也有一些缺點,如需要頻繁地發起請求和保持連接,服務器資源開銷較大。因此,在大規模的實時應用中,更推薦使用更高效的技術,如WebSocket或HTML5的Server-Sent Events。這些技術可以更好地支持實時通信,并減少不必要的網絡請求。
var iframe=document.createElement('iframe');
iframe.style.display='none';
iframe.src='your_server_url';
document.body.appendChild(iframe);
<!DOCTYPE html>
<html>
<head>
<title>Server Sent Events</title>
</head>
<body>
<script>
// 將即時數據作為HTML的內容輸出
document.write('your_instant_data');
</script>
</body>
</html>
iframe.onload=function() {
// 處理服務器返回的數據
var response=iframe.contentDocument.body.innerHTML;
// ...
// 重新發起請求以接收下一次的即時數據
iframe.src='your_server_url';
};
通過基于iframe和htmlfile的流方式實現的即時通訊可以實現實時通訊的效果。服務器端每次有新的數據時,會將數據以HTML的形式返回給客戶端,客戶端通過監聽iframe的onload事件來獲取并處理數據。這種方式相對于長輪詢的方式來說,可以減少請求的次數,減輕服務器的負擔。但是它仍然存在一些缺點,如需要頻繁地創建和銷毀iframe元素,以及一些瀏覽器兼容性的問題。因此,在實際應用中,更推薦使用更高效和更現代的技術,如WebSocket或HTML5的Server-Sent Events來實現即時通訊。
var socket=new WebSocket('ws://your_server_url');
socket.onopen=function() {
// WebSocket連接成功
// 可以在此處發送一些初始化的消息給服務器端
};
socket.onmessage=function(event) {
// 接收到服務器端發送的消息
var message=event.data;
// 處理接收到的消息
};
socket.onclose=function() {
// WebSocket連接關閉
};
socket.send('your_message');
// 引入WebSocket模塊(Node.js)
const WebSocket=require('ws');
// 創建WebSocket服務器
const wss=new WebSocket.Server({ port: 8080 });
// 監聽WebSocket的連接事件
wss.on('connection', function(ws) {
// 監聽WebSocket的消息事件
ws.on('message', function(message) {
// 處理接收到的消息
});
// 監聽WebSocket的關閉事件
ws.on('close', function() {
// 處理連接關閉
});
});
通過WebSocket實現的即時通訊可以實現實時雙向的通信效果,而且相比于長輪詢和流方式,WebSocket具有更低的延遲和更高的性能。同時,WebSocket還支持跨域通信和加密等功能,使得它成為了實時通訊的首選技術。在實際應用中,可以使用現成的WebSocket庫,如Socket.io來簡化開發。
使用WebSocket實現Web即時通訊的確有一些缺點,包括:
盡管WebSocket存在一些缺點,但它仍然是目前實現Web即時通訊的首選技術,由于其高性能和實時性,廣泛應用于聊天應用程序、協作工具、多人游戲和實時數據傳輸等場景。
服務器推送事件(Server-sent Events),簡稱SSE,是 HTML 5 規范中的一個組成部分,可以用來從服務端實時推送數據到瀏覽器端。相對于與之類似的 COMET 和 WebSocket 技術來說,服務器推送事件的使用更簡單,對服務器端的改動也比較小。對于某些類型的應用來說,服務器推送事件是最佳的選擇。
var eventSource=new EventSource('your_server_url');
eventSource.onmessage=function(event) {
// 接收到服務器端發送的消息
var message=event.data;
// 處理接收到的消息
};
eventSource.onerror=function(event) {
// 發生錯誤
};
eventSource.onclose=function(event) {
// 連接關閉
};
// 設置Content-Type為text/event-stream
res.setHeader('Content-Type', 'text/event-stream');
// 設置響應頭,指定連接時長和允許跨域等信息
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.setHeader('Access-Control-Allow-Origin', '*');
// 發送消息給客戶端
res.write('data: ' + message + '\n\n');
通過SSE實現的即時通訊是基于HTTP長連接的,與WebSocket相比,SSE更加簡單易用,并且不需要額外的協議和握手過程。然而,相對于WebSocket,SSE在性能和實時性方面可能稍遜一籌,并且不支持雙向通信。因此,在選擇使用SSE還是WebSocket時,需要根據具體的應用場景和需求來決定。
ventSource 是 HTML5 新增的特性,用于實現服務器端數據實時推送到客戶端。
使用 EventSource 可以這樣實現服務端數據實時推送:
服務器:
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class StreamServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/event-stream");
response.setCharacterEncoding("UTF-8");
PrintWriter writer=response.getWriter();
writer.write("data: some data\n\n");
writer.flush();
}
}
客戶端:
<script>
var source=new EventSource('/stream');
source.onmessage=function(e) {
console.log(e.data);
};
</script>
然后在客戶端使用 EventSource 連接到 http://yourserver/stream
工作原理:
這里的服務端使用了 Servlet 實現數據推送,reactor 模式下,每次調用 writer.write() 都會觸發響應被刷新,實現了數據的實時推送。
EventSource 特性兼容 IE10 以上瀏覽器,其他瀏覽器也都有較好的支持,所以在實際場景中比較方便使用。希望這個示例能幫你明白 EventSource 的工作原理和基本使用方法。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。