Python标准库源码分析:SocketServer.py**********************8

时间:2022-03-26 06:08:22

https://www.zybuluo.com/wzhang1117/note/8202

 

SocketServer简化了网络服务器的编写。它有4个类:TCPServer,UDPServer,UnixStreamServer,UnixDatagramServer。这4个类是同步进行处理的,另外通过ForkingMixIn和ThreadingMixIn类来支持异步。

    创建服务器的步骤。首先,你必须创建一个请求处理类,它是BaseRequestHandler的子类并重载其handle()方法。其次,你必须实例化一个服务器类,传入服务器的地址和请求处理程序类。最后,调用handle_request()(一般是调用其他事件循环或者使用select())或serve_forever()。

 

     HTTPServer.__init__(self, *args, **kwargs)
  File "/usr/local/lib/python2.6/SocketServer.py", line 402, in __init__
    self.server_bind()
  File "/usr/local/lib/python2.6/BaseHTTPServer.py", line 108, in server_bind
    SocketServer.TCPServer.server_bind(self)
  File "/usr/local/lib/python2.6/SocketServer.py", line 413, in server_bind
    self.socket.bind(self.server_address)
  File "<string>", line 1, in bind
error: [Errno 98] Address already in use

 

先贴一段示例代码(主要针对ThreadingTCPServer,其它类似):

 
 
  1. from SocketServer import ThreadingTCPServer,StreamRequestHandler
  2. from time import ctime
  3. HOST = ''
  4. PORT = 12345
  5. ADDR = (HOST, PORT)
  6. class MyRequestHandler(StreamRequestHandler):
  7. def handle(self):
  8. print 'connected from:', self.client_address
  9. self.wfile.write('[%s] %s' % (ctime(),self.rfile.readline()))
  10. tcpServer = ThreadingTCPServer(ADDR, MyRequestHandler)#1
  11. print 'waiting for connection'
  12. tcpServer.serve_forever()#2

1.先分析这一行:tcpServer = ThreadingTCPServer(ADDR, MyRequestHandler)

在源代码在中查找ThreadingTCPServer的定义

class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass

这里使用了Mix-in技术,字面理解就是给TCP类增加了多线程功能,主体还是TCPServer,继续在源代码在中查找TCPServer的定义

class TCPServer(BaseServer):

暂时先不管BaseServer,分析下构造函数

 
 
  1. BaseServer.__init__(self, server_address, RequestHandlerClass)
  2. self.socket = socket.socket(self.address_family, self.socket_type)
  3. if bind_and_activate:
  4. self.server_bind()
  5. self.server_activate()

server_bind()和server_activate()分别调用bind()和listen()函数,熟悉socket编程的应该很清楚

至此,初始化工作就完成了

2.分析:tcpServer.serve_forever()

通过查找TCPServer的定义知道,它本身没有这个成员,这个函数是BaseServer的成员,它的构造函数主要是保存服务器地址ADDR和MyRequestHandler类,下面看函数定义:

def serve_forever(self, poll_interval=0.5):

从字面很容易看出这是一个主循环,还有个时间周期默认为0.5s,主循环主要代码:

 
 
  1. while not self.__shutdown_request:
  2. r, w, e = _eintr_retry(select.select, [self], [], [], poll_interval)
  3. if self in r:
  4. self._handle_request_noblock()

通过不断调用select函数,看看自己是否处于可写状态,如果是就调用_handle_request_noblock(),这里有一个问题select函数操作集合的时候有个要求,要么集合本身是描述符,要么他提供一个fileno()接口,返回一个描述符,查找TCPServer的成员函数确实存在这样一个函数,它返回socket的描述符

下面分析_handle_request_noblock(),一般以下划线开头的函数属于内部函数,有点像C++类里面的private函数,一般不用于外部调用,主要内容就两行:

 
 
  1. request, client_address = self.get_request()
  2. self.process_request(request, client_address)

get_request()在TCPServer中实现,就是调用accept()函数,返回客户端socket和客户端地址

process_request()也是两行

 
 
  1. self.finish_request(request, client_address)###线程阻塞
  2. self.shutdown_request(request)

主要就是完成请求,关闭请求。注意函数里面的注释Overridden by ForkingMixIn and ThreadingMixIn.一会儿再分析。finish_request()函数的工作就是将最开始的MyRequestHandler类初始化

3.下面开始分析BaseRequestHandler

因为MyRequestHandler和StreamRequestHandler都没有构造函数,因此finish_request()执行的是BaseRequestHandler的构造函数

 
 
  1. self.request = request
  2. self.client_address = client_address
  3. self.server = server
  4. self.setup()
  5. try:
  6. self.handle()
  7. finally:
  8. self.finish()

体setup()函数和finish()函数在StreamRequestHandler中实现,而handle函数由MyRequestHandler实现

setup()和finish()主要是设置和清理读写描述符,以便在handle()中可以进行读写操作

4.之前还提到过一个ThreadingMixIn(ForkingMixIn这个在windows平台不支持)

这个类主要是覆盖了BaseServer的process_request()方法,从而实现多线程,因为在原版的process_request()中,函数会阻塞在finish_request()函数中,从而无法处理多个请求,而多线程版的方法中,finish_request()和shutdown_request()在一个新的线程中完成,从而不会阻塞process_request(),主循环得以继续

总结:到这里整个程序运行过程就分析完了,当然还有一些细节,如模块初始化,主线程清理工作等没有仔细分析,但是本文的主要目的是分析优秀代码的设计思路和实现方法,因此忽略这些细节。SocketServer模块的主要设计思路是将我们平时的socket编程两个主要部分进行了分解,一个是主循环监听过程,一个是具体客户端请求处理过程,两个过程分别对应到Server和Request类,Server和Request又进一步抽象成为BaseServer和BaseRequestHandler,在这两个抽象类中完成了对应处理过程,而这些过程的具体实现则分别在不同的具体类中实现,如socket初始化,绑定连接,接受请求,关闭连接等;使用MixIn技术为BaseServer提供了多线程和多进程特性。

ps:我阅读源代码的主要目的是学习优秀代码的设计方法(好的设计方法总是在不断的做解耦和抽象工作),当然也可以学习到很多实现上的技巧。但是直接阅读源代码往往效率很低,很容易被细节所拖累,因此将程序先运行起来,观察到程序的主干路径是一个比较好的方法,根据主干路径逐步提高抽象层次从而逆向分析出原始顶层设计。