sokect编程进阶

IO模型

什么是IO?

IO:input和output的缩写,即输入/输出端口。每个设备都会有一个专用的I/O地址,用来处理自己的输入输出信息

同步、异步、阻塞、非阻塞

  • 同步和异步的概念描述的是用户线程与内核的交互方式:同步是指用户线程发起IO请求后需要等待或者轮询内核IO操作完成后才能继续执行;而异步是指用户线程发起IO请求后仍继续执行,当内核IO操作完成后会通知用户线程,或者调用用户线程注册的回调函数。
  • 阻塞和非阻塞的概念描述的是用户线程调用内核IO操作的方式:阻塞是指IO操作需要彻底完成后才返回到用户空间;而非阻塞是指IO操作被调用后立即返回给用户一个状态值,无需等到IO操作彻底完成

IO发生时涉及的对象和步骤

对于一个network IO (这里我们以read举例),它会涉及到两个系统对象,一个是调用这个IO的process (or thread),另一个就是系统内核(kernel)。当一个read操作发生时,它会经历两个阶段:
1 等待数据准备 (Waiting for the data to be ready)
2 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)
记住这两点很重要,因为这些IO Model的区别就是在两个阶段上各有不同的情况

常见的四种模型

  • 同步阻塞IO(Blocking IO):即传统的IO模型

    ?
    当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。对于network io来说,很多时候数据在一开始还没有到达(比如没有收到一个完整的TCP/UDP包),这个时候kernel就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除 block的状态,重新运行起来。

所以阻塞:blocking IO的特点是I/O执行时的两个操作(等待数据准备 (Waiting for the data to be ready)、将数据从内核拷贝到进程中(Copying the data from the kernel to the process))都是阻塞的。
python socket中:accept() recv() 是阻塞的
所以,所谓阻塞型接口是指系统调用(一般是IO接口)如果不返回结果就一直阻塞,就是socket经常说的,有发就有收,收发必相等.

  • 同步非阻塞IO(Non-blocking IO):默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK。注意这里所说的NIO并非Java的NIO(New IO)库。
    linux下,可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时,流程是这个样子:
    ?

从图中可以看出,当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。
所以,用户进程其实是需要不断的主动询问kernel数据好了没有
* IO多路复用(IO Multiplexing):即经典的Reactor设计模式,有时也称为异步阻塞IO,Java中的Selector和Linux中的epoll都是这种模型。
?

用户首先将需要进行IO操作的socket添加到select中,然后阻塞等待select系统调用返回。当数据到达时,socket被激活,select函数返回。用户线程正式发起read请求,读取数据并继续执行。
从流程上来看,使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket,以及调用select函数的额外操作,效率更差。但是,使用select以后最大的优势是用户可以在一个线程内同时处理多个socket的IO请求。用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的
* 异步IO(Asynchronous IO):即经典的Proactor设计模式,也称为异步非阻塞IO
?
用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。

IO多路复用

概述

IO多路复用是通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
IO多路复用适用如下场合:

  (1)当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。

  (2)当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。

  (3)如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。

  (4)如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。

  (5)如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。
  与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。

IO多路复用的方法

  • select
  • poll
  • epoll

    python实现IO多路复用

  • 概述
    sokect基础中,默认服务端只能同时处理一个客户端的请求,当一个客户端连接到服务器之后,其他客户端只能处于等待状态,而IO多路复用可以实现服务端同时处理多个客户端请求,
    Python中有一个select模块,其中提供了:select、poll、epoll三个方法,分别调用系统的 select,poll,epoll 从而实现IO多路复用
    根据系统不同:支持的方法也不同

    Windows Python:
        提供: select
    Mac Python:
        提供: select
    Linux Python:
        提供: select、poll、epoll

    注意:网络操作、文件操作、终端操作等均属于IO操作,对于windows只支持Socket操作,其他系统支持其他IO操作,但是无法检测 普通文件操作 自动上次读取是否已经变化。
    普通文件操作所有系统都是完成不了的,普通文件是属于I/O操作!但是对于python来说文件变更python是监控不了的,所以我们能用的只有是“终端的输入输出,Socket的输入输出”

  • 实现原理
    先看一段同步阻塞型代码:
 import socket

    ip_port = (‘127.0.0.1‘,8080)
    s = socket.socket()
    s.bind(ip_port)
    s.listen(5)
    conn,addr = s.accept()
    res = conn.recv(1024)
    conn.send(res)
    conn.close()

下面需要理清楚几个概念:

  • IO多路复用主要作用是监听socket对象内部是否变化
  • socket对象什么时候变化? 连接或和客户端交互收发消息的时候socket对象会发生变化
  • socket对象变化的实质 服务端创建的socket对象sk发生变化--->表示有新连接过来 服务端和客户端建立的conn连接对象变化了--->表示客户端发送消息过来了。要收发数据
  • 所以,IO多路复用是不断监听服务端sk对象和conn对象是否发生变化,变化之后,调用对应的线程或进程进行相应的操作。这里用到了select模块中select()方法。该方法支持四个参数: select(rlist, wlist, xlist, timeout=None)
    • 第一个参数是select监听的对象列表,在此列表中的对象一旦发生变化,select会返回变化的对象。如rlist为[sk1,sk2,sk3],如果sk1和sk3发生变化。则返回[sk1对象,sk3对象]
    • 第二个参数是select永远认为变化的对象列表,即一旦对象出现在此列表中,每次循环检查select会认为此对象一直发生变化,所以每次都会返回该对象,例如wlist为[sk1],那么,每次循环select都会返回[sk1对象]
    • 第三个参数是select检测对象是否发生错误的列表,在该列表中的对象如果发生错误,则返回该对象
    • 第四个参数是超时时间

下面通过python代码逐步实现IO多路复用

  • 1.实现动态检测对象是否发生变化

    import socket
    import select
    
    sk = socket.socket()
    ip_port = ("127.0.0.1",8080)
    sk.bind(ip_port)
    sk.listen(5)
    
    inputs = [sk,]     #定义监听的对象列表。默认只监听服务端socket对象
    
    while True:    #循环检测对象
        #监听sk(服务端)对象,如果sk发生变化,表示有新连接来了,此时rlist的值为sk
        rlist,wlist,e = select.select(inputs,[],[],1)
        print("inputs:",len(inputs),"rlist:",len(rlist),inputs)  #打印inputs 便于查看效果
        for i in rlist:       #循环变化的列表,然后对每个对象进行操作
            if i == sk:     #如果是服务端socket对象发生变化,表示新连接建立了
                conn,ip = sk.accept()     #建立连接
                inputs.append(conn)      #将新建立的连接加入监听的列表中
            else:         #否则是客户端给服务端发消息了。所以需要收消息
                recv_data = i.recv(1024)
                i.send(recv_data)

运行服务端程序,在没有新连接之前,输出是这样

inputs: 1 rlist: 0 [<socket.socket fd=280, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080)>]
一个客户端和服务器建立连接:
inputs: 1 rlist: 0 [<socket.socket fd=280, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080)>]
inputs: 1 rlist: 1 [<socket.socket fd=280, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080)>]
inputs: 2 rlist: 0 [<socket.socket fd=280, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080)>, <socket.socket fd=320, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080), raddr=(‘127.0.0.1‘, 52810)>]

发现,当一个客户端和服务端建立连接之后,select检测到sk发生变化,所以rlist等于1,然后将新建立的连接对象加入到监控的inputs列表中,所以第二行inputs长度变为2,而此次循环没有检测到变化的sk,所以rlist为0,最后打印inputs列表得知,列表里存储的是对象

第二个客户端和服务器建立连接:

inputs: 2 rlist: 0 [<socket.socket fd=280, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080)>, <socket.socket fd=320, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080), raddr=(‘127.0.0.1‘, 52810)>]
inputs: 2 rlist: 1 [<socket.socket fd=280, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080)>, <socket.socket fd=320, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080), raddr=(‘127.0.0.1‘, 52810)>]
inputs: 3 rlist: 0 [<socket.socket fd=280, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080)>, <socket.socket fd=320, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080), raddr=(‘127.0.0.1‘, 52810)>, <socket.socket fd=248, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080), raddr=(‘127.0.0.1‘, 52873)>]

发现情况和第一个客户端一样

客户端发多条信息:

客户端:
>> adasdasasd
adasdasasd
>> sdfsd
sdsdfsd

服务端:
inputs: 3 rlist: 0 [<socket.socket fd=284, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080)>, <socket.socket fd=292, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080), raddr=(‘127.0.0.1‘, 52955)>, <socket.socket fd=276, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080), raddr=(‘127.0.0.1‘, 52957)>]
inputs: 3 rlist: 1 [<socket.socket fd=284, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080)>, <socket.socket fd=292, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080), raddr=(‘127.0.0.1‘, 52955)>, <socket.socket fd=276, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080), raddr=(‘127.0.0.1‘, 52957)>]
inputs: 3 rlist: 0 [<socket.socket fd=284, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080)>, <socket.socket fd=292, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080), raddr=(‘127.0.0.1‘, 52955)>, <socket.socket fd=276, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080), raddr=(‘127.0.0.1‘, 52957)>]
inputs: 3 rlist: 1 [<socket.socket fd=284, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080)>, <socket.socket fd=292, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080), raddr=(‘127.0.0.1‘, 52955)>, <socket.socket fd=276, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080), raddr=(‘127.0.0.1‘, 52957)>]
inputs: 3 rlist: 0 [<socket.socket fd=284, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080)>, <socket.socket fd=292, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080), raddr=(‘127.0.0.1‘, 52955)>, <socket.socket fd=276, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 8080), raddr=(‘127.0.0.1‘, 52957)>]
  • 2.实现读写分离

利用第二个参数,如果客户端发消息,则将客户端放在outputs中,select每次循环都会将对象返回到wlist,最后循环wlist,处理消息,发送至客户端,再在outpu中移除该对象

import socket
import select

sk = socket.socket()
ip_port = ("127.0.0.1",8080)
sk.bind(ip_port)
sk.listen(5)

inputs = [sk,]     #定义监听的对象列表。默认只监听服务端socket对象
outputs = []      #定义消息输出列表
message = {}      #定义一个字典,用于记录客户端对象和收到的消息  key为客户端对象,value为消息

while True:    #循环检测对象
    #监听sk(服务端)对象,如果sk发生变化,表示有新连接来了,此时rlist的值为sk
    rlist,wlist,e = select.select(inputs,outputs,[],1)
    print("inputs:",len(inputs),"rlist:",len(rlist),"outputs:",len(outputs),"wlist:",len(wlist))  #打印inputs 便于查看效果
    #读数据
    for i in rlist:       #循环变化的列表,然后对每个对象进行操作
        if i == sk:     #如果是服务端socket对象发生变化,表示新连接建立了
            conn,ip = sk.accept()     #建立连接
            inputs.append(conn)      #将新建立的连接加入监听的列表中
            message[conn] = []    #创建关于新连接的key
        else:         #否则是客户端给服务端发消息了。所以需要收消息
            try:
                recv_data = i.recv(1024)    #接受数据
                if not recv_data:       #判断数据是否为空
                    raise Exception("disconnect")       #为空的话,主动抛出异常
                else:
                    outputs.append(i)           #将发消息的对象存入outputs
                    message[i].append(recv_data)        #将发消息的对象和消息存入message
            except:
                inputs.remove(i)            #捕捉到异常,客户端断开连接,则在监控列表中删除此对象
                del message[i]  #在消息字典中删除此对象key/value
    #发数据
    for i in wlist:     #循环wlist列表。如果wlist列表中有值的话,说明收到客户端消息
        msg = message[i].pop()      #取出该客户端对象发的消息,并在列表中删除
        resp = ‘respond‘.encode() + msg         #处理客户端的消息
        i.send(resp)        #发送给客户端
        outputs.remove(i)       #outputs列表中删除该对象,如果不删除,下次循环还会误认为其发消息了

sk.close()

客户端建立连接,并发送消息:

inputs: 1 rlist: 0 outputs: 0 wlist: 0
inputs: 1 rlist: 0 outputs: 0 wlist: 0
inputs: 1 rlist: 0 outputs: 0 wlist: 0
inputs: 1 rlist: 1 outputs: 0 wlist: 0        #第一个客户端建立连接
inputs: 2 rlist: 0 outputs: 0 wlist: 0
inputs: 2 rlist: 1 outputs: 0 wlist: 0          #第二个客户端建立连接
inputs: 3 rlist: 0 outputs: 0 wlist: 0
inputs: 3 rlist: 0 outputs: 0 wlist: 0
inputs: 3 rlist: 0 outputs: 0 wlist: 0
inputs: 3 rlist: 0 outputs: 0 wlist: 0
inputs: 3 rlist: 0 outputs: 0 wlist: 0
inputs: 3 rlist: 1 outputs: 0 wlist: 0        #第一个客户端发送一条消息
inputs: 3 rlist: 0 outputs: 1 wlist: 1        #消息记录至outpu
inputs: 3 rlist: 0 outputs: 0 wlist: 0        #返回客户端消息,outputs清空
inputs: 3 rlist: 0 outputs: 0 wlist: 0
inputs: 3 rlist: 1 outputs: 0 wlist: 0
inputs: 3 rlist: 0 outputs: 1 wlist: 1
inputs: 3 rlist: 0 outputs: 0 wlist: 0
inputs: 3 rlist: 1 outputs: 0 wlist: 0
inputs: 3 rlist: 0 outputs: 1 wlist: 1
inputs: 3 rlist: 1 outputs: 0 wlist: 0
inputs: 3 rlist: 0 outputs: 1 wlist: 1
inputs: 3 rlist: 1 outputs: 0 wlist: 0
inputs: 3 rlist: 0 outputs: 1 wlist: 1
inputs: 3 rlist: 0 outputs: 0 wlist: 0
inputs: 3 rlist: 0 outputs: 0 wlist: 0

最后,这就根据select方法实现了IO多路复用,即一个简单的服务端和客户端伪并发的通信

socket源码剖析

在socket基础中,我们最后说的使用socketserver实现多线程并发处理客户端请求,下面进行深入剖析实现原理

import socketserver
import subprocess

class MyServer(socketserver.BaseRequestHandler):  #继承
    def handle(self):   #handle方法。注意此时send和recv时调用的self.request方法
        self.request.sendall(bytes(‘Welcome‘,encoding=‘utf-8‘))
        while True:
            try:
                recv_data = self.request.recv(1024)
                if not recv_data: break
                p = subprocess.Popen(str(recv_data, encoding=‘utf-8‘), shell=True, stdout=subprocess.PIPE,stderr=subprocess.PIPE)
                res = p.stdout.read()
                if not res:
                    send_data = p.stderr.read()
                else:
                    send_data = res
                if not send_data:
                    send_data = ‘no output‘.encode()

                data_size = len(send_data)
                self.request.send(bytes(str(data_size), encoding=‘utf-8‘))
                self.request.recv(1024)
                self.request.send(send_data)
            except Exception:
                break

if __name__ == ‘__main__‘:

    server = socketserver.ThreadingTCPServer((‘127.0.0.1‘,8080),MyServer)    #启动server
    server.serve_forever()

实现socketserver运行的代码

  • 下面开始从外到内进行剖析

    • 1.server = socketserver.ThreadingTCPServer((‘127.0.0.1‘,8080),MyServer) 当解析器之行到此步骤时,调用了socketserver的ThreadingTCPServer方法,ThreadingTCPServer是一个类,继承了TreadingMinIn和TCPServer class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass
    • 2.执行ThreadingTCPServer类的构造方法__init__,根据类的多继承原则,子类不存在的话依次查找父类
      • 查找 父类TreadingMinIn是否有__init__方法,结果没有,其没有父类,本段查询终止

        ```python
        class ThreadingMixIn:
            """Mix-in class to handle each request in a new thread."""
        
            # Decides how threads will act upon termination of the
            # main process
            daemon_threads = False
        
            def process_request_thread(self, request, client_address):
                """Same as in BaseServer but as a thread.
        
                In addition, exception handling is done here.
        
                """
                try:
                    self.finish_request(request, client_address)
                    self.shutdown_request(request)
                except:
                    self.handle_error(request, client_address)
                    self.shutdown_request(request)
        
            def process_request(self, request, client_address):
                """Start a new thread to process the request."""
                t = threading.Thread(target = self.process_request_thread,
                                     args = (request, client_address))
                t.daemon = self.daemon_threads
                t.start()

        ThreadingMinIn类

      • 查找父类TCPServer是否有__init__方法,

        class TCPServer(BaseServer):
        
            address_family = socket.AF_INET
        
            socket_type = socket.SOCK_STREAM
        
            request_queue_size = 5
        
            allow_reuse_address = False
        
            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

        TCPServer的构造方法

        TCPServer有构造方法,但 BaseServer.__init__(self, server_address, RequestHandlerClass)实现了首先执行父类BaseServer的构造方法

      • 查找TCPServer父类BaseServer的构造方法:

        class BaseServer:
        
        timeout = None
        
        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

        BaseServer的构造方法

      从此构造方法中,server_address为定义的ip和端口,RequestHandlerClass为自己定义的class类MyServer

      • 继续执行TCPServer的构造方法

      执行sever_bind()方法,实现绑定ip和端口

      def server_bind(self):
                          """Called by constructor to bind the socket.
      
                          May be overridden.
      
                          """
                          if self.allow_reuse_address:
                              self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
                          self.socket.bind(self.server_address)
                          self.server_address = self.socket.getsockname()

      server_bind方法

      执行server_activate()方法,实现监听

      def server_activate(self):
                      """Called by constructor to activate the server.
      
                      May be overridden.
      
                      """
                      self.socket.listen(self.request_queue_size)

      server_activate方法

      此时socket已经创建完毕

    • 3.构造方法执行完之后,开始执行下面的server的serve_forever()方法
      • 重新开始从子类找server_forever方法
      • 在BaseServer中找到server_forever方法

      def serve_forever(self, poll_interval=0.5):
      
              self.__is_shut_down.clear()
              try:
                  with _ServerSelector() as selector:
                      selector.register(self, selectors.EVENT_READ)
      
                      while not self.__shutdown_request:
                          ready = selector.select(poll_interval)
                          if ready:
                              self._handle_request_noblock()
      
                          self.service_actions()
              finally:
                  self.__shutdown_request = False
                  self.__is_shut_down.set()

      serve_foreve方法

      • 从server_forever方法中看出,最终要执行self._handle_request_noblock()方法
      • 从子类重新开始找self._handle_request_noblock()方法,发现在BaseServer中找到

       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:
                      self.handle_error(request, client_address)
                      self.shutdown_request(request)

      _handle_request_noblock方法

      • 从self._handle_request_noblock()中看出,最终要执行self.process_request方法
      • 从子类中继续查找process_request方法,发现在ThreadingMixIn类和BaseServer中同样都存在,根据多继承的原则,将优先执行ThredingMixIn的process_request()方法

       def process_request(self, request, client_address):
              t = threading.Thread(target = self.process_request_thread,
                                   args = (request, client_address))
              t.daemon = self.daemon_threads
              t.start()

      process_request方法

      • process_request方法调用threading.Thread方法,实现多线程处理并发请求,每个线程执行的方法为self.process_request_thread,下面开始继续找该方法
      • 在ThredingMixIn找到该方法

      def process_request_thread(self, request, client_address):
              """Same as in BaseServer but as a thread.
      
              In addition, exception handling is done here.
      
              """
              try:
                  self.finish_request(request, client_address)
                  self.shutdown_request(request)
              except:
                  self.handle_error(request, client_address)
                  self.shutdown_request(request)

      process_request_thread方法

      • process_request_thread调用了self.finish_request方法,继续查找self.finish_request方法
      • 在BaseServer中找到finish_request方法

      def finish_request(self, request, client_address):
              """Finish one request by instantiating RequestHandlerClass."""
              self.RequestHandlerClass(request, client_address, self)

      finish_reques方法

      • finish_request方法中调用了self.RequestHandlerClass方法,注意此时self.RequestHandlerClass在执行构造方法的时候已经定义为自己创建的Myclass类,所以self.RequestHandlerClass()=self.Myclass()
      • 执行self.Myclass(),首先会执行构造函数,其没有,继续查找父类socketserver.BaseRequestHandler
      • 父类socketserver.BaseRequestHandler中有构造方法

      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

      BaseRequestHandler类

      • socketserver.BaseRequestHandler的构造方法定义了setup() handler() finish()三个方法的执行顺序,最后查找到handler()方法并执行,也就验证了自定义类中必须使用handler()方法来处理客户端请求
  • 总结

上述只是一个简单的查找源码实现多线程的过程,查找过程中一定要捋清楚每个父类的方法,根据多继承原则进行查找和执行,上述过程简单做成流程图如下:
?

时间: 2024-10-15 00:54:08

sokect编程进阶的相关文章

Python基础-第七天-面向对象编程进阶和Socket编程简介

本篇内容: 1.面向对象编程进阶-静态方法 2.面向对象编程进阶-类方法 3.面向对象编程进阶-属性方法 4.面向对象编程进阶-特殊成员(内置方法) 5.面向对象编程进阶-反射 6.异常处理.断言 7.Socket编程简介 一.面向对象编程进阶-静态方法 1.静态方法的实现 通过@staticmethod装饰器可以把其装饰的方法变为一个静态方法: 变成静态方法后,形参中可以不用写self了.如果写了self,默认是不会把对象本身传递给self,需要手动传递: class Dog(object):

shell脚本编程进阶练习题

这两天学习了shell脚本编程进阶,作为一枚文科生,小编觉得...恩..脚本很烧脑.....,不过小编还是做了些题,稍作总结后,呈给各位看官,内容如下: 一.条件选择if语句 选择执行: 注意:if语句可嵌套 单分支 if 判断条件;then 条件为真的分支代码 fi 双分支 if 判断条件; then 条件为真的分支代码 else 条件为假的分支代码 fi 多分支 if 判断条件1; then 条件为真的分支代码 elif 判断条件2; then 条件为真的分支代码 elif 判断条件3; t

进击的Python【第七章】:Python的高级应用(四)面向对象编程进阶

Python的高级应用(三)面向对象编程进阶 本章学习要点: 面向对象高级语法部分 静态方法.类方法.属性方法 类的特殊方法 反射 异常处理 Socket开发基础 一.面向对象高级语法部分 静态方法 要在类中使用静态方法,需在类成员函数前面加上@staticmethod标记符,以表示下面的成员函数是静态函数.使用静态方法的好处是,不需要定义实例即可使用这个方法.另外,多个实例共享此静态方法. 类方法 类方法与普通的成员函数和静态函数有不同之处,在接触的语言中好像也没见过这种语义,看它的定义: 

Scala函数式编程进阶

1 package com.dtspark.scala.basics 2 3 /** 4 * 函数式编程进阶: 5 * 1,函数和变量一样作为Scala语言的一等公民,函数可以直接赋值给变量: 6 * 2, 函数更长用的方式是匿名函数,定义的时候只需要说明输入参数的类型和函数体即可,不需要名称,但是如果你要使用的话,一般会把这个匿名函数赋值给一个变量(其实是val常量),Spark源码中大量存在这种语法,必须掌握: 7 * 3, 函数可以作为参数直接传递给函数,这极大的简化的编程的语法,为什么这

shell编程进阶

Shell编程进阶 Shell结构以及执行 [[email protected] ~]# mkdir shell [[email protected] ~]# cd shell/ [[email protected] shell]# vim first.sh #!/bin/bash ##The first test shell script ##written by wangchao ls /tmp/ echo "This is the first script." [[email p

bash脚本编程进阶篇

bash脚本编程进阶篇   函数.数组.字符串操作 一.函数:脚本编程中的函数与我们数学中的函数有着根本区别.这里的函数主要是为了实现过程式编程代码重用的作用.比如一个用于计算数字的函数,当我们需要使用计算数字的时候,直接调用这个函数过来而不必每一次计算数字都要自己重新写一次.因此,函数的主要功能可以概括为:便于实现模块化编程:便于代码的重用:使程序简洁.我们定义函数,可以分两种两种结构. 第一种函数结构,用function声明一个函数,后跟函数名.函数体用花括号括起来.第二种结构是用函数名跟小

SHELL脚本编程进阶(二)

写在前面(最重要) 本文部分资料和示例援引自以下书籍.在此,感谢原作者的创作,以及所有译者的付出,向他们致敬. Advanced Bash-Scripting Guide <高级Bash脚本编程指南>Revision 10中文版 Linux脚本编程执导 其中 <高级Bash脚本编程指南>Revision 10中文版 是 <Advanced Bash-Scripting Guide> 的中文翻译版,文档翻译正在进行中,再次感谢译者付出. 前言 在之前的文章 Linux 基

JavaWeb-17 (JDBC编程进阶2)

JavaWeb-17 JDBC编程进阶2 一.自定义框架 习惯:搞工具类尽量写多些注释 开始制定框架 项目架构: c3p0-config.xml <?xml version="1.0" encoding="UTF-8"?> <c3p0-config> <default-config> <property name="driverClass">com.mysql.jdbc.Driver</pro

Spark函数式编程进阶

函数式编程进阶 1.函数和变量一样作为Scala语言的一等公民,函数可以直接复制给变量: 2.函数更长用的方式是匿名函数,定义的时候只需要说明输入参数的类型和函数体即可,不需要名称,但是匿名函数赋值给一个变量(其实是val常量),Spark源码中大量存在这种语法: 3.函数可以作为参数直接传递给函数,这极大地简化的编程语法: 4.函数式编程一个非常强大的地方之一在于函数的返回值可以是函数,当函数的返回类型是函数的时候,这个时候就是表明Scala的函数是实现了闭包! Scala壁报的内幕是:Sca