自定义异步非阻塞web框架
Python的Web框架中Tornado以异步非阻塞而闻名,本文基于非阻塞的Socket以及IO多路复用从而实现异步非阻塞的Web框架,其中便是众多异步非阻塞Web框架内部原理。
图示:
上面的是异步IO模块:作为客户端发送请求给服务端,实现同时发多个请求的的功能,select监听socket是否有变化,返回对应请求响应
下面的是异步非阻塞web框架:服务端同时处理客户端发来的多个请求
举例:
1.time.sleep()设置阻塞
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 import tornado.ioloop 2 import tornado.web 3 4 5 class MainHandler(tornado.web.RequestHandler): 6 def get(self): 7 import time 8 time.sleep(10) 9 self.write("Hello, world") 10 11 class IndexHandler(tornado.web.RequestHandler): 12 def get(self): 13 self.write("index, world") 14 15 application = tornado.web.Application([ 16 (r"/main", MainHandler), 17 (r"/index", IndexHandler), 18 ]) 19 20 if __name__ == "__main__": 21 application.listen(8888) 22 tornado.ioloop.IOLoop.instance().start() 23 24 #问题:先访问/main会等待10s,然后执行/index,这时候/index要等到/main完成后才能显示
2.增加future,解决IO阻塞问题
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 class Future(object): 2 """Placeholder for an asynchronous result. 3 4 A ``Future`` encapsulates the result of an asynchronous 5 operation. In synchronous applications ``Futures`` are used 6 to wait for the result from a thread or process pool; in 7 Tornado they are normally used with `.IOLoop.add_future` or by 8 yielding them in a `.gen.coroutine`. 9 10 `tornado.concurrent.Future` is similar to 11 `concurrent.futures.Future`, but not thread-safe (and therefore 12 faster for use with single-threaded event loops). 13 14 In addition to ``exception`` and ``set_exception``, methods ``exc_info`` 15 and ``set_exc_info`` are supported to capture tracebacks in Python 2. 16 The traceback is automatically available in Python 3, but in the 17 Python 2 futures backport this information is discarded. 18 This functionality was previously available in a separate class 19 ``TracebackFuture``, which is now a deprecated alias for this class. 20 21 .. versionchanged:: 4.0 22 `tornado.concurrent.Future` is always a thread-unsafe ``Future`` 23 with support for the ``exc_info`` methods. Previously it would 24 be an alias for the thread-safe `concurrent.futures.Future` 25 if that package was available and fall back to the thread-unsafe 26 implementation if it was not. 27 28 .. versionchanged:: 4.1 29 If a `.Future` contains an error but that error is never observed 30 (by calling ``result()``, ``exception()``, or ``exc_info()``), 31 a stack trace will be logged when the `.Future` is garbage collected. 32 This normally indicates an error in the application, but in cases 33 where it results in undesired logging it may be necessary to 34 suppress the logging by ensuring that the exception is observed: 35 ``f.add_done_callback(lambda f: f.exception())``. 36 """ 37 def __init__(self): 38 self._done = False 39 self._result = None 40 self._exc_info = None 41 42 self._log_traceback = False # Used for Python >= 3.4 43 self._tb_logger = None # Used for Python <= 3.3 44 45 self._callbacks = [] 46 47 # Implement the Python 3.5 Awaitable protocol if possible 48 # (we can't use return and yield together until py33). 49 if sys.version_info >= (3, 3): 50 exec(textwrap.dedent(""" 51 def __await__(self): 52 return (yield self) 53 """)) 54 else: 55 # Py2-compatible version for use with cython. 56 def __await__(self): 57 result = yield self 58 # StopIteration doesn't take args before py33, 59 # but Cython recognizes the args tuple. 60 e = StopIteration() 61 e.args = (result,) 62 raise e 63 64 def cancel(self): 65 """Cancel the operation, if possible. 66 67 Tornado ``Futures`` do not support cancellation, so this method always 68 returns False. 69 """ 70 return False 71 72 def cancelled(self): 73 """Returns True if the operation has been cancelled. 74 75 Tornado ``Futures`` do not support cancellation, so this method 76 always returns False. 77 """ 78 return False 79 80 def running(self): 81 """Returns True if this operation is currently running.""" 82 return not self._done 83 84 def done(self): 85 """Returns True if the future has finished running.""" 86 return self._done 87 88 def _clear_tb_log(self): 89 self._log_traceback = False 90 if self._tb_logger is not None: 91 self._tb_logger.clear() 92 self._tb_logger = None 93 94 def result(self, timeout=None): 95 """If the operation succeeded, return its result. If it failed, 96 re-raise its exception. 97 98 This method takes a ``timeout`` argument for compatibility with 99 `concurrent.futures.Future` but it is an error to call it 100 before the `Future` is done, so the ``timeout`` is never used. 101 """ 102 self._clear_tb_log() 103 if self._result is not None: 104 return self._result 105 if self._exc_info is not None: 106 try: 107 raise_exc_info(self._exc_info) 108 finally: 109 self = None 110 self._check_done() 111 return self._result 112 113 def exception(self, timeout=None): 114 """If the operation raised an exception, return the `Exception` 115 object. Otherwise returns None. 116 117 This method takes a ``timeout`` argument for compatibility with 118 `concurrent.futures.Future` but it is an error to call it 119 before the `Future` is done, so the ``timeout`` is never used. 120 """ 121 self._clear_tb_log() 122 if self._exc_info is not None: 123 return self._exc_info[1] 124 else: 125 self._check_done() 126 return None 127 128 def add_done_callback(self, fn): 129 """Attaches the given callback to the `Future`. 130 131 It will be invoked with the `Future` as its argument when the Future 132 has finished running and its result is available. In Tornado 133 consider using `.IOLoop.add_future` instead of calling 134 `add_done_callback` directly. 135 """ 136 if self._done: 137 fn(self) 138 else: 139 self._callbacks.append(fn) 140 141 def set_result(self, result): 142 """Sets the result of a ``Future``. 143 144 It is undefined to call any of the ``set`` methods more than once 145 on the same object. 146 """ 147 self._result = result 148 self._set_done() 149 150 def set_exception(self, exception): 151 """Sets the exception of a ``Future.``""" 152 self.set_exc_info( 153 (exception.__class__, 154 exception, 155 getattr(exception, '__traceback__', None))) 156 157 def exc_info(self): 158 """Returns a tuple in the same format as `sys.exc_info` or None. 159 160 .. versionadded:: 4.0 161 """ 162 self._clear_tb_log() 163 return self._exc_info 164 165 def set_exc_info(self, exc_info): 166 """Sets the exception information of a ``Future.`` 167 168 Preserves tracebacks on Python 2. 169 170 .. versionadded:: 4.0 171 """ 172 self._exc_info = exc_info 173 self._log_traceback = True 174 if not _GC_CYCLE_FINALIZERS: 175 self._tb_logger = _TracebackLogger(exc_info) 176 177 try: 178 self._set_done() 179 finally: 180 # Activate the logger after all callbacks have had a 181 # chance to call result() or exception(). 182 if self._log_traceback and self._tb_logger is not None: 183 self._tb_logger.activate() 184 self._exc_info = exc_info 185 186 def _check_done(self): 187 if not self._done: 188 raise Exception("DummyFuture does not support blocking for results") 189 190 def _set_done(self): 191 self._done = True 192 for cb in self._callbacks: 193 try: 194 cb(self) 195 except Exception: 196 app_log.exception('Exception in callback %r for %r', 197 cb, self) 198 self._callbacks = None 199 200 # On Python 3.3 or older, objects with a destructor part of a reference 201 # cycle are never destroyed. It's no longer the case on Python 3.4 thanks to 202 # the PEP 442. 203 if _GC_CYCLE_FINALIZERS: 204 def __del__(self, is_finalizing=is_finalizing): 205 if is_finalizing() or not self._log_traceback: 206 # set_exception() was not called, or result() or exception() 207 # has consumed the exception 208 return 209 210 tb = traceback.format_exception(*self._exc_info) 211 212 app_log.error('Future %r exception was never retrieved: %s', 213 self, ''.join(tb).rstrip())
1 #future = Future() 2 #作用: 3 #1.挂起当前请求,线程可以处理其他请求 4 #2.future设置值,当前挂起的请求返回
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 import tornado.ioloop 2 import tornado.web 3 from tornado import gen 4 from tornado.concurrent import Future 5 import time 6 #解决一个IO堵塞时,另外一个请求只能等待上一个请求完成后才能继续 7 8 class MainHandler(tornado.web.RequestHandler): 9 @gen.coroutine 10 def get(self): 11 future = Future() 12 # 特殊的形式等待5s,5s后执行doing方法 13 tornado.ioloop.IOLoop.current().add_timeout(time.time() + 5,self.doing) 14 # self.write("Hello, world") 15 yield future 16 17 def doing(self,*args,**kwargs): 18 self.write('Main End') 19 self.finish() 20 21 class IndexHandler(tornado.web.RequestHandler): 22 def get(self): 23 self.write("index, world") 24 25 application = tornado.web.Application([ 26 (r"/main", MainHandler), 27 (r"/index", IndexHandler), 28 ]) 29 30 if __name__ == "__main__": 31 application.listen(8888) 32 tornado.ioloop.IOLoop.instance().start() 33 34 #若开始先请求/main然后请求/index,由于MainHandler有future会先挂起当前请求,线程可以处理其他请求 35 #因此看到的是后面开始请求的/index直接处理完请求返回,而/main还需要等待5s才返回
3.内部再进行请求IO阻塞
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 import tornado.ioloop 2 import tornado.web 3 from tornado.concurrent import Future 4 from tornado import gen 5 6 class MainHandler(tornado.web.RequestHandler): 7 def get(self): 8 # 需要等待请求执行完成,才能处理下一个请求 9 import requests 10 requests.get('http://www.google.com') 11 self.write('Main') 12 13 class IndexHandler(tornado.web.RequestHandler): 14 def get(self): 15 self.write("index, world") 16 17 application = tornado.web.Application([ 18 (r"/main", MainHandler), 19 (r"/index", IndexHandler), 20 ]) 21 22 if __name__ == "__main__": 23 application.listen(8888) 24 tornado.ioloop.IOLoop.instance().start()
4.AsyncHTTPClient异步模块,解决IO阻塞问题
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 import tornado.ioloop 2 import tornado.web 3 from tornado.concurrent import Future 4 from tornado import gen 5 6 class MainHandler(tornado.web.RequestHandler): 7 @gen.coroutine 8 def get(self): 9 # 需要等待请求执行完成,才能处理下一个请求 10 # import requests 11 # requests.get('http://www.google.com') 12 # self.write('xxxx') 13 14 print('进入') 15 from tornado import httpclient 16 http = httpclient.AsyncHTTPClient() 17 yield http.fetch("http://www.google.com", self.done) 18 19 def done(self,*args,**kwargs): 20 print('完事') 21 self.write('Main') 22 self.finish('666') 23 24 class IndexHandler(tornado.web.RequestHandler): 25 def get(self): 26 self.write("index, world") 27 28 application = tornado.web.Application([ 29 (r"/main", MainHandler), 30 (r"/index", IndexHandler), 31 ]) 32 33 if __name__ == "__main__": 34 application.listen(8888) 35 tornado.ioloop.IOLoop.instance().start()
5.future实现异步非阻塞(set_result)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 import tornado.ioloop 2 import tornado.web 3 from tornado.concurrent import Future 4 from tornado import gen 5 6 future = None 7 class MainHandler(tornado.web.RequestHandler): 8 @gen.coroutine 9 def get(self): 10 global future 11 future = Future() 12 future.add_done_callback(self.doing) #未给future设置值,挂起当前请求 13 yield future 14 15 def doing(self,*args,**kwargs): 16 self.write('Main') 17 self.finish() 18 19 class IndexHandler(tornado.web.RequestHandler): 20 def get(self): 21 global future 22 future.set_result(None) #给future设置值,当前挂起的请求返回 23 self.write("index, world") 24 25 application = tornado.web.Application([ 26 (r"/main", MainHandler), 27 (r"/index", IndexHandler), 28 ]) 29 30 if __name__ == "__main__": 31 application.listen(8888) 32 tornado.ioloop.IOLoop.instance().start() 33 34 #情况就是:先访问/main,暂时挂起,然后访问/index,对应的类get方法给future设置值,因此两个请求基本同时完成
自定义web框架:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 import socket 2 import select 3 import time 4 5 class HttpRequest(object): 6 """ 7 用户封装用户请求信息 8 """ 9 def __init__(self, content): 10 """ 11 12 :param content: 用户发送的请求数据,请求头和请求体 13 """ 14 self.content = content 15 16 self.header_bytes = bytes() 17 self.body_bytes = bytes() 18 19 self.header_dict = {} 20 21 self.method = "" 22 self.url = "" 23 self.protocol = "" 24 25 self.initialize() 26 self.initialize_headers() 27 28 #分割请求头和请求体 29 def initialize(self): 30 temp = self.content.split(b'\r\n\r\n', 1) 31 print(temp) 32 if len(temp) == 1: 33 self.header_bytes += temp[0] 34 else: 35 h, b = temp[0],temp[1] 36 print(h,b) 37 self.header_bytes += h 38 self.body_bytes += b 39 @property 40 def header_str(self): 41 return str(self.header_bytes, encoding='utf-8') 42 43 def initialize_headers(self): 44 headers = self.header_str.split('\r\n') 45 first_line = headers[0].split(' ') 46 if len(first_line) == 3: 47 self.method, self.url, self.protocol = headers[0].split(' ') 48 for line in headers: 49 kv = line.split(':') 50 if len(kv) == 2: 51 k, v = kv 52 self.header_dict[k] = v 53 54 def index(request): 55 return 'index' 56 57 def main(request): 58 time.sleep(10) 59 return 'main' 60 61 62 routers = [ 63 ('/index',index), 64 ('/main',main) 65 66 ] 67 68 def run(): 69 sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 70 sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 71 sock.bind(('127.0.0.1',8888)) 72 sock.setblocking(False) #设置服务端非阻塞 73 sock.listen(10) 74 75 inputs = [] 76 inputs.append(sock) 77 78 while True: 79 rlist,wlist,elist = select.select(inputs,[],[],0.05) 80 for r in rlist: 81 if r == sock: 82 """新请求到来""" 83 conn,addr = sock.accept() #返回客户端的conn以及地址 84 conn.setblocking(False) #设置客户端非阻塞 85 inputs.append(conn) #监听客户端和服务端的socket变化 86 else: 87 """客户端发来数据""" 88 data = b"" 89 while True: 90 try: 91 chunk = r.recv(1024) 92 data = data + chunk 93 except Exception as e: 94 chunk = None 95 if not chunk: 96 break 97 98 # data进行处理,请求头和请求体 99 # 1.请求头获取url 100 # 2.去路由中匹配,获取指定的函数 101 # 3.执行函数,获取返回值 102 # 4.将返回值 r.sendall(b'dfafsfsg) 103 104 request = HttpRequest(data) 105 # print(request.url) 106 # print(request.method) 107 # print(request.header_dict) 108 # print(request.body_bytes) 109 110 ### url匹配路由 ### 111 import re 112 flag = False #是否匹配成功 113 func = None #url匹配对应的函数 114 for route in routers: 115 if re.match(route[0],request.url): 116 flag = True 117 func = route[1] 118 break 119 if flag: 120 result = func(request) 121 r.sendall(bytes(result,encoding='utf-8')) 122 else: 123 r.sendall(b'404') 124 125 126 #由于模拟http短链接,响应后断开 127 inputs.remove(r) 128 r.close() #关闭 129 130 if __name__ == '__main__': 131 run()
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 import socket 2 import select 3 import time 4 5 6 class HttpRequest(object): 7 """ 8 用户封装用户请求信息 9 """ 10 11 def __init__(self, content): 12 """ 13 14 :param content: 用户发送的请求数据,请求头和请求体 15 """ 16 self.content = content 17 18 self.header_bytes = bytes() 19 self.body_bytes = bytes() 20 21 self.header_dict = {} 22 23 self.method = "" 24 self.url = "" 25 self.protocol = "" 26 27 self.initialize() 28 self.initialize_headers() 29 30 # 分割请求头和请求体 31 def initialize(self): 32 temp = self.content.split(b'\r\n\r\n', 1) 33 print(temp) 34 if len(temp) == 1: 35 self.header_bytes += temp[0] 36 else: 37 h, b = temp[0], temp[1] 38 print(h, b) 39 self.header_bytes += h 40 self.body_bytes += b 41 42 @property 43 def header_str(self): 44 return str(self.header_bytes, encoding='utf-8') 45 46 def initialize_headers(self): 47 headers = self.header_str.split('\r\n') 48 first_line = headers[0].split(' ') 49 if len(first_line) == 3: 50 self.method, self.url, self.protocol = headers[0].split(' ') 51 for line in headers: 52 kv = line.split(':') 53 if len(kv) == 2: 54 k, v = kv 55 self.header_dict[k] = v 56 57 F = None 58 59 class Future(object): 60 def __init__(self): 61 self.result = None 62 63 def main(request): 64 global F 65 F = Future() 66 return F 67 68 def index(request): 69 return 'index' 70 71 def stop(request): 72 global F 73 F.result = b'xx' 74 return 'Stop' 75 76 routers = [ 77 ('/index', index), 78 ('/main', main), 79 ('/stop',stop) 80 81 ] 82 83 def run(): 84 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 85 sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 86 sock.bind(('127.0.0.1', 8888)) 87 sock.setblocking(False) # 设置服务端非阻塞 88 sock.listen(10) 89 90 inputs = [] 91 inputs.append(sock) 92 93 async_request_dict = {} 94 95 while True: 96 rlist, wlist, elist = select.select(inputs, [], [], 0.05) 97 for r in rlist: 98 if r == sock: 99 """新请求到来""" 100 conn, addr = sock.accept() # 返回客户端的conn以及地址 101 conn.setblocking(False) # 设置客户端非阻塞 102 inputs.append(conn) # 监听客户端和服务端的socket变化 103 else: 104 """客户端发来数据""" 105 data = b"" 106 while True: 107 try: 108 chunk = r.recv(1024) 109 data = data + chunk 110 except Exception as e: 111 chunk = None 112 if not chunk: 113 break 114 115 # data进行处理,请求头和请求体 116 # 1.请求头获取url 117 # 2.去路由中匹配,获取指定的函数 118 # 3.执行函数,获取返回值 119 # 4.将返回值 r.sendall(b'dfafsfsg) 120 121 request = HttpRequest(data) 122 # print(request.url) 123 # print(request.method) 124 # print(request.header_dict) 125 # print(request.body_bytes) 126 127 ### url匹配路由 ### 128 import re 129 flag = False # 是否匹配成功 130 func = None # url匹配对应的函数 131 for route in routers: 132 if re.match(route[0], request.url): 133 flag = True 134 func = route[1] 135 break 136 if flag: 137 result = func(request) 138 #### 判断result的类型是否是Future #### 139 if isinstance(result,Future): 140 async_request_dict[r] = result #将客户端和future对象相关联 141 else: 142 r.sendall(bytes(result, encoding='utf-8')) 143 # 由于模拟http短链接,响应后断开 144 inputs.remove(r) 145 r.close() # 关闭 146 else: 147 r.sendall(b'404') 148 # 由于模拟http短链接,响应后断开 149 inputs.remove(r) 150 r.close() # 关闭 151 152 # 客户端conn和future对象组成的,判断future是否设置值,若设置则对应挂起请求返回 153 for conn in async_request_dict.keys(): 154 future = async_request_dict[conn] 155 if future.result: 156 conn.send(future.result) 157 conn.close() 158 del async_request_dict[conn] 159 inputs.remove(conn) 160 161 if __name__ == '__main__': 162 run()
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 import socket 2 import select 3 import time 4 5 6 class HttpRequest(object): 7 """ 8 用户封装用户请求信息 9 """ 10 11 def __init__(self, content): 12 """ 13 14 :param content: 用户发送的请求数据,请求头和请求体 15 """ 16 self.content = content 17 18 self.header_bytes = bytes() 19 self.body_bytes = bytes() 20 21 self.header_dict = {} 22 23 self.method = "" 24 self.url = "" 25 self.protocol = "" 26 27 self.initialize() 28 self.initialize_headers() 29 30 # 分割请求头和请求体 31 def initialize(self): 32 temp = self.content.split(b'\r\n\r\n', 1) 33 print(temp) 34 if len(temp) == 1: 35 self.header_bytes += temp[0] 36 else: 37 h, b = temp[0], temp[1] 38 print(h, b) 39 self.header_bytes += h 40 self.body_bytes += b 41 42 @property 43 def header_str(self): 44 return str(self.header_bytes, encoding='utf-8') 45 46 def initialize_headers(self): 47 headers = self.header_str.split('\r\n') 48 first_line = headers[0].split(' ') 49 if len(first_line) == 3: 50 self.method, self.url, self.protocol = headers[0].split(' ') 51 for line in headers: 52 kv = line.split(':') 53 if len(kv) == 2: 54 k, v = kv 55 self.header_dict[k] = v 56 57 F = None 58 59 class Future(object): 60 def __init__(self,timeout=0): 61 self.result = None 62 self.timeout = timeout 63 self.start = time.time() 64 65 def main(request): 66 global F 67 F = Future() 68 F.timeout = 2 69 return F 70 71 def index(request): 72 return 'index' 73 74 # def stop(request): 75 # global F 76 # F.result = b'xx' 77 # return 'Stop' 78 79 routers = [ 80 ('/index', index), 81 ('/main', main), 82 # ('/stop',stop) 83 84 ] 85 86 def run(): 87 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 88 sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 89 sock.bind(('127.0.0.1', 8888)) 90 sock.setblocking(False) # 设置服务端非阻塞 91 sock.listen(10) 92 93 inputs = [] 94 inputs.append(sock) 95 96 async_request_dict = {} 97 98 while True: 99 rlist, wlist, elist = select.select(inputs, [], [], 0.05) 100 for r in rlist: 101 if r == sock: 102 """新请求到来""" 103 conn, addr = sock.accept() # 返回客户端的conn以及地址 104 conn.setblocking(False) # 设置客户端非阻塞 105 inputs.append(conn) # 监听客户端和服务端的socket变化 106 else: 107 """客户端发来数据""" 108 data = b"" 109 while True: 110 try: 111 chunk = r.recv(1024) 112 data = data + chunk 113 except Exception as e: 114 chunk = None 115 if not chunk: 116 break 117 118 # data进行处理,请求头和请求体 119 # 1.请求头获取url 120 # 2.去路由中匹配,获取指定的函数 121 # 3.执行函数,获取返回值 122 # 4.将返回值 r.sendall(b'dfafsfsg) 123 124 request = HttpRequest(data) 125 # print(request.url) 126 # print(request.method) 127 # print(request.header_dict) 128 # print(request.body_bytes) 129 130 ### url匹配路由 ### 131 import re 132 flag = False # 是否匹配成功 133 func = None # url匹配对应的函数 134 for route in routers: 135 if re.match(route[0], request.url): 136 flag = True 137 func = route[1] 138 break 139 if flag: 140 result = func(request) 141 #### 判断result的类型是否是Future #### 142 if isinstance(result,Future): 143 async_request_dict[r] = result #将客户端和future对象相关联 144 else: 145 r.sendall(bytes(result, encoding='utf-8')) 146 # 由于模拟http短链接,响应后断开 147 inputs.remove(r) 148 r.close() # 关闭 149 else: 150 r.sendall(b'404') 151 # 由于模拟http短链接,响应后断开 152 inputs.remove(r) 153 r.close() # 关闭 154 155 # 客户端conn和future对象组成的,判断future是否设置值,若设置则对应挂起请求返回 156 for conn in async_request_dict.keys(): 157 future = async_request_dict[conn] 158 start = future.start 159 timeout = future.timeout #超时时间 160 ctime = time.time() 161 if (start + timeout) <= ctime: #超时时间比较 162 future.result = b'timeout' 163 if future.result: 164 conn.send(future.result) 165 conn.close() 166 del async_request_dict[conn] 167 inputs.remove(conn) 168 169 if __name__ == '__main__': 170 run()
相关文档:https://www.cnblogs.com/wupeiqi/p/6536518.html