socketserver 源码阅读笔记

socketserver 是 python 标准库中的模块,作为一个网络服务器框架(A framework for network servers),其作用是简化编写网络服务器流程,并且担当标准库中其它模块跟第三方模块的底层服务器基础。

socketserver 是 python3 中的新名字,在 python2 中名为 SocketServer,本次阅读的是 0.4 版本。

该框架主要是使用 ServerClass 跟 RequestHandlerClass 两大类。

  • ServerClass 处理服务端与客户端的通讯
  • RequestHandlerClass 处理数据的解析,接收和发送;主要的业务逻辑

ServerClass

  • BaseServer 抽象基类
  • TCPServer 处理流式套接字
  • UnixStreamServer 处理本地处理流式套接字,只适用UNIX平台
  • UDPServer 处理数据报套接字
  • UnixDatagramServer 处理本地处理数据报套接字,只适用UNIX平台

RequestHandlerClass

  • BaseRequestHandler 处理基类
  • StreamRequestHandler 处理流式套接字
  • DatagramRequestHandler 处理数据报套接字

通过对通讯和数据处理的解耦,可以实现不同组合的服务器用于处理不同的套接字,但这样的服务器,仅仅是同步处理请求,如果要实现异步处理请求,还需使用 MixINClass

  • ForkingMixIn 利用多进程实现异步,接收一个请求后,fork 一个进程进行响应处理
  • ThreadingMixIn 利用多线程实现异常,接收一个请求后,使用一个线程进行响应处理

关于 MixIN,Mixin 扫盲班

Server 类的继承关系

        +------------+
        | BaseServer |
        +------------+
              |
              v
        +-----------+        +------------------+
        | TCPServer |------->| UnixStreamServer |
        +-----------+        +------------------+
              |
              v
        +-----------+        +--------------------+
        | UDPServer |------->| UnixDatagramServer |
        +-----------+        +--------------------+

UDPServer 继承 TCPServer,TCPServer 继承 BaseServer,UnixStreamServer 继承自 TCPServer,UnixDatagramServer 继承自 UDPServer。

if hasattr(socket, 'AF_UNIX'):

    class UnixStreamServer(TCPServer):
        address_family = socket.AF_UNIX

    class UnixDatagramServer(UDPServer):
        address_family = socket.AF_UNIX

从源码看,UnixStreamServer 跟 TCPServer,UnixDatagramServer 跟 UDPServer,仅仅是改变了 address_family

关于 socket.AF_UNIX,主要是用于同一台机器上的进程间通信。
AF_INET域与AF_UNIX域socket通信原理对比

if hasattr(os, "fork"):
    class ForkingUDPServer(ForkingMixIn, UDPServer): pass
    class ForkingTCPServer(ForkingMixIn, TCPServer): pass

class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass

而多进程服务器跟多线程服务器,也仅仅是把 TCPServer、UDPServer 跟 ForkingMixIn、ThreadingMixIn 组合在一起而已。

class BaseRequestHandler:
    def __init__(self, request, client_address, server):
        self.request = request
        self.client_address = client_address
        self.server = server
        self.setup()
        try:
            self.handle()
        finally:
            self.finish()

    def setup(self):
        pass

    def handle(self):
        pass

    def finish(self):
        pass

RequestHandler 类的继承关系

          +--------------------+        +----------------------+
          | BaseRequestHandler |------->| StreamRequestHandler |
          +--------------------+        +----------------------+
                    |
                    v
        +------------------------+       
        | DatagramRequestHandler |
        +------------------------+ 

StreamRequestHandler 跟 DatagramRequestHandler 是继承 BaseRequestHandler,它们都重写了 setup 跟 finish,本质上是用缓存对套接字提供服务。

调用流程

示例 来看调用流程

import socketserver

class MyTCPHandler(socketserver.BaseRequestHandler):

    def handle(self):
        # self.request is the TCP socket connected to the client
        self.data = self.request.recv(1024).strip()
        print("{} wrote:".format(self.client_address[0]))
        print(self.data)
        # just send back the same data, but upper-cased
        self.request.sendall(self.data.upper())

if __name__ == "__main__":
    HOST, PORT = "localhost", 9999

    with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
        server.serve_forever()

这里定义了一个继承自 BaseRequestHandler 的 MyTCPHandler,重写了 handle 方法,然后主机、端口一同传入 TCPServer。

class BaseServer:
    def __init__(self, server_address, RequestHandlerClass):
        """Constructor.  May be extended, do not override."""
        self.server_address = server_address
        self.RequestHandlerClass = RequestHandlerClass
        self.__is_shut_down = threading.Event()
        self.__shutdown_request = False

class TCPServer(BaseServer):
    def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True):
        """Constructor.  May be extended, do not override."""
        BaseServer.__init__(self, server_address, RequestHandlerClass)
        self.socket = socket.socket(self.address_family,
                                    self.socket_type)
        if bind_and_activate:
            try:
                self.server_bind()
                self.server_activate()
            except:
                self.server_close()
                raise

1.TCPServer 实例化,调用了自身 server_bind() 方法,绑定了传入的 server_address 参数,而 server_activate() 方法是重写自 BaseServer类,主要是请求队列大小的设置。

2.启动服务 ,调用 BaseServer 类的 serve_forever() 方法,用于不断监听请求,当有请求时调用 _handle_request_noblock() 方法完成请求处理,然后调用 service_actions() 处理后的操作;这个方法在 ForkingMixIn 类会被重写,用于解决完成请求处理的进程。

    def _handle_request_noblock(self):
        try:
            request, client_address = self.get_request()
        except OSError:
            return
        if self.verify_request(request, client_address):
            try:
                self.process_request(request, client_address)
            except Exception:
                self.handle_error(request, client_address)
                self.shutdown_request(request)
            except:
                self.shutdown_request(request)
                raise
        else:
            self.shutdown_request(request)

_handle_request_noblock() 方法处理请求时,会调用下列方法

  • get_request() 方法在 TCPServer 类中定义,调用socket.accept() 方法,返回 request 参数和 client_address 参数
  • verify_request(request, client_address) 方法在 BaseServer 类中定义,可以在子类中重写做一些验证处理
  • process_request(request, client_address) 方法请求处理函数,用于调用 finish_request() 方法,在 ForkingMixIn 类和 ThreadingMixIn 类中会被重写,用进程或线程发起处理
  • finish_request(request, client_address) 方法具体的请求处理过程,这里是自定义 MyTCPHandler 类实例化,该类继承了 BaseRequestHandler 类,遵传它的定义,当自己被实例化时,会调用 handle() 方法
  • shutdown_request(request) 方法在 TCPServer 类中定义,会先 request.shutdown(socket.SHUT_WR) ,出错再调用 close_request(request) 方法做 request.close(),而在 UDPServer 类中,该函数会重写 pass

3.关闭服务,要结束服务,可以手动调用 BaseServer 类中的 shutdown() 方法,示例说直接用 Ctrl-C

    def shutdown(self):
        self.__shutdown_request = True
        self.__is_shut_down.wait()

主要是 __shutdown_request 标识更换,并阻塞等待 serve_forever() 线程返回

这便是示例中 TCPServer 的生命周期。

些许问题

request.shutdowm(socket.SHUT_WR)request.close() 区别

1.调用shutdown会马上关闭指定链接, 而close会等到描述符的引用计数器为0时才会开始关闭链接
2.close会同时关闭两个链接, 而shutdown值关闭指定链接
3.close后文件描述符不再可用(引用基数为0,释放资源), shutdown后文件描述符是可用的.

具体

poll、epoll 选择

if hasattr(selectors, 'PollSelector'):
    _ServerSelector = selectors.PollSelector
else:
    _ServerSelector = selectors.SelectSelector

##  Lib/selectors.py

# Choose the best implementation, roughly:
#    epoll|kqueue|devpoll > poll > select.
# select() also can't accept a FD > FD_SETSIZE (usually around 1024)
if 'KqueueSelector' in globals():
    DefaultSelector = KqueueSelector
elif 'EpollSelector' in globals():
    DefaultSelector = EpollSelector
elif 'DevpollSelector' in globals():
    DefaultSelector = DevpollSelector
elif 'PollSelector' in globals():
    DefaultSelector = PollSelector
else:
    DefaultSelector = SelectSelector

socketserver 模块中手动选择了 poll 库,而不是使用模块提供的 DefaultSelector 自适应选择。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容