Python使用socketServer包搭建简易服务器过程详解

官方提供了socketserver包去方便咱们快速的搭建一个服务器框架。算法

server类
缓存

socketserver包提供5个Server类,这些单独使用这些Server类都只能完成同步的操做,他是一个单线程的,不能同时处理各个客户端的请求,只能按照顺序依次处理。安全

1
2
3
4
5
6
7
8
9
10
11
12
13
+------------+
| BaseServer |
+------------+
   |
   v
+-----------+    +------------------+
| TCPServer |------->| UnixStreamServer |
+-----------+    +------------------+
   |
   v
+-----------+    +--------------------+
| UDPServer |------->| UnixDatagramServer |
+-----------+    +--------------------+

两个Mixin类
bash

1
2
3
+--------------+    +----------------+
| ForkingMixIn |    | ThreadingMixIn |
+--------------+    +----------------+

各自实现了多进程和多线程的功能(ForkingMixIn在Windows不支持)服务器

因而将这些同步类和Mixin类组合就实现了异步服务类的效果。多线程

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

class ForkingUDPServer(ForkingMixIn, UDPServer): pass 
class ForkingTCPServer(ForkingMixIn, TCPServer): pass

异步

基本使用
socket

因为server须要同时处理来自多个客户端的请求,须要提供异步的支持,因此一般使用上面的异步类建立服务器。在Windows系统中没有提供os.fork()接口,Windows没法使用多进程的ForkingUDPServer和ForkingTCPServer,只能使用ThreadingTCPServer或者ThreadingUDPServer;而Linux和Unix多线程和多进程版本均可以使用。tcp

服务器主要负责接受客户端的链接请求,当一个新的客户端请求到来后,将分配一个新的线程去处理这个请求(异步服务器ThreadingTCPServer),而与客户端信息的交互则交给了专门的请求处理类(RequestHandlerClass)处理。

1
2
3
4
import socketserver
# 建立一个基于TCP的server对象,并使用BaseRequestHandler处理客户端发送的消息
server = socketserver.ThreadingTCPServer(( "127.0.0.1" , 8000 ), BaseRequestHandler)
server.serve_forever() # 启动服务器,

只须要上面两行代码就能够建立开启一个服务,运行上面代码后常看本机8000端口,发现有程序正在监听。

C:\Users\user>netstat -anp tcp | findstr 8000
TCP 127.0.0.1:8000 0.0.0.0:0 LISTENING

ThreadingTCPServer能够对咱们的请求进行接受,可是并不会进行处理请求,处理请求的类是上面指定BaseRequestHandler类,该类能够定义handle方法来处理接受的请求。

BaseRequestHandler的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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

在server = socketserver.ThreadingTCPServer(("127.0.0.1", 8000), BaseRequestHandler)中,BaseRequestHandler将做为参数绑定到服务器的实例上,服务器启动后,每当有一个新的客户端接接入服务器,将会实例化一个请求处理对象,并传入三个参数,request(链接客户端的socket)、client_address(远程客户端的地址)、server(服务器对象),执行init方法,将这三个参数保存到对应属性上。这个请求处理对象即可以与客户端交互了。

简单示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import socketserver
import threading
 
class MyRequestHandler(socketserver.BaseRequestHandler):
   """ BaseRequestHandler的实例化方法中,得到了三个属性
   self.request = request  # 该线程中与客户端交互的 socket 对象。
   self.client_address   # 该线程处理的客户端地址
   self.server = server   # 服务器对象
   """
 
   def handle( self ):
     while True :
       msg = self .request.recv()  # 接受客户端的数据
       if msg = = b "quit" or msg = = "": # 退出
         break
 
       print (msg.decode())
       self .request.send(msg) # 将消息发送回客户端
   def finish( self ):
     self .request.close()    # 关闭套接字
if __name__ = = "__main__" :
   # 建立一个基于TCP的server对象,并使用BaseRequestHandler处理客户端发送的消息
   server = socketserver.ThreadingTCPServer(( "127.0.0.1" , 8000 ), MyRequestHandler)
 
   server.serve_forever()  # 启动服务器

咱们建立了一个ThreadingTCPServer服务器,而后在传入的处理类MyRequestHandler,并在handle方法中提供与客户端消息交互的业务逻辑,此处只是将客户端的消息返回客户端。最后咱们在finish方法中关闭资源,finish方法使用了finally机制,保证了这些代码必定会执行。

上一篇使用socket实现了一个群聊服务器,这个里使用socketServer将更加方便的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class MyRequestHandle(BaseRequestHandler):
   clients = {} # 在类属性中记录全部与客户端链接socket。
   lock = threading.Lock() # 互斥锁,各个线程共用
 
   def setup( self ): # 新的用户链接时,预处理,将这个新的链接加入到clients中,考虑线程安全,须要加锁
     with self .lock:
       self .clients[ self .client_address] = self .request
 
   def handle( self ): # 处理客户端的请求主逻辑
     while True :
       data = self .request.recv( 1024 ).strip()  # 接受数据
 
       if data = = b "quit" or data = = b"": # 客户端退出
         with self .lock:
           self .server.clients.pop( self .client_address)
           self .request.close()
           break
 
       print ( "{}-{}: {}" . format ( * self .client_address, data.decode()))
 
       with self .lock:
         for _, c in self .server.clients.items(): # 群发
           c.send(data)
   def finish( self ):
     with server.lock:
       for _, c in server.clients.items():
         c.close()
     server.server_close() def main():
   server = ThreadingTCPServer(( "127.0.0.1" , 8000 ), MyRequestHandle)
   # 将建立的全部线程设置为daemon线程,这样控台主程序退出时,这个服务器的全部线程将会被结束
   server.daemon_threads = True
 
if __name__ = = "__main__" :
   main()

上面requestHandlerclass中的handle方法和finish方式对应了上一篇中TCP服务器的recv方法和stop方法,他们处理请求的逻辑是相同的。只是上面使用了socketserver的代码变少了,处理的逻辑也变少了,TCPserver帮咱们完成了大量的工做,这利于软件的快速开发。

内置的两个RequestHandlerClass

StreamHandlerRequest

StreamHandlerRequest顾名思义是一种流式的求情处理类,对应TCP协议的面向字节流的传输形式。咱们从源代码分析。(去除了一些次要代码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class StreamRequestHandler(BaseRequestHandler):
   rbufsize = - 1 # 读缓存
   wbufsize = 0  # 写缓存
   timeout = None # 超时时间
   # IP/TCP拥塞控制的Nagle算法算法。
   disable_nagle_algorithm = False
 
   def setup( self ): # 实现了setup,
     self .connection = self .request
     if self .timeout is not None :
       self .connection.settimeout( self .timeout)
     if self .disable_nagle_algorithm:
       self .connection.setsockopt(socket.IPPROTO_TCP,
                     socket.TCP_NODELAY, True )
     
     # 使用 makefile方法得到了一个只读文件对象 rfile
     self .rfile = self .connection.makefile( 'rb' , self .rbufsize)
     
     # 得到一个只写的文件对象 wfile
     if self .wbufsize = = 0 :
       self .wfile = _SocketWriter( self .connection)
     else :
       self .wfile = self .connection.makefile( 'wb' , self .wbufsize)
 
   def finish( self ): # 负责将这个 wfile 和 rfile方法关闭。
     if not self .wfile.closed:
       try :
         self .wfile.flush()
       except socket.error:
         pass
     self .wfile.close()
     self .rfile.close()

使用StreamRequestHandler方法能够将这个socket包装成一个类文件对象,方便咱们使用一套文件对象的方法处理这个socket,它没有实现handle方法,我仍然须要咱们实现。咱们能够这样使用它

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyHandle(StreamRequestHandler):
   # 若是须要使用setup和finish方法,须要调用父类方法,不然该方法将会被覆盖。
   def setup( self ):
     super ().setup()
     # 添加本身的需求
   def handle( self ):
     # 这里咱们可使用wfile和rfile来处理socket消息了,例如以前使用self.request.recv()方法等同于self.rfile.read()
     # 而 self.wfile.write 等同于 self.request.send(),在handle方法中完成业务逻辑便可
 
   def finish( self ):
     super ().finish()
 
server = ThreadingTCPServer( "127.0.0.1" , MyHandle)
server.serve_forever()

StreamRequestHandler主要定义了两个新的 wfile对象和rfile对象,来分别对这个socket进行读写操做,当咱们业务须要时,好比须要使用文件接口方法时,选择继承于StreamRequestHandler构建咱们本身处理请求类来完成业务逻辑将会更加的方便。

DatagramRequestHandler

DatagramRequestHandler字面意思是数据报请求处理,也就是基于UDPServer的服务器才能使用该请求处理类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class DatagramRequestHandler(BaseRequestHandler):
 
   def setup( self ):
     from io import BytesIO
     # udp的self.request包含两部分(data,socket)它来自于
     # data, client_addr = self.socket.recvfrom(self.max_packet_size)
     #   return (data, self.socket), client_addr
     # (data, self.socket)就是这个self.request,在这里将其解构,data为recvfrom接收的数据
     self .packet, self .socket = self .request
     
     # 该数据包封装为 BytesIO,一样为一个类文件对象。
     self .rfile = BytesIO( self .packet)
     self .wfile = BytesIO()
 
   def finish( self ):
     self .socket.sendto( self .wfile.getvalue(), self .client_address)

从源码能够看出,DatagramRequestHandler将数据包封装为一个rfile,并实例化一个ByteIO对象用于写入数据,写入的数据能够经过self.socket这个套接字发送。这样可使用rfile和wfile这两个类文件对象的read或者write接口来进行一些IO方面的操做。

本文出自https://www.jb51.net/article/188551.htm

相关文章
相关标签/搜索