網頁收發消息是一個常見的系統應用場景,通常我們有兩種方式來完成消息的發送,一種是通過客戶端來拉取消息,一種是服務端推送消息,到底使用哪種方式好一點呢?
具體使用哪種方式,我們需要根據實際的業務場景來分析,沒有絕對正確的方式,只有適不適合。所以,我們分析一下網頁端的用戶一般都存在哪些應用場景:
- 系統將通知發送給用戶——這種場景下,用戶對于消息的實時性要求并不高
- 用戶和用戶之間發送聊天消息——這種場景下,用戶就對實時性的要求很高了,越實時越好
我們拋開純技術實現不談,只是從解決方案來談,由于是使用的是網頁端,HTTP協議是通過“請求-響應”的方式傳遞,網頁端和服務端之間是沒有消息通道的,那么怎么來實現消息的接收呢?
輪詢拉取
輪詢拉取可以說是所有消息的實現方案中最簡單的一種,實現起來也非常簡單。

大致的實現方法如下:
- 發送方發送消息后,消息先進入隊列中暫存(也可以是數據庫)
- 網頁端建立一個timer,固定時間(例如:30秒)輪詢到隊列(或數據庫)中拉取消息
- 無論有沒有拉到消息,收到返回的消息后,30秒后再次輪詢拉取
這種方式最大的優勢就是實現非常簡單,而且容易理解,早期的聊天室基本都是這種實現方式,我曾經給朋友做過一個答題的系統,有多個終端,每個終端看到的內容需要有所不同,也是使用的這種實現方式。
當然這種實現方式的缺點也是非常明顯:
- 時效性差:隨著timer間隔時間的長短,收到消息的延時時間會被拉長,以30秒為例,消息最大的延時就會達到30秒
- 效率差:網頁端會不停的請求服務器,但是發消息的頻率事實上并沒有這么高,如果10次輪詢才拉到一條消息,那么有效性只有10%,大量的浪費了服務器資源
使用這種方式,時效性和效率是矛盾的,我降低timer的間隔時間,就可以提高時效性,但是會降低效率,例如:間隔時間降低到1秒,這種基本就可以趨近于實時了,但是可能300次輪詢才會拉到1條消息,有效性只有0.3%了。
所以,由于這個不可調和的矛盾存在,這種解決方案只能適用于一些同時在線用戶少,對實時性要求不高的場景中。
長連接
如果想要同時保證時效性和效率,其實長連接是一個不錯的選擇,一般我們的PC端聊天軟件都是使用的長連接方式來實現。而網頁端的長連接實現方式通常有兩種:
- WebSocket
- FlashSocket
FlashSocket就不說了,如果不是網頁游戲的話其實很少會用到這個方案來做長連接,它要求用戶必須安裝了Flash插件。如果是html網頁端的話,其實更多會選擇WebSocket這種方案,WebSocket的優點非常明顯,建立一次握手以后,服務端和網頁端就可以雙向通信了,擺脫了HTTP的Request-Response的限制,消息的及時性和效率都大幅度的提升了。
但是WebSocket也不是那么簡單,其中的坑也非常的多,如何單個生產者推送給多個消費者,如何保證不重復推送,斷線以后的重連等等。當然更重要的是,不同的瀏覽器對于WebSocket的支持可能不同,兼容性也是一大問題,所以使用得并不是很多。
那有沒有一種更常用的方法來處理消息的接收呢?
HTTP長輪詢
想要建立一條HTTP長輪詢的通道,我們需要在瀏覽器和服務器之間建立一條通知連接。

而這條通知連接不同于普通的HTTP連接,它要有一些特殊性:
- 這個HTTP連接只能用來收取推送的消息
- 不同于普通的Request-Response HTTP請求,這個HTTP連接不會馬上響應,會先被Hold在這里,知道接到通知的消息或者超過了約定的時間(我們都知道,HTTP請求是會有超時的,一般我們都會設置一個請求超時的閾值,如果超過這個閾值,那么請求就會被粗暴的斷開,返回一個錯誤消息,為了保證我們的請求不被粗暴的對待,我們需要在超時之前優雅的返回一個結果)
怎么來Hold住這個請求呢?
場景一:隊列里面有消息

- 發起一個通知連接HTTP請求
- 發現消息隊列里面有消息,于是拿到消息然后立刻返回
- 收到返回的消息后,立刻再次發起通知連接的請求
場景二:隊列里面沒有消息

- 發起一個通知連接HTTP請求
- 發現消息隊列里面沒有消息,于是一直等待直到達到時間閾值然后返回
- 收到返回的消息后,立刻再次發起通知連接的請求
個人認為,長輪詢的請求就一直保持對消息隊列的數據拉取就行,如果有實時的消息來了,也等到它進入消息隊列以后再處理,這樣可以防止消息丟失,也可以降低系統的復雜度。
總的來說,網頁端的消息接收,用什么方式好呢?拉和推都可以,每種方式有每種方式的優缺點。
- 如果業務不復雜,實時性不高,建議輪詢拉取
- 最佳方案是推,但是WebSocket和FlashSocket各有局限性,實現起來也麻煩一點
- 常見方式就是長輪詢,需要開辟一條專用的消息通道