02-Twisted 構建 Web Server 的 Socket 長連結問題
鄭昀 201005 隸屬於《07.雜項》
背景
利用幾句簡單代碼可以構建一個 Web Server:
from twisted.internet import reactor
from twisted.web.server import Site
from my_webhandler import *
reactor.listenTCP(8080, Site(MyWebResource.setup()))
更複雜的運用參見IBM文件庫:第 1 部分講述非同步伺服器編程; 第 2 部分介紹編寫Web服務的進階技術; 第 3 部分用 Woven 模板實現動態Web伺服器;第 4 部分講述如何利用 SSH。或者Configuring and Using the Twisted.Web Server。
有時易造成 Socket 串連開啟過多
當用此 Web Server 接收 PubSubHubbub Hub Server 發送過來的各種請求時,遇到了一個大問題:
隨著時間推移,處於 ESTABLISHED 狀態的 Socket 串連越來越多,慢慢抵達500多個,
TCP X.X.X.X:8080 72.14.192.68:55693 ESTABLISHED
TCP X.X.X.X:8080 74.125.126.80:59064 ESTABLISHED
最終導致服務爆出異常“too many file descriptors in select”,當此異常發生時,已無法挽救,只能重啟服務。
這裡的知識點是 Windows 下 select module 檔案描述符(file descriptor)最多是512個,而在 Linux 下這個限制為 32767 ,如果超過這個限制值,就會出現類似上面的異常。
我們暫且不管這個問題牽涉到 PubSubHubbub Hub 喜歡保持長連結(Hubs MAY leave their TCP connections open and reuse them to make HTTP requests for freshly published events)並maybe重用串連的習慣。
既然 Server 端(指我們的 WebServer)無法保證總能斷開閑置串連,那麼可以通過設定閑置逾時來解決這個問題。
twisted.web.server.Site 的 timeout 設定
twisted.web.server.Site 類的初始化函數有一個選擇性參數 timeout ,它的預設值是 60*60*12 ,應該就是12小時。
這個 timeout 值經由 twisted.web.http.HTTPFactory 的初始化函數賦值給 twisted.internet.protocol 的 timeOut 屬性,從而能夠在底層 HTTP 協議發現串連閑置逾時後交由 TimeoutMixin 處理。
12小時太長。所以才會有許多處於 ESTABLISHED 狀態的 Socket Connections 積累。所以我們縮短為 15分鐘。So,我們只需要在開始執行:
reactor.listenTCP(8080, Site(MyWebResource.setup(),timeout=60*15))
即可。
這樣,當 Socket 接收完資料後(此時調用self.resetTimeout()重設)串連閑置時間逾時,將預設調用 twisted.protocols.policies.TimeoutMixin.timeoutConnection 函數,它的定義是:
def timeoutConnection(self):
"""Called when the connection times out.
Override to define behavior other than dropping the connection.
"""
self.transport.loseConnection()
也就是關閉串連。此時,會有輸出提示的:
Timing out client: IPv4Address(TCP, '72.14.192.68', 43949)
經過實踐,確實可以讓Web Server 佔用的 Socket 串連大為減少。
何為 TimeoutMixin
有人和我一樣抱怨 Twisted 的這個問題:
『Client will not close the connect unit it disconnects itself. But the server can't control the connect. In some critical environment, the server has to maintain a lot of connect which maybe is idle.』
有人回複說, twisted.protocols.policies.TimeoutMixin 可以讓 Server 主動中斷連線:
class TimeoutTester(protocol.Protocol, policies.TimeoutMixin):
timeOut = 3
timedOut = 0
def connectionMade(self):
self.setTimeout(self.timeOut)
def dataReceived(self, data):
self.resetTimeout()
protocol.Protocol.dataReceived(self, data)
def connectionLost(self, reason=None):
self.setTimeout(None)
def timeoutConnection(self):
self.timedOut = 1
另一個範例參見:
http://code.google.com/p/proxy65/source/browse/trunk/proxy65/socks5.py
參考資源:
1、How to set client timeout with reactor.listenTCP?
2、參考 twisted 的文檔 《Knowing When We're Not Wanted》:
『
Sometimes it is useful to know when the other side has broken the connection. Here is an example which does that:
from twisted.web.resource import Resource
from twisted.web import server
from twisted.internet import reactor
from twisted.python.util import println
class ExampleResource(Resource):
def render_GET(self, request):
request.write("hello world")
d = request.notifyFinish()
d.addCallback(lambda _: println("finished normally"))
d.addErrback(println, "error")
reactor.callLater(10, request.finish)
return server.NOT_DONE_YET
resource = ExampleResource()
This will allow us to run statistics on the log-file to see how many users are frustrated after merely 10 seconds.
』
3、關於Windows頻繁開啟關閉連接埠時出現的問題 ;
4、Choosing a TCP Port for a Network Service ;
5、02-Twisted 構建 Web Server 的 Socket 長連結問題 | 07.雜項 | Python ;
6、Windows頻繁開啟和關閉連接埠可能引發的問題 | 07.雜項 。