沒興趣讀落落長心得的人, 這裡有用 Tornado 實作的程式和測試碼。
server push 是指從 server 主動送訊息給 client, 這裡有圖解一些達成 server push 的方式。其中我個人偏好 long polling 的作法，運作方式就和字面一樣: client 先發出一個連線, server 不要立即回應。等 server 需要主動通知 client 時, 再回傳資料。
這個作法的好處是:
各種 client 都適用, 只要能用 http 連線即可, 不是瀏覽器也OK, 現在各個平台都有好用的 http 函式庫。
不用擔心 client 網路環境問題。client 能主動建立往 server 的連線, 反過來就很難說了。
實作 long polling 的另一個問題是: 如何在察覺資料更新時, 能立即通知 client? 想像 client 透過連線 S 連往 server, server 先不作回應。接著 server 在其它地方取得新資料, 再透過 S 回傳資料。處理連線 S 的程式要怎麼暫停? 之後要怎麼返回執行? 這個作法可以 scale 嗎?
對於 single process 程式來說, 直覺且低成本的作法是: 在 thread A 讀資料, 發覺沒資料時改用 condition 變數 (搭配 lock) 停住程式, 然後在 thread B 收到新資料時, 用同一個 condition 變數通知 thread A 可以繼續讀資料。對應到 Tornnado 的寫法是用 locks.Condition:
有了 locks.Condition, 要實作一個完整可上線使用的 long polling 只是小事一椿, 下面的程式碼實作兩個 API /get 和 /set: 提供基本的 "get value by key" 和 "set value for key"。/get 沒資料時會先停住不回應, 等到 /set 取得資料後再立即返回。這只是示意用程式, 沒防錯也沒控管記憶體的用量:
g_cache = {}
key = self.get_argument("key", None)
entry = g_cache.get(key, None)
entry = g_cache[key] = Entry()
value = entry.get_value()
self.write(json.dumps({
'error_code': ERROR_OK,
# Wait the data to be ready.
timeout = self.get_argument("timeout", DEFAULT_TIMEOUT)
now = tornado.ioloop.IOLoop.current().time()
timeout = now + float(timeout)
condition = entry.get_condition()
yield condition.wait(timeout=timeout)  # Yield a Future.
logging.exception('condition timeout? timeout=%s' % str(timeout))
self.write(json.dumps({'error_code': ERROR_TIMEOUT}))
class SetHandler(tornado.web.RequestHandler):
value = self.get_argument("value", None)
entry.set_value(value)
self.write(json.dumps({'error_code': ERROR_OK}))
(r"/get", GetHandler),
(r"/set", SetHandler),
完整的程式見開頭的連結。
async python web