socketserver源码解析和协程版socketserver
来,贴上一段代码让你仰慕一下欧socketserver的魅力,看欧怎么完美实现多并发的魅力
client
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | import socket ip_port = ( '127.0.0.1' , 8009 ) sk = socket.socket() sk.connect(ip_port) sk.settimeout( 5 ) while True : data = sk.recv( 1024 ) print ( 'receive:' ,data.decode()) inp = input ( 'please input:' ) sk.sendall(inp.encode()) if inp = = 'exit' : break sk.close() |
server
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | ''' 对于socketserver,你需要做的事: 定义个一类,继续socketserver.BaseRequestHandler 重写handle方法 把写好的类和端口进行实例,启动程序 ''' import socketserver class MyServer(socketserver.BaseRequestHandler): def handle( self ): # print self.request,self.client_address,self.server conn = self .request conn.sendall( '欢迎致电 10086,请输入1xxx,0转人工服务.' .encode()) Flag = True while Flag: data = conn.recv( 1024 ).decode() if data = = 'exit' : Flag = False elif data = = '0' : conn.sendall( '通过可能会被录音.balabala一大推' .encode()) else : conn.sendall( '请重新输入.' .encode()) if __name__ = = '__main__' : server = socketserver.ThreadingTCPServer(( '127.0.0.1' , 8009 ),MyServer) server.serve_forever() |
代码执行看源码
我们看到最后两句代码,第一句--带有括号,我们第一想到的是要么是个函数,要是是个类,那么看一下什么吧
是个类,继承了ThreadingMixIn和TCPServer两个类,好!实例对象肯定要找构造方法,当前这个类没有,就需要到父类中找了,从左往右
我们发现ThreadingMixIn类里没有,那肯定在TCPServer类里啦,果真在,并且还执行了TCPServer的父类BaseServer里的构造方法
在BaseServer里只是做了一些数据初始化的事,那我们回到TCPServer里构造方法里接着往下看吧
执行父类构造方法后,实例一个socket对象,然后就是在if下执行self.server_bind()方法,要想知道这个方法,必须弄清楚self是指代谁?
说到这里,我必须屡屡类的继承关系了...
self是指实例对象,谁实例的,是ThreadingTCPServer类,所以找server_bind方法,要从ThreadingTCPServer往上找
我们发现这个方法还是TCPServer里,
似乎是做了绑定socket的事,这里过,在找看构造方法里,后又执行self.server_activate方法,我们按照那继承关系又在TCPServer找到了
似乎是做了有关监听数的事,好这里构造方法执行完毕,第一代码也就这样看完了
看到第二句,开始找serve_forever方法在哪了
在BaseServer里,这方法大概说的是,一次处理一个请求直到连接关闭,如果处理其他要求另外开启一个线程
我发现下有个self._handle_request_noblock(),我们看看这个是做啥的吧?
还是在BaseServer,好像是做不阻塞的工作
在这里还是执行了一个process_request方法,这个方法我们在ThreadingMixIn找到了
主要是实例线程和启动线程的...
没错,似乎我有了一点感悟--
socketserver是基于多线程来做的,并做到多并发处理
内部调用流程为:
- 启动服务端程序
- 执行 TCPServer.__init__ 方法,创建服务端Socket对象并绑定 IP 和 端口
- 执行 BaseServer.__init__ 方法,将自定义的继承自SocketServer.BaseRequestHandler 的类 MyRequestHandle赋值给 self.RequestHandlerClass
- 执行 BaseServer.server_forever 方法,While 循环一直监听是否有客户端请求到达 ...
- 当客户端连接到达服务器
- 执行 ThreadingMixIn.process_request 方法,创建一个 “线程” 用来处理请求
- 执行 ThreadingMixIn.process_request_thread 方法
- 执行 BaseServer.finish_request 方法,执行 self.RequestHandlerClass() 即:执行 自定义 MyRequestHandler 的构造方法(自动调用基类BaseRequestHandler的构造方法,在该构造方法中又会调用 MyRequestHandler的handle方法)
服务类:
SocketServer提供了5个基本的服务类:
BaseServer
: 基础类,由于下面四个网络服务类的继承
TCPServer
:针对TCP套接字流
UDPServer
:针对UDP数据报套接字
UnixStreamServer
:处理流式套接字,与TCPServer配合
UnixDatagramServer
:处理数据报套接字,与UDPServer配合
异步处理类:
这个四个服务类都是同步处理请求的。一个请求没处理完不能处理下一个请求。要想支持异步模型,可以利用多继承让server类继承ForkingMixIn 或 ThreadingMixIn。
ForkingMixIn
: 利用多进程(分叉)实现异步。(Mix-in class to handle each request in a new process)
ThreadingMixIn
: 利用多线程实现异步。(Mix-in class to handle each request in a new thread)
请求处理类:
要实现一项服务,还必须派生一个handler请求处理类,并重写父类的handle()方法。handle方法就是用来专门是处理请求的。该模块是通过服务类和请求处理类组合来处理请求的。
SocketServer模块提供的请求处理类有BaseRequestHandler
,以及它的派生类StreamRequestHandler
和DatagramRequestHandler
。从名字看出可以一个处理流式套接字,一个处理数据报套接字。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #服务器端 from SocketServer import TCPServer,StreamRequestHandler,\ ThreadingMixIn, ForkingMixIn #定义请求处理类 class Handler(StreamRequestHandler): def handle( self ): addr = self .request.getpeername() print 'connection:' , addr while 1 : self .request.sendall( self .request.recv( 1024 )) #实例化服务类对象 server = TCPServer( server_address = ( '127.0.0.1' , 8123 ), # address RequestHandlerClass = Handler # 请求类 ) #开启服务 server.serve_forever() |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #客户端 import socket def socketClient(): so = socket.socket() so.connect(( '127.0.0.1' , 8123 )) # so.close() while 1 : so.sendall( raw_input ( 'msg' )) print so.recv( 1024 ) if __name__ = = '__main__' : socketClient() |
多线程服务端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | from SocketServer import TCPServer,StreamRequestHandler,\ ThreadingMixIn, ForkingMixIn #定义基于多线程的服务类 class Server(ThreadingMixIn, TCPServer): pass #定义请求处理类 class Handler(StreamRequestHandler): def handle( self ): addr = self .request.getpeername() print 'connection:' , addr while 1 : self .request.sendall( self .request.recv( 1024 )) #实例化服务类对象 server = Server( server_address = ( '127.0.0.1' , 8123 ), # address RequestHandlerClass = Handler # 请求类 ) #开启服务 server.serve_forever() |
源码分析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 | """普通的Socket服务类. socket服务: - address family: - AF_INET{,6}: IP (Internet Protocol) sockets (default) - AF_UNIX: Unix domain sockets - others, e.g. AF_DECNET are conceivable (see <socket.h> - socket type: - SOCK_STREAM (reliable stream, e.g. TCP) - SOCK_DGRAM (datagrams, e.g. UDP) 请求服务类 (including socket-based): - 客户端地址验证之前进一步查看请求 (实际上是一个请求处理的钩子在请求之前,例如logging) - 如何处理多个请求: - synchronous (同步:同一时间只能有一个请求) - forking (分叉:每个请求分配一个新的进程) - threading (线程:每个请求分配一个新的线程) 五个类的继承关系如下: +------------+ | BaseServer | +------------+ | v +-----------+ +------------------+ | TCPServer |------->| UnixStreamServer | +-----------+ +------------------+ | v +-----------+ +--------------------+ | UDPServer |------->| UnixDatagramServer | +-----------+ +--------------------+ 通过ForkingMixIn创建进程,通过ThreadingMixIn创建线程,如下实例: class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass """ __version__ = "0.4" import socket import select import sys import os import errno try : import threading except ImportError: import dummy_threading as threading __all__ = [ "TCPServer" , "UDPServer" , "ForkingUDPServer" , "ForkingTCPServer" , "ThreadingUDPServer" , "ThreadingTCPServer" , "BaseRequestHandler" , "StreamRequestHandler" , "DatagramRequestHandler" , "ThreadingMixIn" , "ForkingMixIn" ] #family参数代表地址家族,比较常用的为AF_INET或AF_UNIX。 #AF_UNIX用于同一台机器上的进程间通信,AF_INET对于IPV4协议的TCP和UDP 。 if hasattr (socket, "AF_UNIX" ): __all__.extend([ "UnixStreamServer" , "UnixDatagramServer" , "ThreadingUnixStreamServer" , "ThreadingUnixDatagramServer" ]) def _eintr_retry(func, * args): """重新启动系统调用EINTR中断""" while True : try : return func( * args) except (OSError, select.error) as e: if e.args[ 0 ] ! = errno.EINTR: raise class BaseServer: """服务类的基类. 调用方法: - __init__(server_address, RequestHandlerClass) - serve_forever(poll_interval=0.5) - shutdown() - handle_request() # if you do not use serve_forever() - fileno() -> int # for select() 可以被重写的方法: - server_bind() - server_activate() - get_request() -> request, client_address - handle_timeout() - verify_request(request, client_address) - server_close() - process_request(request, client_address) - shutdown_request(request) - close_request(request) - handle_error() 派生类(derived classes)方法: - finish_request(request, client_address) 可以由派生类或重写类变量实例: - timeout - address_family - socket_type - allow_reuse_address 实例变量: - RequestHandlerClass - socket """ timeout = None def __init__( self , server_address, RequestHandlerClass): """初始化,能被扩展但不要重写.""" self .server_address = server_address # 地址元祖如('127.0.0.1', 8123) self .RequestHandlerClass = RequestHandlerClass # 请求处理类 self .__is_shut_down = threading.Event() # 多线程通信机制 self .__shutdown_request = False def server_activate( self ): """通过构造函数激活服务器.可被重写.""" pass def serve_forever( self , poll_interval = 0.5 ): """在一个时间段内处理一个请求直到关闭. 处理请求,直到一个明确的shutdown()请求。每poll_interval秒轮询一次shutdown。 忽略self.timeout。如果你需要做周期性的任务,建议放置在其他线程。 """ self .__is_shut_down.clear() #Event是Python多线程通信的最简单的机制之一.一个线程标识一个事件,其他线程一直处于等待状态。 #一个事件对象管理一个内部标示符,这个标示符可以通过set()方法设为True,通过clear()方法重新设为False,wait()方法则使线程一直处于阻塞状态,直到标示符变为True #也就是说我们可以通过 以上三种方法来多个控制线程的行为。 try : while not self .__shutdown_request: #考虑使用其他文件描述符或者连接socket去唤醒它取代轮询 #轮询减少在其他时间我们响应了关闭请求CPU。 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 () def shutdown( self ): """终止serve_forever的循环. 阻塞直到循环结束. 当serve_forever()方法正运行在另外的线程中必须调用它,否则会发生死锁. """ self .__shutdown_request = True self .__is_shut_down.wait() # - handle_request() 是顶层调用. 它调用select,get_request(),verify_request()和process_request() # - get_request() 不同于流式和报文socket # - process_request() 产生进程的位置,或者产生线程去结束请求 # - finish_request() 请求处理类的实例,此构造都将处理请求本身 def handle_request( self ): """处理一个请求, 可能阻塞.考虑self.timeout.""" # Support people who used socket.settimeout() to escape # handle_request before self.timeout was available. timeout = self .socket.gettimeout() # 返回当前超时期的值,如果没有设置超时期,则返回None 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() def _handle_request_noblock( self ): """处理一个请求, 非阻塞.""" try : request, client_address = self .get_request() except socket.error: return if self .verify_request(request, client_address): try : self .process_request(request, client_address) except : self .handle_error(request, client_address) self .shutdown_request(request) def handle_timeout( self ): """超时处理。默认对于forking服务器是收集退出的子进程状态,threading服务器则什么都不做""" pass def verify_request( self , request, client_address): """ 返回一个布尔值,如果该值为True ,则该请求将被处理,反之请求将被拒绝。 此功能可以重写来实现对服务器的访问控制。 默认的实现始终返回True。client_address可以限定客户端,比如只处理指定ip区间的请求。 常用。 """ return True def process_request( self , request, client_address): """调用finish_request.被 ForkingMixIn and ThreadingMixIn重写 如果用户提供handle()方法抛出异常,将调用服务器的handle_error()方法。 如果self.timeout内没有请求收到, 将调用handle_timeout()并返回handle_request()。 """ self .finish_request(request, client_address) self .shutdown_request(request) def server_close( self ): """关闭并清理server.""" pass def finish_request( self , request, client_address): """通过请求类的实例结束请求,实际处理RequestHandlerClass发起的请求并调用其handle()方法。 常用.""" self .RequestHandlerClass(request, client_address, self ) def shutdown_request( self , request): """关闭结束一个单独的请求.""" self .close_request(request) def close_request( self , request): pass def handle_error( self , request, client_address): """优雅的操作错误,可重写,默认打印异常并继续 """ print '-' * 40 print 'Exception happened during processing of request from' , print client_address import traceback traceback.print_exc() # XXX But this goes to stderr! print '-' * 40 |
基于协程实现socket多并发
在这里,首先我们先了解一下协程一个概念:
-
协程又称微线程,单线程实现异步并发
-
线程寄存在cpu里,而协程有自己的寄存器,上下文和栈
-
由于协程本质上是单线程,所以不存在上下文切换花销,以及锁和同步的概念
-
低成本,高并发,唯一不足的就是不能利用cpu的核资源
-
你就记住协程干了这么一件事:遇IO阻塞就去做别的事了(socket连接就是IO操作)
从上面的源码解析,我们知道,socketserver实现多并发本质就是多线程或多进程,但这样还是有些低效,你想啊,如果有几万客户连接过来,就要创建几万个线程,如果用的人其实不是很多,CPU还要不断的去检测socket客户端有没有传输数据,花销很大,效率就低
server
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | import sys import socket import time import gevent from gevent import socket,monkey monkey.patch_all() def server(port): s = socket.socket() s.bind(( '0.0.0.0' , port)) s.listen( 500 ) while True : cli, addr = s.accept() gevent.spawn(handle_request, cli) def handle_request(conn): try : while True : data = conn.recv( 1024 ) print ( "recv:" , data) conn.send(data) if not data: conn.shutdown(socket.SHUT_WR) except Exception as ex: print (ex) finally : conn.close() if __name__ = = '__main__' : server( 8001 ) |
client
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import socket HOST = 'localhost' # The remote host PORT = 8001 # The same port as used by the server s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((HOST, PORT)) while True : msg = bytes( input ( ">>:" ),encoding = "utf8" ) s.sendall(msg) data = s.recv( 1024 ) #print(data) print ( 'Received' , repr (data)) s.close() |
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步