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》:
『
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()
』
4、Choosing a TCP Port for a Network Service ;
5、02-Twisted 构建 Web Server 的 Socket 长链接问题 | 07.杂项 | Python ;