从Python socketserver中理解面向对象

一、前言

前段时间做Raspberry Pi的开发,正好借这个机会用Python写了几个socket的服务,之前开发都是照着网上的代码自己去改改弄弄,Python没有过开发经历,自然也是这么来起步。开发过程中用到了Python类的继承,并使用类作为对象进行传递的方法,虽然程序是码出来了,但是心有不甘,对于类继承和重写还是知之甚少,于是借此死磕一下,再深入理解一下什么是面向对象。

二、代码分析

扯的有点远了,直接进入正题。Python程序使用的是TCP/IP端口进行协议转换,程序既是server端,能够一对多,又是client端,将server端接收的信息进行转换后再输出。server端一对多通信在Python库里有封装好的socketserver模块,同时Python库里还有socket模块,用于一对一通信。这次主要探讨的就是socketserver模块,server端一对多通信需要用到socketserver模块中的两个类,BaseRequestHandler和ThreadingTCPServer,程序大致代码如下:


MyServer类继承BaseRequestHandler



ThreadingTCPServer为每一次连接建立一个单独线程

新建MyServer类,并继承BaseRequestHandler类,同时重写该类下handle方法,handle方法主要是为了对soket接收的数据进行处理;实例化ThreadingTCPServer,并传入IP/端口号,以及之前新建的MyServer类,使用serve_forever()开启服务,每当监听到一个连接,就新建一个单独的线程。短短这几行代码,就把TCP数据通信全搞定了,不用担心线程间数据传递的阻塞,不要担心连接的client数量,就是这么神奇,神奇到我刚开始完全不知道究竟发生了什么,因为这个类,封装的太好了,它帮我们把能想到的都放进去了。

2.1、BaseRequestHandler类

究竟里面发生了什么,我们只能按照给的两个线索,BaseRequestHandler和ThreadingTCPServer类去查,首先是BaseRequestHandler类,在socketserver中它的定义如下:


BaseRequestHandler类

这个类其实啥都没干,它就是个基类,定义了handle,finish,setup这三个API,同时初始化了三个参数request,client_address,server,并在初始化时调用了handle和finish两个方法,这也就是为什么我们在继承这个基类的时候一定要重写handle方法,因为这个类在实例化时会执行handle函数。我们暂时记住这三个传入参数,因为还不知道它们如何传入,只能根据字面意思理解为“请求”,“客户端地址”,“服务端”。

2.2、ThreadingTCPServer、ThreadingMixIn、TCPServer

其次是ThreadingTCPServer类,它的定义就更简单:


ThreadingTCPServer类

就一行,pass是占位符,等于没有,但是它有同时继承了ThreadingMixIn和TCPServer两个类,ThreadingMixIn先继承,继续往前查询ThreadingMixIn类


ThreadingMixIn类

这个类也不多,从解释上理解它的作用就是处理每次请求,并新建一个线程,就里面三个内参daemon_threads(线程保护,默认不开),_block_on_close(关闭服务时的线程阻塞等待,默认不开),_threads(线程);同时类里面还有三个方法:

a.process_request_thread

解释上面说类似于BaseServer,但是它是作为线程使用,同时内部定义了三个方法,finish_request,handle_request,shutdown_request,暂时不理解有什么用。

b.process_request

开启一个线程来处理请求,然后把该线程加入到_threads这个定义好的线程组里面去。request和client_address,就是BaseRequestHandler初始化时的需要传入的参数中的两个,但是这里没看到process_request方法的调用,估计后面会有解释。

c.server_close

首先会去继承父类中的server_close方法(不用管),然后关闭所有线程。

至此,ThreadingMixIn这个类查询完,继续往下查询TCPServer类

2.3、TCPServer、BaseServer


TCPServer类

TCPServer继承了BaseServer这个基类,同时在初始化时在继承基类的基础上进行了一定的修改,BaseServer我们会在最后查询,先了解下TCPServer这个类。

TCPServer里前两个变量address_family和socket_type用于指定端口的连接方式,即TCP数据流方式的连接。

request_queue_size,指定线程间通信的队列长度,默认是5。

allow_reuse_address,看字面意思是允许复用地址,实际应用中没试过怎么用。

a.__init__

继承BaseServer初始化方法,同时引用server_bind,server_active,server_close三个内部方法,主要用作绑定并打开服务端监听。

b.server_bind

在初始化时被引用,用于绑定TCP服务端端口。

c.server_active

在初始化时被引用,用于打开TCP服务端监听。

d.server_close

在初始化时被引用,发生异常情况时断开连接。

e.fileno

返回TCP端口的文件名。

f.get_request

当有client端连接时,返回连接的请求对象以及连接设备的地址。

g.shutdown_request

关闭指定客户端的连接。

h.close_request

关闭所有客户端的连接。

由上述可见,TCPServer类也没用做太多的工作,它主要的目的是建立server端的绑定和监听,同时可以对server端的连接对象做一定的操作,至此我们还是不明白这里面的参数传递关系,只有继续往下看,再深入了解BaseServer这个基类。


BaseServer类

BaseServer中变量只有一个timeout,默认是None,但定义了17个方法,__init__初始化的时候定义了两个外部传入参数server_address和RequestHandlerClass,BaseServer的子类TCPServer,其子类的子类ThreadingTCPServer,都包含这两个外部传入参数,并且没有被修改过。因此正好契合我们刚开始在实例化socketserver.ThreadingTCPServer时传入的两个参数“ip_port”,“MyServer”(具体请看开始部分),转了一圈,终于摸索到参数怎么传递进来了(即在BaseServer的子类TCPServer的子类ThreadingTCPServer初始化的时候)。

原来由于方法太多,且很多方法内都是一个空的API(方便重写),因此只截取了前面几个,本文的目的不在于去分析BaseServer里面每个方法的作用,但是有一个方法对于我们非常重要,因为在最开始的时候已经被引用过了,该方法就是serve_forever(在实例化socketserver.ThreadingTCPServer后调用的唯一一个方法),我们来详细分析一下serve_forever到底做了写什么,外部传入了连个参数ip_port已经在TCPServer类的初始化时被引用了,MyServer至此还没有被引用,那么可以推测它一定会在BaseServer中被引用到。


serve_forever方法定义


a._is_shut_down_

是线程之间通信等待的标志位,_is_shut_down.clear()表示标志位清零,不理解的话没关系,继续往下走。

b.实例化ServerSelector为selector对象

其实ServerSelector的基类就是BaseSelector,它的作用是实现IO的自动多路复用(说明白点就是socket server端口的一对多通信).

c.为每次IO请求注册一个实例

同个每隔0.5秒(poll_interval)去查询是否有新的socket请求到达,如果有新的请求的话,就执行handle_request_noblock方法。

d.service_actions是个空方法

可以被重写,每隔0.5秒去做一个操作。

e.handle_request_noblock方法

字面意思是不带阻塞的去执行请求。方法内部获取client的连接亲求,返回请求的对象(request)和client地址(client_address);verify_request默认返回是true,然后去执行process_request方法,如果发生错误,就去执行shutdown_request和handle_error,字面意思很好理解,我们只需要关注process_request会怎么操作。


handle_request_noblock方法

f.process_request方法

这里解释提醒我们process_request已经被ThreadingMixIn给重写了,那我们回到ThreadingMixIn的类里继续查看process_request,变化不大,主要是去调用finish_request方法。

proces_request方法-BaseServer类


process_request方法-ThreadingMixIn类


g.finish_request方法

终于要到最后一个方法了,它去调用RequestHandlerClass,这是在BaseServer在实例化时需要传入的对象之一,进而它的子类的子类,ThreadingTCPServer也沿用了这个对象传递,实例化RequestHandlerClass类,并传入request和client_address,这两个参数哪里来的?前面handle_request_noblock方法调用而产生的两个对象。这样也能解释为什么在MyServer类重写handle的过程中,有“conn=self.request”,request就是每次client请求获得的一个对象。


finish_request方法-BaseServer类

至此,sockserver模块中TCP server端一对多通信的整个流程算是连接成功了,整个是一个闭环,我们再梳理下大致流程。

1、实例化socketserver.ThreadingTCPServer,并传入IP端口地址以及MyServer类。

2、调用serve_forever方法,通过select方法实现IO多路复用,并调用handle_request_noblock来处理每次client端的连接请求。

3、handle_request_noblock会建立与client端连接,返回client请求对象和client地址,然后再将这两个参数交给MyServer。

4、MyServer再重写handle方法时,会用到request请求,来接收和发送client端的信息。

最后,回到本文刚开始做项目时的需求,如果我们还需要传入其他参数,比如说与外部server端建立连接的client对象,实现程序里既当服务端又当客户端,我们应该怎么做?其实很简单,重写serve_forever方法,继承原来方法的同时,加入新的参数定义,并重新定义对该参数的操作。

三、总结

总结,sockserver这个模块看下来,花了不少时间,但是过程还是很愉快的,举一反三,什么是面向对象的编程,我觉得不光是会用一个方法,会抄抄改改网上的代码这么简单而已,还应该自己花时间去思考,通过不断的继承、复用、重定义,来让方法变成自己的方法,这也是一个进步的过程,正所谓站在巨人的肩膀上,这也许就是面向对象编程的快乐了吧。

总结的草稿
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容