Python 学习笔记 - socketserver源代码剖析

简介:

前面学习的例子都是单线程的socket收发;如果有多个用户同时接入,那么除了第一个连入的,后面的都会处于挂起等待的状态,直到当前连接的客户端断开为止。

通过使用socketserver,我们可以实现并发的连接。

socketserver的使用很简单:


首先看个简单的例子


服务端:

自己定义一个类,继承socketserver.baserequesthandler;

然后定义一个方法 handle()

然后通过socketserver.threadingTCPServer指定套接字和自己定义的类,每次当客户端连入的时候,会自动实例化一个对象,然后通过server_forever()不断循环读写数据。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author Yuan Li
import  socketserver
class  mysocketserver(socketserver.BaseRequestHandler):
     def  handle( self ):
         conn  =  self .request
         conn.sendall(bytes( "Welcome to the Test system." , encoding = 'utf-8' ))
         while  True :
             try :
                 data  =  conn.recv( 1024 )
                 if  len (data)  = =  0 break
                 print ( "[%s] sends %s"  %  ( self .client_address, data.decode()))
                 conn.sendall(data.upper())
             except  Exception:
                 break
if  __name__  = =  '__main__' :
     server  =  socketserver.ThreadingTCPServer(( '127.0.0.1' 8009 ), mysocketserver)
     server.serve_forever()


客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author Yuan Li
import  socket
ip_port  =  ( '127.0.0.1' 8009 )
=  socket.socket()
s.connect(ip_port)
data  =  s.recv( 1024 )
print (data.decode())
while  True :
     send_data  =  input ( "Data>>>" )
     s.send(bytes(send_data, encoding = 'utf-8' ))
     recv_data  =  s.recv( 1024 )
     print (recv_data.decode())


上面的效果是多个客户端可以同时连入服务器,输入字母,返回大写字母。


客户端没啥好说的,这个和单线程的操作一样;但是服务器咋一看很混乱。我们可以通过剖析源码来弄清他的执行过程。


这个类的基本结构是如下所示的,我们按照顺序来跑一次看看他怎么调用的


wKiom1gNmjTj7TH2AAK6-Px_JOg280.png


1. 首先执行的这句话,很明显ThredingTCPServer是一个类,点进去看看他的实例化过程

1
server  =  socketserver.ThreadingTCPServer(( '127.0.0.1' 8009 ), mysocketserver)

2. 点着Ctrl键,点击这个类,PyCharm会自动打开对应的源码,可以看见这个类又继承了两个父类ThredingMixIn和TCPServer

1
class  ThreadingTCPServer(ThreadingMixIn, TCPServer):  pass

因为他的内容是pass,啥也没做,根据继承的顺序,我们继续往上(从左到右)找init构造函数; 


3. ThreadingMixIn里面没有构造函数,那就继续往右找,TCPServer里面倒是有构造函数,但是他又调用了他父类BaseServer的构造函数,顺着看上去,发现他就是封装了几个值在里面,注意   self.RequestHandlerClass = RequestHandlerClass把我们自己定义的类传进去了

1
2
3
4
5
6
     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


4.接下来,在TCPServer的构造函数里面,他执行了bind,listen的操作,这个和单线程的操作是一样的。到此为止,一个初始化的过程基本就完成了。


5.接下来,执行了server.serve_forever()的操作,我们看看内部是怎么调用的。在这个函数里面,使用了selector的IO多路复用的技术,循环的读取一个文件的操作。接着调用了_handle_request_noblock()函数

1
2
3
4
5
6
7
8
9
10
11
12
   try :
             # XXX: Consider using another file descriptor or connecting to the
             # socket to wake this up instead of polling. Polling reduces our
             # responsiveness to a shutdown request and wastes cpu at all other
             # times.
             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()


6.每次调用函数的时候都记住查找的顺序,从下往上,从左往右,最后在最上面的BaseServer再次找到这个函数,这个函数里面又调用了 process_request函数

1
2
3
4
5
6
7
8
         """
         try :
             request, client_address  =  self .get_request()
         except  OSError:
             return
         if  self .verify_request(request, client_address):
             try :
                 self .process_request(request, client_address)

一定要记住继承的顺序!!顺序!!顺序!!


因为baseserver自己有process_request的方法,ThreadingTCPServer也有同名的方法,当他调用的时候,按照顺序,是执行的ThreadingTCPServer里面的方法!!


wKioL1gNoxaThqRFAAB5Z_o7GWs657.png


wKiom1gNouDj48lSAABEODjRuN0521.png


 可以看见他开了一个多线程

1
2
3
4
5
6
   def  process_request( self , request, client_address):
         """Start a new thread to process the request."""
         =  threading.Thread(target  =  self .process_request_thread,
                              args  =  (request, client_address))
         t.daemon  =  self .daemon_threads
         t.start()

在他调用的process_request_thread里面,他又调用了finsih_request

1
2
3
4
5
6
7
8
9
10
     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)


finish_request里面有对我们自定义的类做了一个实例化的操作

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


因为我们自定义的类没有构造函数,他会去父类寻找,父类里面会尝试执行handle()方法,这就是为什么我们需要在自定义的类里面定义一个同名的方法,然后把所有需要执行的内容都放在这里。

1
2
3
4
5
6
7
  def  __init__( self , request, client_address, server):
         self .request  =  request
         self .client_address  =  client_address
         self .server  =  server
         self .setup()
         try :
             self .handle()


到此,socketserver一个完整的过程就结束了






本文转自 beanxyz 51CTO博客,原文链接:http://blog.51cto.com/beanxyz/1864956,如需转载请自行联系原作者

目录
相关文章
|
2月前
|
移动开发 Python Windows
python编程获取网页标题title的几种方法及效果对比(源代码)
python编程获取网页标题title的几种方法及效果对比(源代码)
|
1月前
|
安全 Linux 数据安全/隐私保护
python知识点100篇系列(15)-加密python源代码为pyd文件
【10月更文挑战第5天】为了保护Python源码不被查看,可将其编译成二进制文件(Windows下为.pyd,Linux下为.so)。以Python3.8为例,通过Cython工具,先写好Python代码并加入`# cython: language_level=3`指令,安装easycython库后,使用`easycython *.py`命令编译源文件,最终生成.pyd文件供直接导入使用。
python知识点100篇系列(15)-加密python源代码为pyd文件
|
1月前
|
网络协议 Java Linux
PyAV学习笔记(一):PyAV简介、安装、基础操作、python获取RTSP(海康)的各种时间戳(rtp、dts、pts)
本文介绍了PyAV库,它是FFmpeg的Python绑定,提供了底层库的全部功能和控制。文章详细讲解了PyAV的安装过程,包括在Windows、Linux和ARM平台上的安装步骤,以及安装中可能遇到的错误和解决方法。此外,还解释了时间戳的概念,包括RTP、NTP、PTS和DTS,并提供了Python代码示例,展示如何获取RTSP流中的各种时间戳。最后,文章还提供了一些附录,包括Python通过NTP同步获取时间的方法和使用PyAV访问网络视频流的技巧。
240 4
PyAV学习笔记(一):PyAV简介、安装、基础操作、python获取RTSP(海康)的各种时间戳(rtp、dts、pts)
|
1月前
|
Python
Socket学习笔记(二):python通过socket实现客户端到服务器端的图片传输
使用Python的socket库实现客户端到服务器端的图片传输,包括客户端和服务器端的代码实现,以及传输结果的展示。
140 3
Socket学习笔记(二):python通过socket实现客户端到服务器端的图片传输
|
1月前
|
JSON 数据格式 Python
Socket学习笔记(一):python通过socket实现客户端到服务器端的文件传输
本文介绍了如何使用Python的socket模块实现客户端到服务器端的文件传输,包括客户端发送文件信息和内容,服务器端接收并保存文件的完整过程。
154 1
Socket学习笔记(一):python通过socket实现客户端到服务器端的文件传输
|
1月前
|
关系型数据库 MySQL 数据库
Mysql学习笔记(四):Python与Mysql交互--实现增删改查
如何使用Python与MySQL数据库进行交互,实现增删改查等基本操作的教程。
67 1
|
1月前
|
Ubuntu Linux Python
Ubuntu学习笔记(六):ubuntu切换Anaconda和系统自带Python
本文介绍了在Ubuntu系统中切换Anaconda和系统自带Python的方法。方法1涉及编辑~/.bashrc和/etc/profile文件,更新Anaconda的路径。方法2提供了详细的步骤指导,帮助用户在Anaconda和系统自带Python之间进行切换。
87 1
|
1月前
|
索引 Python
Python学习笔记编程小哥令狐~持续更新、、、(上)
Python学习笔记编程小哥令狐~持续更新、、、(上)
51 2
|
1月前
|
存储 Python
Python学习笔记编程小哥令狐~持续更新、、、 (下)
Python学习笔记编程小哥令狐~持续更新、、、 (下)
33 1
|
1月前
|
存储 Python
【免费分享编程笔记】Python学习笔记(二)
【免费分享编程笔记】Python学习笔记(二)
42 0
【免费分享编程笔记】Python学习笔记(二)
下一篇
无影云桌面