在解析socketserver是如工作之前,我们先看看socektserver类的继承关系图:

  请求类继承关系:

          

  server类继承关系:

          

  有了上面的继承关系图后,我们解析socketserver就轻松多了,下面,我们从代码开始,慢慢揭开socketserver面纱:

import socketserver
import struct, json, os class FtpServer(socketserver.BaseRequestHandler):
coding = 'utf-8'
server_dir = 'file_upload'
max_packet_size = 1024
BASE_DIR = os.path.dirname(os.path.abspath(__file__)) def handle(self):
print(self.request)
while True:
data = self.request.recv(4)
data_len = struct.unpack('i', data)[0]
head_json = self.request.recv(data_len).decode(self.coding)
head_dic = json.loads(head_json)
cmd = head_dic['cmd']
if hasattr(self, cmd):
func = getattr(self, cmd)
func(head_dic) def put(self):
pass def get(self):
pass if __name__ == '__main__':
HOST, PORT = "localhost", 9999
with socketserver.ThreadingTCPServer((HOST, PORT), FtpServer) as server:
server.serve_forever()

  我们通过socketserver.ThreadingTCPServer实例化对象server,那么此时应用调用类的__init__方法,前往ThreadingTCPServer类看看:

class ThreadingTCPServer(ThreadingMixIn, UDPServer): pass

  发现這个类啥都没写,我们知道,如果一个类什么方法都没有定义,那么它的方法肯定都是从其父类继承而来,接着,先到ThreadingMinIn里面看看,

class ThreadingMixIn:
daemon_threads = False def process_request_thread(self, request, client_address):
passdef process_request(self, request, client_address):
pass

  这个类也没有__init__方法,因此,我们应该去右继承的父类TCPserver中找:

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):
BaseServer.__init__(self, server_address, RequestHandlerClass)#
self.socket = socket.socket(self.address_family,self.socket_type) # 创建套接字对象
if bind_and_activate:
try:
self.server_bind() #绑定端口和IP
self.server_activate() # 监听端口
except:
self.server_close()
raise

  看到Tcpserver的__init__方法,完成了以下几件事:

    创建套接字,绑定端口和IP,并监听

    将端口、IP和我们创建类传递到Baseserver类中;

  此时,对象的初始化工作并没有完成,接着,我们要进入baseserver类,看看该类下的__init__完成了什么工作:

class BaseServer:
timeout = None
def __init__(self, server_address, RequestHandlerClass):
self.server_address = server_address #将端口和IP暂存
self.RequestHandlerClass = RequestHandlerClass #暂存我们创建的类
self.__is_shut_down = threading.Event() # 创建event对象

  到此,对象的初始化工作完成。然后是调用serve_forever()方法,开始不断循环监听。下面,我们来看看,这个server_forever实现

  注意:我们要清楚一点,我们在找這个方法在哪里的时候,一定要按照顺序去找,也就是说,我们先得从子类开始找,如果子类不存在,就去其父类找。下面我们就遵循這个原则来找找看。

  先来看看左继承的父类ThreadingMixIn中有没有server_forever:

class ThreadingMixIn:
daemon_threads = False def process_request_thread(self, request, client_address):
try:
self.finish_request(request, client_address)
except Exception:
self.handle_error(request, client_address)
finally:
self.shutdown_request(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()

  再来看看父类Tcpserver:

class TCPServer(BaseServer):def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True):

    def server_bind(self):
def server_activate(self):
def server_close(self):
def fileno(self):
def get_request(self):
def shutdown_request(self, request):
def close_request(self, request):

  我们发现,没有server_forever方法,好,我去其继承的父类BaseServer类看看:

class BaseServer:def __init__(self, server_address, RequestHandlerClass):
def server_activate(self): def serve_forever(self, poll_interval=0.5):
def shutdown(self):
def service_actions(self): def handle_request(self):
def _handle_request_noblock(self):
def handle_timeout(self): def verify_request(self, request, client_address):
def process_request(self, request, client_address):
def server_close(self): def finish_request(self, request, client_address):
def shutdown_request(self, request):
def close_request(self, request): def handle_error(self, request, client_address):
def __enter__(self):
def __exit__(self, *args):

  我们发现server_forever()果然在這个类中,现在,我们的目标是:找到在什么地方调用我们自己写的handle方法。

  在我们找到的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)#原来底层是用epoll来实现不断循环监听
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()

  好,我大致找到了链接的处理入口,我们跟进去,继续寻找:

    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)#注意这里的process_request()
except Exception:
self.handle_error(request, client_address)
self.shutdown_request(request)
except:
self.shutdown_request(request)
raise
else:
self.shutdown_request(request)

  到源码中,我们找到该函数,现在,只看我划线的部分。其他部分都是针对异常的处理,如果没有异常,其他都是不会执行的,所以,其他的异常处理,我们先暂时不看。

  我们发现,如果有链接,最后会交给process_request()(我们会发现,在baseserver类和ThreadingMixIn都有這个方法,这里找类方法,一定要按照类的继承顺序来查找),所以,我们到ThreadingMiXin中去看看processs_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_requsest_thread()方法,

    def process_request_thread(self, request, client_address):
try:
self.finish_request(request, client_address)
except Exception:
self.handle_error(request, client_address)
finally:
self.shutdown_request(request)

  到此为止,链接建立成功!

  下面,我们来看看,当有消息发送,是如何进行处理的。

  当有消息发送,selector监听到了,

    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()

  下面我们跟进_handle_request_noblock(),

    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)

  我们到process_request()看看:

    def process_request(self, request, client_address):
"""Start a new thread to process the request."""
t = threading.Thread(target = self.process_request_thread, # start a threading to handle the request
args = (request, client_address))
t.daemon = self.daemon_threads
t.start()

  然后开启线程执行,process_request_thread()方法,

    def process_request_thread(self, request, client_address):

        try:
self.finish_request(request, client_address) # -----> to Baseserver find
except Exception:
self.handle_error(request, client_address)
finally:
self.shutdown_request(request)

  然后调用finish_request()方法,现在我们跟进看看,

    def finish_request(self, request, client_address):
self.RequestHandlerClass(request, client_address, self)

  执行了RequestHandlerClass(request, client_address, self),這个是啥??还记得最开始我们传进来的类保存在哪呢?没错,就是RequestHandlerClass里面,现在这里才开始实例化這个类,也就是说,在这里开始调用我们自己的类了。既然是调用我们自己的类,那么必然要实例化,我们先回到自己创建的类,找找__init__方法。

class MyTCPHandler(socketserver.BaseRequestHandler):
def handle(self):
self.data = self.request.recv(1024).strip()
print("{} wrote:".format(self.client_address[0]))
print(self.data)
self.request.sendall(self.data.upper())

  自己类没有写__init__方法,那么我去它继承的BaseRequestHandler()下面找找看:

class BaseRequestHandler:
def __init__(self, request, client_address, server):
self.request = request # 接受传进来的请求链接
self.client_address = client_address # 客户端的ip/端口
self.server = server #
self.setup()
try:
self.handle()
finally:
self.finish() def setup(self):
pass def handle(self):
pass def finish(self):
pass

  我们来看看,它继承类实例化完成了哪些操作:

    调用handle()方法,我们发现,在这个类中也有一个handle()方法,那么这里调用时调用自己写的还是這个类中的呢?

  当然是调用我们自己写!

  至此,我们完成了一次通信的完整过程!

  总结sockerserver整个流程:

    1.开启了线程,支持并发操作

    2.I/O多路复用,监听多个文件描述符!

 

参考

 

Python面试题之解读Socketserver & Tcpserver的更多相关文章

  1. 文件上传下载、socketserver(并发)、解读socketserver源码

    1.文件上传/下载 学习了socket套接字,我们现在可以写一个文件上传/下载的程序,如下示例: 分析上边代码,我们发现,client发送上传文件相关信息的字典序列化之后,server又给client ...

  2. python公司面试题集锦 python面试题大全

    问题一:以下的代码的输出将是什么? 说出你的答案并解释. class Parent(object): x = 1 class Child1(Parent): pass class Child2(Par ...

  3. python基础之socket与socketserver

    ---引入 Socket的英文原义是“孔”或“插座”,在Unix的进程通信机制中又称为‘套接字’.套接字实际上并不复杂,它是由一个ip地址以及一个端口号组成.Socket正如其英文原意那样,像一个多孔 ...

  4. 震惊!几道Python 理论面试题,Python面试题No18

    本面试题题库,由公号:非本科程序员 整理发布 第1题: 简述解释型和编译型编程语言? 解释型语言编写的程序不需要编译,在执行的时候,专门有一个解释器能够将VB语言翻译成机器语言,每个语句都是执行的时候 ...

  5. Python中下划线---完全解读(转)

      Python中下划线---完全解读 Python 用下划线作为变量前缀和后缀指定特殊变量 _xxx 不能用’from module import *’导入 __xxx__ 系统定义名字 __xxx ...

  6. Python面试题 —— 获取列表中位数

    中位数是一个可将数值集合划分为相等的上下两部分的一个数值.如果列表数据的个数是奇数,则列表中间那个数据就是列表数据的中位数:如果列表数据的个数是偶数,则列表中间那2个数据的算术平均值就是列表数据的中位 ...

  7. Python进阶:全面解读高级特性之切片!

    导读:切片系列文章连续写了三篇,本文是对它们做的汇总.为什么要把序列文章合并呢?在此说明一下,本文绝不是简单地将它们做了合并,主要是修正了一些严重的错误(如自定义序列切片的部分),还对行文结构与章节衔 ...

  8. 【Python】【面试必看】Python笔试题

    前言 现在面试测试岗位,一般会要求熟悉一门语言(python/java),为了考验求职者的基本功,一般会出 2 个笔试题,这些题目一般不难,主要考察基本功.要是给你一台电脑,在编辑器里面边写边调试,没 ...

  9. Python面试题整理-更新中

    几个链接: 编程零基础应当如何开始学习 Python ? - 路人甲的回答 网易云课堂上有哪些值得推荐的 Python 教程? - 路人甲的回答 怎么用最短时间高效而踏实地学习 Python? - 路 ...

随机推荐

  1. 【BZOJ1935/4822】[Shoi2007]Tree 园丁的烦恼/[Cqoi2017]老C的任务 树状数组

    题意:两道题差不多,都是给你一堆平面上的点,每个点有权值,然后m次询问求某一矩形区域内的点权和 题解:先离散化,然后将询问拆成左右两条线段,然后将点和这些线段一起按x坐标排序,在y轴上维护树状数组.然 ...

  2. LeetCode 笔记系列 18 Maximal Rectangle [学以致用]

    题目: Given a 2D binary matrix filled with 0's and 1's, find the largest rectangle containing all ones ...

  3. Linux安装vsftpd

    卸载vsftpd sudo yum remove vsftpd 安装vsftpd sudo yum -y install vsftpd 创建一个文件夹用来当作ftp得仓库 cd / sudo mkdi ...

  4. ajax请求加全局loading , 个别特殊请求不显示loading

    项目中,请求开始前加载loading遮罩层,请求结束关闭遮罩,一般都会加在全局中,但有个别请求不需要加全局loading 的话,这时候就需要对这些请求进行配置 全局加loading: ; functi ...

  5. Hibernate的二级缓存(SessionFaction的外置缓存)-----Helloword

    1. 使用 Hibernate 二级缓存的步骤: 1). 加入二级缓存插件的 jar 包及配置文件: I. 复制 \hibernate-release-4.2.4.Final\lib\optional ...

  6. HDU 5701 中位数计数 百度之星初赛

    中位数计数 Time Limit: 12000/6000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others) Total Sub ...

  7. 20165330 2017-2018-2 《Java程序设计》第2周学习总结

    课本知识总结 第二章 基本数据类型与数组 标识符:标识类名.变量名.方法名.类型名.数组名及文件名的有效字符序列. 标识符的第一个字符不能是数字字符,且字母区分大小写. Java语言使用Unicode ...

  8. wiki配置文件

    jira数据库配置文件(链接:https://blog.csdn.net/jiangguilong2000/article/details/39718407) /var/atlassian/appli ...

  9. Python爬虫实例(一)爬取百度贴吧帖子中的图片

    程序功能说明:爬取百度贴吧帖子中的图片,用户输入贴吧名称和要爬取的起始和终止页数即可进行爬取. 思路分析: 一.指定贴吧url的获取 例如我们进入秦时明月吧,提取并分析其有效url如下 http:// ...

  10. JavaScript自定义函数

    js对象转成用&拼接的请求参数(转) var parseParam=function(param, key){ var paramStr=""; if(param inst ...