Socket网络编程-SocketServer
作者:尹正杰
版权声明:原创作品,谢绝转载!否则将追究法律责任。
一.SocketServer概述
socket编程过于底层,编程虽然有套路,但是想要写出健壮的代码还是比较困难的,所以很多语言都对 socket底层API进行封装,Python的封装就是socketserver模块。它是网络服务编程框架,便于企业级 快速开发。 类的继承关系如下所示: +------------+ | BaseServer | +------------+ | | v +-----------+ +--------------------+ | UDPServer |------->| UnixDatagramServer | +-----------+ +--------------------+ v +-----------+ +------------------+ | TCPServer |------->| UnixStreamServer | +-----------+ +------------------+ SocketServer简化了网络服务器的编写。 它有4个同步类: TCPServer UDPServer UnixStreamServer UnixDatagramServer。 2个Mixin类:ForkingMixIn 和 ThreadingMixIn 类,用来支持异步。由此得到 class ForkingUDPServer(ForkingMixIn, UDPServer): pass class ForkingTCPServer(ForkingMixIn, TCPServer): pass class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass class ThreadingTCPServer(ThreadingMixIn, TCPServer): pass fork是创建多进程,thread是创建多线程。 fork需要操作系统支持,Windows不支持。
二.编程接口
1>.创建服务器需要几个步骤
从BaseRequestHandler类派生出子类,并覆盖其handle()方法来创建请求处理程序类,此方法将 处理传入请求
实例化一个服务器类,传参服务器的地址和请求处理类
调用服务器实例的handle_request()或serve_forever()方法
调用server_close()关闭套接字
2>.案例展示
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 7 import threading 8 import socketserver 9 import logging 10 11 FORMAT = "%(asctime)s %(threadName)s %(thread)d %(message)s" 12 logging.basicConfig(format=FORMAT, level=logging.INFO) 13 14 """ 15 BaseRequestHandler: 16 def __init__(self, request, client_address, server): 17 self.request = request 18 self.client_address = client_address 19 self.server = server 20 self.setup() 21 try: 22 self.handle() 23 finally: 24 self.finish() 25 26 参数说明: 27 它是和用户连接的用户请求处理类的基类 28 服务端Server实例接收用户请求后,最后会实例化这个类。 29 它被初始化时,送入3个构造参数:request, client_address, server自身 30 以后就可以在BaseRequestHandler类的实例上使用以下属性: 31 self.request是和客户端的连接的socket对象 32 self.server是TCPServer实例本身 33 self.client_address是客户端地址 34 这个类在初始化的时候,它会依次调用3个方法。子类可以覆盖这些方法。 35 """ 36 class MyHandler(socketserver.BaseRequestHandler): 37 def handle(self): 38 # super().handle() #可以不调用,父类handle什么都没有做 39 print('-'*30) 40 print(self.server) #服务 41 print(self.request) #服务端负责客户端连接请求的socket对象 42 print(self.client_address) #客户端地址 43 print(self.__dict__) 44 print(self.server.__dict__) #能看到负责accept的socket 45 print(threading.enumerate()) 46 print(threading.current_thread()) 47 print('-'*30) 48 for i in range(3): 49 data = self.request.recv(1024) 50 logging.info(data) 51 logging.info('====end====') 52 53 addr = ('172.30.1.2', 9999) 54 55 """ 56 将ThreadingTCPServer换成TCPServer,同时连接2个客户端观察效果。 ThreadingTCPServer是异步的,可以同时处理多个连接。 57 TCPServer是同步的,一个连接处理完了,即一个连接的handle方法执行完了,才能处理另一个连接, 且只有主线程。 58 """ 59 server = socketserver.ThreadingTCPServer(addr, MyHandler) #注意参数是MyHandler类 60 server.serve_forever() #永久循环执行
三.实现EchoServer(顾名思义,Echo,来什么消息回显什么消息 客户端发来什么信息,返回什么信息)
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 7 import threading 8 import socketserver 9 10 class Handler(socketserver.BaseRequestHandler): 11 def setup(self): 12 super().setup() 13 self.event = threading.Event() 14 15 def finish(self): 16 super().finish() 17 self.event.set() 18 19 def handle(self): 20 super().handle() 21 print('-' * 30) 22 while not self.event.is_set(): 23 data = self.request.recv(1024).decode() 24 print(data) 25 msg = '{} {}'.format(self.client_address, data).encode() 26 self.request.send(msg) 27 28 server = socketserver.ThreadingTCPServer(('172.30.1.2', 9999), Handler) 29 print(server) 30 threading.Thread(target=server.serve_forever, name='EchoServer', daemon=True).start() 31 32 while True: 33 cmd = input('>>') 34 if cmd == 'quit': 35 server.server_close() 36 break 37 print(threading.enumerate())
四.实战—改写ChatServer
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 7 import datetime 8 import threading 9 from socketserver import ThreadingTCPServer, StreamRequestHandler 10 import logging 11 12 FORMAT = "%(asctime)s %(threadName)s %(thread)d %(message)s" 13 logging.basicConfig(format=FORMAT, level=logging.INFO) 14 15 """ 16 注意:此程序线程不安全 17 """ 18 class ChatHandler(StreamRequestHandler): 19 clients = {} 20 21 def setup(self): 22 super().setup() 23 self.event = threading.Event() 24 self.clients[self.client_address] = self.wfile 25 26 def handle(self): 27 super().handle() 28 # for k,v in self.__dict__.items(): 29 # print(k, type(v), v) 30 31 while not self.event.is_set(): 32 data = self.rfile.read1(1024) # 可以读取到数据 33 data = data.decode().rstrip() 34 print(data, '~~~~~~~~~~~~~') 35 36 if data == 'quit' or data == '': # 主动退出和断开 37 break 38 39 msg = '{} {}:{} {}'.format(datetime.datetime.now(), *self.client_address,data) 40 41 for f in self.clients.values(): 42 f.write(msg.encode()) 43 f.flush() 44 45 def finish(self): 46 self.clients.pop(self.client_address) 47 super().finish() 48 self.event.set() 49 50 server = ThreadingTCPServer(('172.30.1.2', 9999), ChatHandler) 51 server.daemon_threads = True # 让所有启动线程都为daemon 52 53 threading.Thread(target=server.serve_forever, name='chatserver', daemon=True).start() 54 55 while True: 56 cmd = input('>>') 57 if cmd.strip() == 'quit': 58 server.server_close() 59 break 60 print(threading.enumerate())
五.总结
为每一个连接提供RequestHandlerClass类实例,依次调用setup、handle、finish方法,且使用了try...finally结构保证finish方法一定能被调用。这些方法依次执行完成,如果想维持这个连接和客户端 通信,就需要在handle函数中使用循环。
socketserver模块提供的不同的类,但是编程接口是一样的,即使是多进程、多线程的类也是一样,大 大减少了编程的难度。 将socket编程简化,只需要程序员关注数据处理本身,实现Handler类就行了。这种风格在Python十分常见。