04-Windows频繁打开和关闭端口可能引发的问题
郑昀 20100810 隶属于《07.杂项》小节
老赵写了一篇《关于Windows频繁打开关闭端口时出现的问题》,论述了他从 Windows Web Server 2008 R2 通过 TCP 连接对 Cent OS 下的 MongoDB 数据库服务做压力测试,遇到了 Socket 连接资源耗尽,导致程序报告“由于系统缓冲区空间不足或队列已满,不能执行套接字上的操作。”错误的情况。
Socket 连接资源耗尽,在 Windows Server 下很常见,如果使用者程序写得没问题的话,一般都是微软(或其他软件厂商)设置的一些默认参数不合时宜导致的。
我以前写过一篇《02-Twisted 构建 Web Server 的 Socket 长链接问题 | 07.杂项 | Python》,记述了另外一种 Windows Server 常见问题:在 Python 开发语言下,用 twisted 框架编写一个 Web Server ,接收 Google PubSubHubbub 的 Hub Server 推送过来的 Google Reader 文章共享信息时,遇到了文件描述符到达上限的现象。这个模式的特点是,用 twisted.web.server 对接 PubSubHubbub Hub Server ,双方都支持重用连接,照理说,既然保持长连接并且重用 socket 连接,不应该出现这种“too many file descriptors in select”错误。这里的知识点是 Windows 下 select module 文件描述符(file descriptor)最多是512个,而在 Linux 下这个限制为 32767 ,如果超过这个限制值,就会出现类似上面的异常。
老赵的文章指出了一个有意思的知识点:临时端口号可分配范围,
临时端口号范围定义
当一个客户端程序(比如说一个浏览器)初始化一个 connection 连接远端服务(比如一个网站)时,客户端会打开一个“ephemeral(临时)”端口,端口号一般是随机分配的。你可以用微软工具 TCPView 来查看。
那么这个进程为出站连接(outbound connection)分配端口号时,端口号的可分配范围与操作系统有关。其中一个主要影响因素是 MaxUserPort ,位于注册表的 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters 下。
下面这张表列出了不同 Windows 操作系统所能分配的端口范围:
操作系统 | MaxUserPort | 端口范围,如果MaxUserPort | 端口范围,如果MaxUserPort | 最小值 | 最大值 |
Windows NT/2000/XP/2003 | Ending Port | 1025 to 5000 | 1025 to [MaxUserPort] | 5000 | 65535 |
Ending Port | 49152 to 65535 | 1025 to [MaxUserPort] | 5000 | 65535 | |
Number of Ports | 49152 to 65535 | 49152 to [49152 + MaxUserPort − 1] | 255 | 16384† |
表1 端口范围
当然,你如果在服务器上安装了微软的一些服务,还是会自动修改这个 MaxUserPort 参数的,比如Small Business Server 2000/2003 会修改为 60000, ISA Server 会修改为 65535, Exchange Server 2003 会改为 60000 。
看了这张表后,你会知道:
1、不要使用小于1025的端口号;
2、尽量使用系统临时端口范围(ephemeral port range)之外的端口,比如你定义自己应用程序要打开的端口号为8000,而不是5000。
3、重用连接。
查看了我的笔记本电脑(Win XP)和服务器(Windows 2003),注册表里都没有设置过这个MaxUserPort参数,所以端口范围就是默认的,在服务器上,可分配的临时端口是从1025到5000。
回到我的文件描述符打开过多的问题上,
重用端口失败+保持长连接生效
如果监听的 Google Reader User 足够多,这些用户又在一个时段集中分享文章,那么 Google PubSubHubub Hub Server 就会以极快的速度把数据推送过来,此时如果重用 socket 端口失败,而保持长连接策略又起作用,于是很快在 twisted web server 监听的端口上打开的文件描述符又爆了。
处理办法:
1、参考《02-Twisted 构建 Web Server 的 Socket 长链接问题 | 07.杂项 | Python》,
twisted.web.server.Site 类的初始化函数有一个可选参数 timeout ,它的默认值是 60*60*12 ,也就是12小时,它的目的是闲置端口超时自动关闭。在我们的情景下,超时时间太长,所以才会有许多处于 ESTABLISHED 状态的 Socket Connections 积累。
所以我们缩短为 15 分钟(也可以更短),让没有得到重用的 Connections 尽快自动关闭。只需要在开始执行:reactor.listenTCP(8080, Site(MyWebResource.setup(),timeout=60*15))
即可。
2、参考《03-PubSubHubbub 和 twisted 的 Persistent connections 能力 | 07.杂项 | Python》,接收到对方 Server 推送过来的数据后,异步扔给另一个方法解析和处理,render_POST 方法立刻 return NOT_DONE_YET 标志, 等异步处理数据成功了,回调里再 finish 掉当前的 request 。
参考资源:
2、Choosing a TCP Port for a Network Service ;
3、02-Twisted 构建 Web Server 的 Socket 长链接问题 | 07.杂项 | Python ;
4、03-PubSubHubbub 和 twisted 的 Persistent connections 能力 | 07.杂项 | Python