一、Web即時通信
所謂Web即時通信,就是說我們可以通過一種機制在網頁上立即通知用戶一件事情的發生,是不需要用戶刷新網頁的。Web即時通信的用途有很多,比如實時聊天,即時推送等。如當我們在登陸瀏覽 知乎時如果有人回答了我們的問題,知乎就會即時提醒我們,再比如現在電子商務的在線客服功能。這些能大大提高用戶體驗的功能都是基於Web即時通信實現的。
普通HTTP流程
客戶端從服務器端請求網頁
服務器作出相應的反應
服務器返回相應到客戶端
而由於HTTP請求是無狀態的,也就是說每次請求完成後,HTTP鏈接就斷開了,服務器和瀏覽器互相之間是完全不可知的,只有下一次再發起一次請求才能更新相應的信息。談到這裏我們就不難想到,我們可以簡單的讓瀏覽器每隔一個週期就發起一次請求,這樣就能在一定程度上模擬實時效果了,這也就是輪訓,術語叫做Polling。
Polling流程
客戶端使用普通的http方式向服務器端請求網頁
客戶端執行網頁中的JavaScript輪詢腳本,定期循環的向服務器發送請求(例如每5秒發送一次請求),獲取信息
服務器對每次請求作出響應,並返回相應信息,就像正常的http請求一樣
通過輪訓的方式我們就可以相對即時的獲取信息。但是由於輪訓的原理是使瀏覽器頻繁的向服務器發起請求,這在一定程度上會造成性能效率問題。爲了優化這些性能問題,人們又想到了一種方法。那就是在服務器接收到請求的時候不理解返回,而是隻有當有數據變化(或者超時)的時候才返回。這樣一來,我們就可以 利用一次請求最大可能的保持連接的有效性,大大的減少了Polling中的請求次數。這個方法叫做長輪訓,也叫做Long-Polling。
以上方法是實現Web實時通信的常用方法。當然在HTML5出來之後,我們就有更好的選擇啦。在HTML5中,我們可以使用SSE或者是WebSocket。SSE的全稱是Server Send Event,聽名字就很好理解啦。也就是由服務器來推送數據。看到這裏是不是興奮呢?其實很多情況下,我們只需要這種簡單的功能:由服務器推送數據到瀏覽器。比如推送比賽信息、股價的變化等等。
如果SSE還不能滿足我們的需求的話,我們完全就可以使用WebSocket啦。當使用WebSocket時,瀏覽器和服務器之間就建立了一個全雙工通道,互相都可以發送消息,完全的做到了及時,就像使用tcp socket一樣。
SSE和WebSocket的簡單對比:
WebSocket是全雙工通道,可以雙向通信,功能更強;SSE是單向通道,只能服務器向瀏覽器端發送。
WebSocket是一個新的協議,需要服務器端支持;SSE則是部署在HTTP協議之上的,現有服務器軟件都支持。
SSE是一個輕量級協議,相對簡單;WebSocket是一種較重的協議,相對複雜。
到這裏我們就基本瞭解了一些事先Web實時通信的機制,下一節中,我們將使用SSE實現一個簡單的在線聊天室。
二、基於SSE
的在線聊天室的實現
在線聊天室推送消息有很多種方式,在這門課程中我們使用SSE
來實現。爲了方便接收消息,我們藉助Redis
的pub/sub
功能來接收和發送消息。Web服務器端我們將使用Flask
實現。如果對Flask不是很熟悉,也可以在實驗樓學習相關的Flask課程。(http://www.shiyanlou.com/)
1. SSE
的工作方式
在上面的課程中,我們瞭解到SSE
是基於HTTP實現的,那麼瀏覽器怎麼樣知道這是一個服務器事件流呢?其實很簡單啦,就是將HTTP的頭部Content-Type
設置成text/event-stream
就可以了。其實SSE
,就是瀏覽器向服務器發送一個HTTP請求,然後服務器不斷單向地向瀏覽器推送"信息",這些信息的格式也非常簡單,就是前綴data:
加上發送的數據內容,然後以\n\n
結尾。
2. Redis
中的訂閱功能
Redis是很流行的一個內存數據庫,可以用於實現緩存,隊列等服務。在這門項目課程中我們將使用的Redis
的發佈/訂閱功 能。簡單來說,我們所謂訂閱功能就是我們可以訂閱一些頻道,然後當這些頻道有新的消息的時候我們就可以自動接收這些信息。當服務器接收到瀏覽器POST過來的消息的時候,會將這些信息發佈到特定的頻道中。接着我們之前訂閱了這些頻道的客戶端就回自動收到這些消息,最後我們就將這些消息通過SSE
推送到客戶端。
3. 實現
經過上面的分析,整個聊天室的流程已經很清晰啦。下面通過源代碼註釋的方式進行分析吧。在/home/shiyanlou
目錄下,創建目錄code
,然後在該目錄下創建源文件app.py
# 消息生成器 def event_stream(): pubsub = r.pubsub() # 訂閱'chat'頻道 pubsub.subscribe('chat') # 開始監聽消息,如果有消息產生在返回消息 for message in pubsub.listen(): print message # Server-Send Event的數據格式以'data:'開始 yield'data: %s\n\n' % message['data'] # 登陸函數,首次訪問需要登陸 @app.route('/login', methods=['GET', 'POST']) def login(): if flask.request.method == 'POST': # 將用戶信息記錄到session中 flask.session['user'] =flask.request.form['user'] returnflask.redirect('/') return'<form action="" method="post">user: <inputname="user">' # 接收javascriptpost過來的消息 @app.route('/post', methods=['POST']) def post(): message = flask.request.form['message'] user = flask.session.get('user', 'anonymous') now =datetime.datetime.now().replace(microsecond=0).time() # 將消息發佈到'chat'頻道中 r.publish('chat', u'[%s] %s: %s' % (now.isoformat(), user, message)) return flask.Response(status=204)
詳細代碼請登錄實驗樓http://www.shiyanlou.com/courses/?course_type=project&tag=all
4. 運行
由於使用了Redis,所以需要安裝redis服務器,通過以下步驟就可以將redis服務器,以及相關的依賴包安裝好。
$ sudo apt-get update
$ sudo apt-getinstall redis-server
$ sudo service redis start
$ sudo pip install redis
$ sudo pip install flask
安裝完成以後,運行,然後通過瀏覽器訪問http://127.0.0.1:8989
就看到效果啦.
另有其他項目課的詳細講解和內容歡迎登陸實驗樓http://www.shiyanlou.com/courses/?course_type=project&tag=all
官方網站:實驗樓 http://www.shiyanlou.com