今天為now.in做了一個最陽春版本的聊天室
這聊天室用了AJAX/Comet技術,說穿了AJAX/Comet真的沒什麼,其實不過就一個http request在server端在接受到後,直到有事件發生才回傳,也有另一種做法是利用比較特別的header讓browser對於每次收到的資料分批處理,而這些都在一個response中完成,因為似乎後者還得有browser的支援,所以我用的是前者,而雖然概念上很簡單,但實際做起來會有些細節得注意的
前端不可以有proxy
原本我寫好後放上去結果不能跑,發現原因出在於透過WebFaction前端的Apache再rewrite到我的server,似乎在前端的連線數很快就被擠暴了,因為線上會有數百個連線一直在線上,所以如果前端有proxy,很容易就會被塞暴,後來改用直接連線,問題就解決了
一般的伺服器架構其實根本不適合Ajax/Comet
事實上一般常見的伺服器架構大多都不適合寫Ajax/Comet,為什麼呢? 原因很簡單,大多的伺服器架構都是thread pool之類的做法,一個連線進來,就從thread pool中拿一個worker丟給他去做,在完成之前都屬於在處理的階段,因此你有同時100個連線被Ajax/Comet卡住,就佔掉了thread pool中100個worker,這些worker什麼都不能做,通常只能sleep,定期檢查事件發生了沒,除了伺服器很快就會被塞暴以外,一點效率都沒有,像是Apache就是這類的架構
解決的方案: 非同步的IO
解決方案其實很簡單,就是改用非同步IO的網路框架,我個人最熟的就是Twisted,自然就是選擇這個,身為愛好Python的程式設計師,其實很多時候苦腦的不是沒有選擇,而是選擇太多,Python的非同步網路函式庫不是用一打就能數完的,多到誇張的一個地步,而且一直在增加,不久前又多出了一個Tornado,這也只是冰山一角,有人寫了一篇文章專門是在比較Python非同步網路函式庫的
Asynchronous Servers in Python
當然,這文章裡列出的也只是一部份而已
Twisted的Ajax/Comet做法
class Chatroom(resource.Resource):
isLeaf = True
def wait(self, request):
"""Wait for notifications
"""
self.waitingRequests.append(request)
log.debug('[%s] %d users are waiting',
self.name,
len(self.waitingRequests))
return server.NOT_DONE_YET
def notify(self):
"""Notify all waiting users
"""
log.info('[%s] Notify %d users', self.name, len(self.waitingRequests))
for request in self.waitingRequests:
if not request._disconnected:
userTick = int(request.args['tick'][0])
messages = self.getMessagesByTick(userTick)
result = dict(tick=self.tick, messages=messages)
request.write(json.dumps(result))
request.finish()
self.waitingRequests = []
省略掉雜七雜八的部份,其實最關鍵的地方很簡單,在於Twisted.web的resource的render可以回傳server.NOT_DONE_YET,來用以延後資料的回傳,接著之後要回傳時,只要呼叫request.write()就可以寫資料回去,而當request要結束時呼叫request.finish()即可,就是這麼簡單,在設計上很簡單,當發現使用者的tick已經跟上最新進度,伺服器這邊沒有新的東西給使用者,既然如此就讓使用者等待事件發生,把request丟進waitingRequest的list中,而當有新的資料進來,就可以呼叫notify,把新的資料丟回給每個在等待的使用者
在一般網頁伺服器也能用Ajax/Comet的解決方案
當然,並不是所有網頁程式用非同步的網路框架寫起來都會輕鬆容易,很多時候我們希望能夠在一般的PHP、Django、TurboGears網頁程式裡也能有Ajax/Comet的好處,但不幸的是這些都是同步式的伺服器架構,兩者各有優缺點,於是我就想到一個解決方案,各取其長,很簡單的解決方案就是,我們將需要長期等待事件發生的request都丟給非同步的伺服器,而一般的網頁都在一般的網頁伺服器處理,要怎麼做呢? 想法很簡單
- 當一般的網頁伺服器有需要讓使用者等候事件發生的場合時,只要用xmlrpc呼叫非同步的網頁伺服器,建立並回傳一個對應事件的唯一的url,一般的網頁伺服器拿到url後將user轉向到此url,所有的request連到這個url在timeout前都會一直等待
- 當事件發生時,以xmlrpc呼叫非同步網頁伺服器來讓那些request停止等待
- 當client發現request已載入或失敗時,會連到一般的網頁伺服器檢查是否有新資料,有的話進行更新,沒有的話重覆步驟1繼續等待
為什麼建立的url需要是唯一的呢? 很簡單,當事件的url已存在,一般的伺服器要重導給client,在這段期間如果事件被觸發,url如果一樣有可能會等到新的事件,而當事件被觸發,url會回傳404,所以整個流程可以重跑一次,可以正確並正常執行
這樣的解決方案有個最大的優點,就是任何網頁程式開發工具,只要是能使用xmlrpc都可以建立Ajax/Comet事件,我原本聊天室的設計就是採用這樣的方案,但是同樣的它也有不少缺點,一來就是當事件url被載入後,還要再開另一個request到我TurboGears2伺服器要資料來更新,再來就是Cross-Domain的問題,Ajax的request有不能跨domain的限制,如果將伺服器分開,這樣就沒辦法用XMLHtmlRequest來送要求,得改用iframe等解決方案,然而這也不是無解,一來是新的browser支援一種新的協定,可以讓Ajax跨domain送request,我曾經有實作過,但發現其實支援的browser實在太有限,我當時試似乎只有FireFox和某些有支援,IE? 甭提了,因此考慮到佔了大半的使用者,那不會是好的解決方案,另一種做法是將兩種server掛在同一個前端server下,用virtual host的方式,但是有個問題,如果前端的伺服器是同步式的伺服器,會遇到我先前提到的問題,所以前端的也得是非同步的伺服器,nginx似乎就是一種,Twisted也可以辦到
因為這樣的寫法還得加資料庫進去,為了方便加上省掉那個檢查新資料用的request,於是我就將聊天室所有東西都放在同一個Twisted的伺服器,包括訊息的暫存,這樣就簡單又可靠
Twisted + Ajax/Comet = TwistedComet
上段中提到的解決方案,因為是用Twisted加上又是Ajax/Comet所以叫TwistedComet,我開了一個Google code project,把程式丟上去,其實程式非常地簡單,就只是如上面提到,提供xmlrpc介面,然後提供非同步request的等待和觸發等動作,專案的網址在此
有興趣可以玩看看
—-
更新 2012/08/01
依據我對Ajax/Comet開發的經驗,我將它做成雲端服務,現在只要註冊取得API就可以輕易地打造即時的網頁應用程式,有興趣請到
我有朋友說 她用聊天室會出現”網頁發生錯誤”
這怎麼辦呢??
這我倒是沒遇過,但我猜可能他的網路環境有擋非80 port,因為那聊天室開在非80 port,所以可能因為這樣被擋掉所以出現網頁發生錯誤
不好意思 我最近開電台 聽眾都會說聽得斷斷續續
聽到的音樂也大部份發生d-live的情況
之前沒聊天室時就很少會這樣
請問是什麼緣故呢 有什麼解決的方法呢
當dj很苦惱啊
我也很納悶為什麼會有這種情況發生,最主要的原因應該都是聽眾連美國主機的速度太慢,我自己用的是中華電信,我聽是幾乎沒有delay過,即使主機一樣在美國
要測試是否會不會delay的方式,最簡單的就是去下載MrDJ的檔案來測速看看
http://static.ez2learn.com/now.in/mrdj.zip
一般播放音樂的品質都設定在15.xKB/seconds,所以當你連我在美國主機的速度能夠超過這個值時,那聽音樂基本上應該都會是順暢的情況,但如果當你速度低於這個值,因為傳給你的音樂很快就被播完了,但你的頻寬來不及將更新的音樂補上,因此它就只能暫時停下來等待更新的音樂資料送到,這就是主要的delay原因
我知道當人太多時主機對外頻寬可能有被塞滿的可能,但是目前看來人數都沒之前多,而且也有很多人都能正常收聽,所以應該不是我主機那邊的問題
目前已知學術網路幾乎是一定會delay,因為學術網路連國外天生就極慢,而像其它人的中華電信之類的,也會delay我就覺得挺納悶的,有可能是鄰居開BT把頻寬佔滿,或是電信商連外頻寬不足,或是什麼情況我目前就不得而知,有空我會找時間研究看看到底是什麼樣的情況
您好:
最近在練習comet方面的php程式,
在網頁載入的時候會送出一個不間斷的request來最查詢資料,
但此時要再送出第二個request的時候會一直卡著。。。
應該是因為http 1.1的並發連結限制,
但是看到您的聊天室似乎在做查詢的時候還可以再送出request去發送發言的訊息,
不知道可否告訴小弟是用什麼方法呢 ><"
話說剛剛找到問題原因了。。。
是因為有 session_start 的關係,
天啊,好奇怪 = =
@Jeffrey
因為發言也會順便更新聊天內容,所以把原本正在polling的request給取消掉,就這樣而已
我也有”網頁發生錯誤”的問題
按進去說”物件不支援此屬性或方法”
請問有沒有解決的辦法呢
(不好意思我不大懂程式XD)
謝謝大大!!