python TCPServer, StreamRequestHandler设置超时时间timeout

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zhangqi_gsts/article/details/52829959

先来大概说一下,网络编程中总是分不开服务器和客户端,所谓的超时也分两种情况,一种是服务器等待客户端连接超时,一种是服务器处理客户端请求超时(可以理解为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)

猜你喜欢

转载自blog.csdn.net/zhangqi_gsts/article/details/52829959
今日推荐