socket编程过于底层, 编程虽然有套路,但是想要写出健壮的代码还是比较困难的,所以很多语言都对socket底层api进行封装,Python的封装就是——socketserver模块。它是网络服务编程框架,便于企业级快速开发。
类的继承关系
socketserver简化了网络服务器的编写。
他有四个同步类:TCPServer,UDPServer,UNIXStreamServer,UNIXDatagramServer。
2个mixin类,ForKingMIXIN.和ThreadingMixln类,用来支持异步。
class ForkingUDPServer(ForkingMixln,UDPServer):pass
class ForkingTCPServer(ForkingMixln,TCPServer):pass
class ThreadingUDPServer(ThreadingMixln,UDPServer):pass
class ThreadingTCPServer(ThreadingMixln,TCPServer):pass
fork创建多进程,thread是创建多线程。
编程接口
socketserver.BaseServer(server_address,RequestHandlerClass)
需要提供服务器绑定的地址信息,和用于处理请求的RequestHandlerClass类。
RequestHandlerClass类必须是BaseRequestHandler类的子类,在BaseServer中代码如下:
BaseRequestHandler类
它是和用户连接的用户请求处理类的基类,定义为BaseRequestHandler(request,client_address,server)
服务端server实例接收用户晴请求后,最后会实例化这个类。
它被初始化时,送入3个构造参数,request,client_address,server自身。
以后就可以在BaseRequestHandler类的实例上使用以下属性:
self.request是和客户端的链接的socket对象
self.server是TCPServer本身
self.client_address是客户端地址
这个类在初始化的时候,它会依次调用3个方法。子类可以覆盖这些方法。
import socketserver class MyHandler(socketserver.BaseRequestHandler): def handle(self): super().handle() #todo print("come") addr = ("127.0.0.1",9999) server = socketserver.ThreadingTCPServer(addr,MyHandler) server.serve_forever() server.server_close()
上面的代码运行起来,测试可以看到服务已经启动起来了。处于监听的状态。
再继续修改代码。
import socketserver import threading class MyHandler(socketserver.BaseRequestHandler): def setup(self): super().setup() self.event = threading.Event() def handle(self): super().handle() print(self.server,self.client_address,self.request) while not self.event.wait(1): print("come") addr = ("127.0.0.1",9999) server = socketserver.ThreadingTCPServer(addr,MyHandler) server.serve_forever() server.server_close() 结果为: <socketserver.ThreadingTCPServer object at 0x00000000026BA8D0> ('127.0.0.1', 55533) <socket.socket fd=212, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999), raddr=('127.0.0.1', 55533)>
在继续修改代码、
import socketserver import threading class MyHandler(socketserver.BaseRequestHandler): def setup(self): super().setup() self.event = threading.Event() def handle(self): super().handle() print(self.server,self.client_address,self.request) while not self.event.is_set(): data = self.request.recv(1024) print(data) addr = ("127.0.0.1",9999) server = socketserver.ThreadingTCPServer(addr,MyHandler) server.serve_forever() server.server_close()
上面的代码运行起来,由小工具发送数据就可以直接显示了。
import socketserver import threading class MyHandler(socketserver.BaseRequestHandler): def setup(self): super().setup() self.event = threading.Event() def handle(self): super().handle() print(self.server,self.client_address,self.request) while not self.event.is_set(): data = self.request.recv(1024) msg = "your msg = {}".format(data.decode()).encode() print(data) self.request.send(msg) addr = ("127.0.0.1",9999) server = socketserver.ThreadingTCPServer(addr,MyHandler) server.serve_forever() server.server_close()
上面就可以回消息了。上面可以连接几个客户端。
测试代码
import threading import socketserver class MyHandler(socketsever.BasequestHandler): def handler(self): #super().handle()可以不调用,父类handle什么都没有做 print("_"*30) print(self.server)#服务 print(self.request)#服务端复制客户端链接请求的socket对象 print(self.client_address)#客户端地址 print(self.__dict__) print(self.server.__dict__)#能看到负责accept的socket print(threading.enumerate()) print(threading.current_thread()) print("-"*30) addr = ("192.168.142.1",9999) server = socketserver.ThreadingTCPServer(addr,MyHandler) server.serve_forever()#永久
测试结果说明,handle方法相当于socket的recv方法。
每个不同的链接上的请求过来后,生成这个链接的socket对象即self.request,客户端渎职是self.client_address。
问题
测试过程中,上面代码,连接后立即断开了,为什么?
怎么样才能客户端和服务器端长时间连接
import threading import socketserver import logging logging.bacicConfig(level = logging.INFO,format = "%(asctime)s %(threadName)s %(thread)d %(message)s") class MyHandler(socketsever.BaseRequestHandler): def handler(self): #super().handle()可以不调用,父类handle什么都没有做 print("_"*30) print(self.server)#服务 print(self.request)#服务端复制客户端链接请求的socket对象 print(self.client_address)#客户端地址 print(self.__dict__) print(self.server.__dict__)#能看到负责accept的socket print(threading.enumerate()) print(threading.current_thread()) print("-"*30) for i in range(3): data = self.request.recv(1024) logging.info(data) logging.info("====end====") addr = ("192.168.142.1",9999) server = socketserver.ThreadingTCPServer(addr,MyHandler) server.serve_forever()#永久
将ThreadingTCPServer换成TCPServer,同时连接2个客户端观察效果。
ThreadingTCPServer是异步的,可以同时处理多个连接。
TCPServer是同步的,一个连接处理完了,即一个连接的handle方法执行完了,才能处理另一个连接,且只有主线程。
总结
创建服务器需要几个步骤
- 从BaseRequestHandler类派生出子类,并覆盖起handle方法来创建请求处理程序类,此方法将处理传入请求。
- 实例化一个服务器类,传参服务器的地址和请求处理类。
- 调用服务器实例的handle_request()或serve——forever()方法。
- 调用server_close()关闭套接字
实现EchoServer
顾名思义,Echo,来什么消息回显什么消息。
客户端发来什么消息,返回什么消息。
import threading from socketserver import ThreadingTCPServer,BaseRequestHandler import sys class EchoHandler(BaseRequestHandler): def setup(self): super().setup() self.event = threading.Event()#初始化工作 def finish(self): super().finish() self.event.set() def handle(self): super().handle() while not self.event.is_set(): data = self.request.recv(1024).decode() msg = "{} {}".format(self.client_address,data).encode() self.request.send(msg) print("end") addr = ("127.0.0.1",9999) server = ThreadingTCPServer(addr,EchoHandler) server_thread = threading.Thread(target=server.serve_forever,name="echoserver",daemon=True) server_thread.start() try: while True: cmd = input(">>>>>") if cmd.strip() =="quit": break print(threading.enumerate()) except Exception as e: print(e) except KeyboardInterrupt: pass finally: print("exit") sys.exit(0)
上面的代码启动起来,就可以回显消息了,主程序不会有任何显示。
#小工具结果 10:38:59 发送数据:123[1次] 10:38:59 收到数据:('127.0.0.1', 51338) 123 10:39:11 发送数据:xpffsw [1次] 10:39:11 收到数据:('127.0.0.1', 51338) xpffsw
练习——改写ChatServer
使用ThreadingTCPServer改写ChatServer
import threading from socketserver import ThreadingTCPServer,BaseRequestHandler import sys import logging FORMAT = "%(asctime)s %(threadName)s %(thread)d %(message)s" logging.basicConfig(format=FORMAT,level=logging.INFO) class ChatHandler(BaseRequestHandler): clients = {} def setup(self): super().setup() self.event = threading.Event()#初始工作 self.clients[self.client_address] = self.request def finish(self): super().finish()#清理工作 self.clients.pop(self.client_address)#能执行到吗? self.event.set() def handle(self): super().handle() while not self.event.is_set(): data = self.request.recv(1024).decode() if data == "quit": break msg = "{} {}".format(self.client_address,data).encode() logging.info(msg) for c in self.clients.values(): self.request.send(msg) print("end") addr =("0.0.0.0",9999) server = ThreadingTCPServer(addr,ChatHandler) server_thread = threading.Thread(target=server.serve_forever,name="chatserver",daemon=True) server_thread.start() try: while True: cmd = input(">>>") if cmd.strip()== "quit": break print(threading.enumerate()) except Exception as e: print(e) except KeyboardInterrupt: pass finally: print("exit") sys.exit(0)
问题,self.clients.pop(self.client_address)#能执行到吗?
如果连接的线程中handle方法中抛出异常,例如客户端主动断开导致的异常,线程崩溃,self.clients的pop方法还能执行吗?
当然能执行,基类源码保证了即使异常,也能执行finish方法。但不代表不应该捕获客户端各种异常。
解决客户端主动连接断开问题
import threading from socketserver import ThreadingTCPServer,BaseRequestHandler import sys import logging FORMAT = "%(asctime)s %(threadName)s %(thread)d %(message)s" logging.basicConfig(format=FORMAT,level=logging.INFO) class ChatHandler(BaseRequestHandler): clients = {} def setup(self): super().setup() self.event = threading.Event()#初始工作 self.clients[self.client_address] = self.request def finish(self): super().finish()#清理工作 self.clients.pop(self.client_address)#能执行到吗? self.event.set() def handle(self): super().handle() while not self.event.is_set(): data = self.request.recv(1024).decode() print(data,"____________________")#增加 if data == "quit": break msg = "{} {}".format(self.client_address,data).encode() logging.info(msg) for c in self.clients.values(): print("++++++++++++++")#增加 self.request.send(msg) print("end") addr =("0.0.0.0",9999) server = ThreadingTCPServer(addr,ChatHandler) server_thread = threading.Thread(target=server.serve_forever,name="chatserver",daemon=True) server_thread.start() try: while True: cmd = input(">>>") if cmd.strip()== "quit": break print(threading.enumerate()) except Exception as e: print(e) except KeyboardInterrupt: pass finally: print("exit") sys.exit(0)
通过打印可以看到,客户端主动断开,会导致recv方法立即返回一个空bytes,并没有同时抛出异常,当循环回到recv这一句的时候就会抛出异常。所以,可以通过判断data数据是否为空来客户端是否断开。
import threading from socketserver import ThreadingTCPServer,BaseRequestHandler import sys import logging FORMAT = "%(asctime)s %(threadName)s %(thread)d %(message)s" logging.basicConfig(format=FORMAT,level=logging.INFO) class ChatHandler(BaseRequestHandler): clients = {} def setup(self): super().setup() self.event = threading.Event()#初始工作 self.clients[self.client_address] = self.request def finish(self): super().finish()#清理工作 self.clients.pop(self.client_address)#能 执行到吗? self.event.set() def handle(self): super().handle() while not self.event.is_set() data = self.request.recv(1024).decode() print(data,"____________________")#增加 if not data or data == "quit": print("broken pipe") break msg = "{} {}".format(self.client_address,data).encode() logging.info(msg) for c in self.clients.values(): self.request.send(msg) print("end") addr =("0.0.0.0",9999) server = ThreadingTCPServer(addr,ChatHandler) server_thread = threading.Thread(target=server.serve_forever,name="chatserver",daemon=True) server_thread.start() try: while True: cmd = input(">>>") if cmd.strip()== "quit": break print(threading.enumerate()) except Exception as e: print(e) except KeyboardInterrupt: pass finally: print("exit") sys.exit(0)
上面的代码可以增加一个代码,处理一个异常。
总结:
为每个连接提供RequestHandlerclass类实例,一次调用setup,handle、finish方法,且使用了try……finally结构保证finish方法一定能被调用,这些方法依次执行完成,如果想维持这个连接和客户端通信,就需要在handle函数中使用循环。
socketserver模块提供的不同的类,但是编程接口都是一样的,即便是多进程,多线程的类也是一样,大大减少了编程的难度。