先来大概说一下,网络编程中总是分不开服务器和客户端,所谓的超时也分两种情况,一种是服务器等待客户端连接超时,一种是服务器处理客户端请求超时(可以理解为sever和handle),这两种情况要分别对待。
先来看看等待连接超时的server timeout
python对服务器也有简单的封装,先看看文档。
+------------+
| BaseServer |
+------------+
|
v
+-----------+ +------------------+
| TCPServer |------->| UnixStreamServer |
+-----------+ +------------------+
|
v
+-----------+ +--------------------+
| UDPServer |------->| UnixDatagramServer |
+-----------+ +--------------------+
这就是基础服务器的继承关系,当然除此之外还有HTTP的服务器HTTPServer继承自TCPServer等。
在基类BaseServer中有一个timeout变量。BaseServer中有一个handle_request函数,源码如下:
def handle_request(self):
"""Handle one request, possibly blocking.
Respects self.timeout.
"""
# Support people who used socket.settimeout() to escape
# handle_request before self.timeout was available.
timeout = self.socket.gettimeout()
if timeout is None:
timeout = self.timeout
elif self.timeout is not None:
timeout = min(timeout, self.timeout)
fd_sets = _eintr_retry(select.select, [self], [], [], timeout)
if not fd_sets[0]:
self.handle_timeout()
return
self._handle_request_noblock()
可以看到,这里设置了timeout并用了select的复用,select的时间间隔是timeout,如果不设置timeout,那就一直等待。
需要说明的一点,一旦创建了socket服务,那么有两种方法可以开启服务,分别是server_forerver()和handle_request(),handle_request的代码如上,那么server_forerver的代码又是怎样的?如下:
def serve_forever(self, poll_interval=0.5):
"""Handle one request at a time until shutdown.
Polls for shutdown every poll_interval seconds. Ignores
self.timeout. If you need to do periodic tasks, do them in
another thread.
"""
self.__is_shut_down.clear()
try:
while not self.__shutdown_request:
# XXX: Consider using another file descriptor or
# connecting to the socket to wake this up instead of
# polling. Polling reduces our responsiveness to a
# shutdown request and wastes cpu at all other times.
r, w, e = _eintr_retry(select.select, [self], [], [],
poll_interval)
if self in r:
self._handle_request_noblock()
finally:
self.__shutdown_request = False
self.__is_shut_down.set()
注意,这儿的注释有句话:Polls for shutdown every poll_interval seconds.
Ignores self.timeout. If you need to do periodic tasks,好吧,这里把ignores和timeout分写两行了,实在是不太注意到。
同样,server_forerver里面同样也用了select多路复用,这里的时间间隔是poll_interval,默认是0.5s,但是对比handle_request函数,server_forerver函数中并没有对handle_timeout,而server_request函数中有两行
if not fd_sets[0]:
self.handle_timeout()
return
在这个地方进行超时处理,这个地方可以对基类的handle_timeout进行覆盖,做我们想做的事,说明的是pyhton中所有的方法默认都是虚函数,所以只要覆盖了就会生效。
来简单梳理一下。当TCPServer创建好之后,有两种开启方式,server_forerver和handle_request,这两种的区别就是server_forever不会处理超时,而handle_request会处理超时。
再来看一下处理超时 handle_timeout
同样的,python对请求的处理也有相关的类,BaseRequestHandler、StreamRequestHandler、DatagramRequestHandler,注意的是BaseRequestHandler中并没有timeout,StreamRequestHandler才有。注释说是 # A timeout to apply to the request socket, if not None.所以这里指的是客户端从发起请求到请求结束超时,假如有恶意的连接攻击,可以设置超时,断开连接。
最后再来整理一下。超时有两种,一种是等待连接超时,一种是处理请求超时,这两种的超时在两个类中
示例代码:
service:
#!/usr/bin/python
import socket
import time
from SocketServer import TCPServer, StreamRequestHandler
__author__ = 'meiqizhang'
class MyStreamRequestHandlerr(StreamRequestHandler):
def __init__(self, request, client_address, server):
print('%s create handler...' % (time.time()))
StreamRequestHandler.__init__(self, request, client_address, server)
def handle(self):
self.request.settimeout(3)
try:
data = self.rfile.readline().strip()
time.sleep(5)
print('%s recv from client: %s %s' % (time.time(), self.client_address, data))
except socket.timeout as e:
print('%s catch an timtout exception. %s(%s)' % (time.time(), e, self.client_address))
self.finish()
class SimpleServer(TCPServer):
#timeout = 2
def __init__(self, server_address, RequestHandlerClass):
#self.timeout = 2
TCPServer.__init__(self, server_address, RequestHandlerClass)
def handle_timeout(self):
print('%s timeout...' % time.time())
def main():
server = SimpleServer(('127.0.0.1', 888), MyStreamRequestHandlerr)
server.timeout = 2
print('server running...')
#server.serve_forever()
while True:
server.handle_request()
if '__main__' == __name__:
main()
对于连接超时的设置三种方法都可以,可以直接写timeout = 2,也可以self.timeout = 2,还可以server.timeout = 2,而对于处理请求的超时的设置,要在handle中通过self.request.settimeout(3)来设置。
client:
#!/usr/local/bin/python
#coding=utf-8
import time
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('localhost', 888))
print("connect server success!")
data = 'hello world'
sock.send(data)
time.sleep(10)
data = sock.recv(1023)
print(data)
sock.close()
先运行server,因为没有连接,每隔2秒会打印出timeout。接着运行client,在收到请求后handler中暂停了5秒,超过了处理请求超时时间3秒,打印出catch an timtout exception. timed out。
最后说一点:行TCPServer收到客户端的accept连接请求,就会创建一个handler对象对其进行请求处理(会把accept返回是socket传递到handler)