源码阅读笔记:Python标准库SocketServer
简介
SocketServer
是对底层的Socket
进行封装,简化网络服务器编写工作的Python标准库。
源码地址:Lib/SocketServer.py
文档地址:20.17. SocketServer — A framework for network servers
基本socket编程
我们假设读者对socket编程已经有了基本的了解,否则请阅读官方文档
以TCP为例,基本的socket编程,server端的流程如下:
- 创建一个socket
- bind绑定地址和端口
- listen端口监听
- accept接受连接
- 然后通过recv和send来接收、发送数据
- close关闭连接
client端流程如下:
- 创建socket
- connect与服务端建立连接
- 连接建立后即可通过recv和send交互数据
- close关闭连接
server端:
1 | # Echo server program |
client端:
1 | # Echo client program |
从上面的例子看出,直接使用socket编程的话,需要处理比较多的细节,并且扩展性不够好。
基于SocketServer编程
同样以TCP为例,server端代码:
1 | import SocketServer |
从上面看出,利用SocketServer,有关于socket连接的细节都由其处理,我们只需要构造一个继承自StreamRequestHandler
的类,override掉handle
方法,我们更加专心于业务逻辑处理。
SocketServer源码结构
SocketServer.py
文件中包含Server
和RequestHandler
两大类的class。
RequestHandler
RequestHandler
比较简单,StreamRequestHandler
(TCP)和DatagramRequestHandler
(UDP)都继承自BaseRequestHandler
。
RequestHandler
只有四个方法:
__init__
,初始化setup
,构造一些协议相关的东西,比如用于读写的filehandle
,用于给用户override的方法,用于编写业务逻辑的地方finish
,一些扫尾工作,如用于读写文件的关闭
源码分析
RequestHandler部分的源码比较简单,我们就用处理TCP连接的StreamRequestHandler
为例来讲解,UDP的Handler和TCP大同小异,并且更加简单一些。
从671行开始看起,先定义了的rbufsize
和wbufsize
分别表示读缓冲区大小和写缓冲区大小。其中disable_nagle_algorithm
是用来控制是否启用nagle
算法。nagle
算法是为了解决网络中小数据传输的带宽浪费问题的。在TCP/IP协议中,哪怕只传输1个字节的数据,但只要是一个数据段,就要加上几十个字节的协议头,所以在传输小数据的时候,造成了大量的浪费。nagle
算法的基本思想就是将数据进行缓存,当达到一定的大小时,再统一发送,这样可以大大提高网络利用率。但是提高网络利用率的同时,网络的实时性就降低了。对于某些交互性很强的应用程序来说是不允许的。默认情况下是启用了这个算法的。
在693行,setup
方法中,赋的第一个变量self.connection
表示用来交互的socket
对象。它的值是从self.request
给出来的。我们来追踪一下这个self.request
。首先追踪到644行,在BaseRequestHandler
的__init__
方法中,self.request
是从类构造的参数request
传进来的。那么我们就找这个类使用的地方。
从上面的示例代码可以看出,我们的RequestHandler
是作为参数传进Server
里面的,所以我们追到209行,看出来赋值给了Server
的self.RequestHanlerClass
,再看哪儿使用了这个变量,追到334行,在finish_request
里传了那个request
进来,并构造了一个RequestHandler
的实例。那么这个request
又是从321行的process_request
传进来的,再追下去,传进来的request
是在295行,这个值又是290行的get_request
的返回值。get_request
这个方法基类BaseServer
没有定义,是由子类来实现的,我们到463行可以看出,get_request
的返回值就是socket.accept()
的返回值,即建立连接的socket
对象。
由此我们可以看出,在SocketServer
中的request
其本质就是socket
对象。
RequestHandler
接下来就没有什么好讲的了,都很简单。
Server
从下图可以看出有关于Server
部分的继承关系
1 | +------------+ |
我们以TCPServer
为例讲解。从221行的对外的入口serve_forever()
开始。从230行可以看出,这是个由self.__is_shutdown_request
变量控制的循环。与self.__is_shutdown_request
相关的还有一个threading.Event
对象self.__is_shut_down
。
threading.Event
对象是一种我理解上的信号,只有True
和False
两个值。有三个基本方法,wait
、set
、clear
。wait
就是阻塞等待直到其值为True
,set
方法就是将其赋值为True
,而clear
就是reset其值成False
。在243行shutdown
这另一个对外的方法,首先将self.__is_shutdown_request
赋值为True
之后,便wait
等待结束。
self.__is_shutdown_request
控制的就是230行的Server主循环。主循环里面就做了两件事情,一个是使用select
来等待连接的到来,二个是到连接到来的时候,调用self._handle_request_noblock
来处理这个连接。
先说这个select
是对操作系统下的select
系统调用做的封装。select
系统调用是是通过维护一个文件描述符数组,来监视等待IO的文件描述符。是一种跨平台性较好、比较简单的IO复用技术。但缺点很明显,由于能开的文件描述符数量在系统上是有限制的,并且随着数组的增大,其扫描一遍的时间增长,效率会大幅度的降低。代码235行调用的select
外面包了一层方法,这个方法不需要太多了解,只是为了处理一个系统中断的。select
的最后一个参数是poll_interval
,默认值为0.5,也就是0.5秒,表示阻塞等待的时间为0.5秒。
然后就是handle这个request了。处理流程就是get_request
->verify_request
->process_request
,如果出现异常,则调用handle_error
和shutdown_request
。get_request
我们上面也讲了,就是一个socket.accept()
。verify_request
在这里直接返回True
,并且下面的两个子类也并没有修改这个方法,也就是为了抽象考虑,并留给用户来使用的。这个process_request
中重点在finish_requst
上,上面已经说了,这个里面就是调用RequestHandler
的handle
来进行逻辑处理。
MixIn
源码中还有两个类,ForkingMixIn
和ThreadingMixIn
,两个混合类。对于混合类的解释,我显然是比不上赖勇浩大叔的,所以还是贴上他的文章吧——Mixin 扫盲班。
简单的来说,混合类就是用来给原本没有某种能力的类插入某个能力的东西。这两个类就是给我们本来只能处理单request的server类提供多request处理能力的类。一个是通过多进程,一个通过多线程来实现的。
我们以ThreadingMixIn
为例,其实本质上就是通过多继承的方式,把MixIn
放在前面,根据Python多继承的一个方法的搜索顺序,由下至上,从左到右,把MixIn
放在前面,重写process_request
方法,然后通过开线程的方式来实现同时处理多request的。
其他
其他的诸如ThreadingTCPServer
,UnixStreamServer
只不过是对上述的几个类的组合,继承,很简单,就不做过多的介绍了。