python3 socketserver模块 网络服务编程框架
socket编程过于底层,编程虽然有套路,但是想要写出健壮的代码还是比较困难的,所以很多语言都对socket底层 API进行封装,Python的封装就是——socketserver模块。它是网络服务编程框架,便于企业级快速开发。
类的继承关系
+------------+ | BaseServer | +------------+|v +-----------+ +------------------+ | TCPServer |------->| UnixStreamServer | +-----------+ +------------------+|v +-----------+ +--------------------+ | UDPServer |------->| UnixDatagramServer | +-----------+ +--------------------+
SocketServer简化了网络服务器的编写
- 它有4个同步类:
- TCPServer
- UDPServer
- UnixStreamServer
- UnixDatagramServer
- 两个Mixin类,用来支持异步。
- ForkingMixIn
- ThreadingMixIn
- 组合得到
- class ForkingUDPServer(ForkingMixIn,UDPServer):pass
- class ForkingTCPServer(ForkingMixIn,TCPServer):pass
- class ThreadingUDPServer(ThreadingMixIn,UDPServer):pass
- class ThreadingTCPServer(ThreadingMixIn,TCPServer):pass
- 它有4个同步类:
- fork是创建多进程,thread是创建多线程。
- fork需要操作系统支持,Windows不支持。
- ThreadingUDPServer与ThreadingTCPServer类中的特有属性:
- daemon_threads=False #默认值是False表示创建的线程都不是daemon线程,改为True表示创建的所有线程都是daemon线程
- block_on_close=False #默认值为Fasle,如果为True,可以设置为守护线程3.7版本可以用
编程接口
socketserver.BaseServer(server_address,RequestHandlerClass) #实例化一个服务器
- server_address #服务器绑定的地址信息,是一个元组(ip,prost)
- RequestHandlerClass #必须是BaseRequestHandler的一个子类。
- 在BaseServer中原码代码如下:
def __init__(self, server_address, RequestHandlerClass):"""Constructor. May be extended, do not override."""self.server_address = server_addressself.RequestHandlerClass = RequestHandlerClassself.__is_shut_down = threading.Event()self.__shutdown_request = False# 处理请求的方法,会实例化一个RequestHandlerClass对象
def finish_request(self, request, client_address):"""Finish one request by instantiating RequestHandlerClass."""self.RequestHandlerClass(request, client_address, self)
BaseServer中定义的接口常用属性和方法
方法 | 含义 |
---|---|
server_address | 服务器正在监听的地址和端口,在不同协议格式不一样。Internet协议上是一个元组(“127.0.0.1”,80) |
socket | 服务器正在监听的套接字对象。socket |
request_queue_size | 请求队列的大小。如果处理单个请求需要很长时间,那么在服务器繁忙时到达的任何请求都会被放入队列中,直到request_queue_size请求为止。一旦队列满了,来自客户机的进一步请求将得到一个“连接被拒绝”错误。默认值通常是5,但是可以被子类覆盖。 |
address_family | 服务器套接字所属的协议族。常见的例子是套接字。AF_INET socket.AF_UNIX。 |
socket_type | 服务器使用的套接字类型;套接字。SOCK_STREAM套接字。SOCK_DGRAM是两个常见的值。 |
timeout | 超时持续时间,以秒为单位度量,如果不需要超时,则为None。如果handle_request()在超时期间没有收到传入的请求,则调用handle_timeout()方法。 |
handle_request() |
处理单个请求,同步执行 这个函数按顺序调用以下方法:get_request()、verify_request()和process_request()。如果处理程序类的用户提供的handle()方法引发异常,将调用服务器的handle_error()方法。如果在超时秒内没有收到任何请求,那么将调用handle_timeout()并返回handle_request()。 |
server_forever(poll_interval=0.5) |
异步执行,处理请求。每隔poll_interval秒轮询一次。 忽略timeout属性,还会调用service_actions(),在ForkingMixIn的子类中定义,可以用来清理僵尸进程。 |
shutdown() | 告诉serve_forever循环停止。并等待他结束。 |
server_close() | 关闭服务器 |
finish_request(request,client_address) | 通过实例化RequestHandlerClass并调用它的handle()方法来处理请求 |
server_bind() | 由服务器的构造函数调用,以将套接字绑定到所需的地址。可能会被覆盖。 |
verify_request(request,client_address) | 必须返回一个布尔值;如果值为True,请求将被处理,如果值为False,请求将被拒绝。可以重写此函数来实现服务器的访问控制。默认实现总是返回True。 |
BaseRequestHandler类
- 是和用户连接的用户请求处理类的基类,
BaseRequestHandler(request,client_address,server)
#构造函数- request #是和客户端的连接的socket对象
- client_address #是客户端地址
- server #是TCPServer实例本身
- 服务端Server实例接收用户请求后,最后会实例化这个类。它被初始化时,送入3个构造参数:request, client_address, server自身 以后就可以在BaseRequestHandler类的实例上使用以下属性:
- self.request是和客户端的连接的socket对象
- self.server是TCPServer实例本身
- self.client_address是客户端地址
- 这个类在初始化的时候,它会依次调用3个方法。子类可以覆盖这些方法。
class BaseRequestHandler:def __init__(self, request, client_address, server):self.request = requestself.client_address = client_addressself.server = serverself.setup()try:self.handle()finally:self.finish()def setup(self): #每一个连接初始化passdef handle(self): #每一次请求处理passdef finish(self): #每一个连接清理pass
测试代码
import socketserver
import socketclass MyBaseRequestHandle(socketserver.BaseRequestHandler):def setup(self):super().setup() #可以不调用父类的setup()方法,父类的setup方法什么都没做print("----setup方法被执行-----")def handle(self):super().handle() #可以不调用父类的handler(),方法,父类的handler方法什么都没做print("-------handler方法被执行----")print(self.server)print(self.request) #服务print(self.client_address) #客户端地址print(self.__dict__)print("- "*30)print(self.server.__dict__)print("- "*30)sk:socket.socket = self.requestdata = sk.recv(1024)print(data)sk.send("{}-{}".format(sk.getpeername(),data).encode())print("----------handler end ----------")def finish(self):super().finish() #可以不调用父类的finish(),方法,父类的finish方法什么都没做print("--------finish方法被执行---")laddr = "127.0.0.1",3999
tcpserver = socketserver.TCPServer(laddr,MyBaseRequestHandle) #注意:参数是MyBaseRequestHandle
tcpserver.handle_request() #只接受一个客户端连接
# tcpserver.serve_forever() #永久循环执行,可以接受多个客户端连接
每个不同的连接上的请求过来后,生成这个连接的socket对象即self.request,客户端地址是self.client_address。
将ThreadingTCPServer换成TCPServer,当每个客户端连接进来就会创建一个新的线程。
ThreadingTCPServer是异步的,可以同时处理多个连接。
TCPServer是同步的,一个连接处理完了,即一个连接的handle方法执行完了,才能处理另一个连接,且只有主线程。
总结
- 从BaseRequestHandler类派生出子类,并覆盖其handle()方法来创建请求处理程序类,此方法将处理传入请求
- 实例化一个服务器类,传参服务器的地址和请求处理类
- 调用服务器实例的handle_request()或serve_forever()方法
- 调用server_close()关闭套接字
实现EchoServer
Echo:来声明消息,回显什么消息,即客户端发来什么消息,返回什么消息
import logging
import sys
import socketserver
import socket
import threadinglogging.basicConfig(format="%(asctime)s %(thread)d %(threadName)s %(message)s",stream=sys.stdout,level=logging.INFO)class Handler(socketserver.BaseRequestHandler):def setup(self):super().setup()self.event = threading.Event()logging.info("新加入了一个连接{}".format(self.client_address))def handle(self):super().handle()sk:socket.socket = self.requestwhile not self.event.is_set():try:data = sk.recv(1024).decode()except Exception as e:logging.info(e)breaklogging.info(data)msg = "{}-{}".format(self.client_address,data).encode()sk.send(msg)def finish(self):super().finish()self.event.set()self.request.close()if __name__ == "__main__":server = socketserver.ThreadingTCPServer(("127.0.0.1",3999),Handler)threading.Thread(target=server.serve_forever,name="server").start()while True:cmd = input(">>>")if cmd.strip() == "quit":server.server_close()breaklogging.info(threading.enumerate())
使用ThreadingTCPServer改写ChatServer
import logging
import sys
import socketserver
import socket
import threadinglogging.basicConfig(format="%(asctime)s %(thread)d %(threadName)s %(message)s",stream=sys.stdout,level=logging.INFO)
log = logging.getLogger()class Handler(socketserver.BaseRequestHandler):lock = threading.Lock()clients = {}def setup(self):super().setup()self.event = threading.Event()with self.lock:self.clients[self.client_address] = self.requestlog.info("新加入了一个连接{}".format(self.client_address))def handle(self):super().handle()sock:socket.socket = self.requestwhile not self.event.is_set():try:data = sock.recv(1024)except Exception as e:log.error(e)data = b""log.info(data)if data == b"by" or data == b"":breakmsg = "service:{}-->{}".format(self.client_address, data).encode()expc = [] # 记录sock出错时对应的clientswith self.lock:for c, sk in self.clients.items():try:sk.send(msg) # 可能在发送消息是就出错except:expc.append(c)for c in expc:self.clients.pop(c)def finish(self):super().finish()self.event.set()with self.lock:if self.client_address in self.clients:self.clients.pop(self.client_address)self.request.close()log.info("{}退出了".format(self.client_address))if __name__ == "__main__":server = socketserver.ThreadingTCPServer(("127.0.0.1",3999),Handler)server.daemon_threads = True #设置所有创建的线程都为Daemo线程threading.Thread(target=server.serve_forever,name="server",daemon=True).start()while True:cmd = input(">>>")if cmd.strip() == "quit":server.shutdown() #告诉serve_forever循环停止。server.server_close()breaklogging.info(threading.enumerate())
使用StreamRequestHandler定义handler
import logging
import sys
import socketserver
import socket
import threadinglogging.basicConfig(format="%(asctime)s %(thread)d %(threadName)s %(message)s",stream=sys.stdout,level=logging.INFO)
log = logging.getLogger()class Handler(socketserver.StreamRequestHandler):lock = threading.Lock()clients = {}def setup(self):super().setup()self.event = threading.Event()with self.lock:self.clients[self.client_address] = self.requestlog.info("新加入了一个连接{}".format(self.client_address))def handle(self):super().handle()import iorfile:io.TextIOWrapper= self.rfilewhile not self.event.is_set():try:data = rfile.read1(1024) #类似于sock.recv(1024)# data = rfile.readline() #行读取except Exception as e:log.error(e)data = b""log.info(data)if data == b"by" or data == b"":breakmsg = "service:{}-->{}".format(self.client_address, data).encode()expc = [] # 记录sock出错时对应的clientswith self.lock:for c, sk in self.clients.items():try:sk.send(msg) # 可能在发送消息是就出错except:expc.append(c)for c in expc:self.clients.pop(c)def finish(self):super().finish()self.event.set()with self.lock:if self.client_address in self.clients:self.clients.pop(self.client_address)self.request.close()log.info("{}退出了".format(self.client_address))if __name__ == "__main__":server = socketserver.ThreadingTCPServer(("127.0.0.1",3999),Handler)server.daemon_threads = True #设置所有创建的线程都为Daemo线程threading.Thread(target=server.serve_forever,name="server",daemon=True).start()while True:cmd = input(">>>")if cmd.strip() == "quit":server.shutdown() #告诉serve_forever循环停止。server.server_close()breaklogging.info(threading.enumerate())
- 为每一个连接提供RequestHandlerClass类实例,依次调用setup、handle、finish方法,且使用了try…finally结构 保证finish方法一定能被调用。这些方法依次执行完成,如果想维持这个连接和客户端通信,就需要在handle函数 中使用循环。
- socketserver模块提供的不同的类,但是编程接口是一样的,即使是多进程、多线程的类也是一样,大大减少了编 程的难度。
- 将socket编程简化,只需要程序员关注数据处理本身,实现Handler类就行了。这种风格在Python十分常见。
python3 socketserver模块 网络服务编程框架相关推荐
- 高性能并发TCP网络服务-IOCP框架修正VC2008版本
From: http://blog.csdn.net/lsfa1234/article/details/6223635 高性能并发TCP网络服务IOCP框架修正VC2008版本 从Source Cod ...
- 学习C++项目—— 搭建多进程网络服务框架,增加业务和日志,心跳机制
学习计算机网络编程 一.思路和学习方法 本文学习于:C语言技术网(www.freecplus.net),在 b 站学习于 C 语言技术网,并加以自己的一些理解和复现,如有侵权会删除. 接下来对 ...
- python模块介绍- SocketServer 网络服务框架
转载自http://my.oschina.net/u/1433482/blog/190612 摘要 SocketServer简化了网络服务器的编写.它有4个类:TCPServer,UDPServer, ...
- python socket tcp客户端_python网络编程socketserver模块(实现TCP客户端/服务器)
摘录python核心编程 socketserver(python3.x版本重新命名)是标准库中的网络编程的高级模块.通过将创建网络客户端和服务器所必须的代码封装起来,简化了模板,为你提供了各种各样的类 ...
- python3 socketserver_《Python核心编程(第3版)》——2.5 *SocketServer模块
本节书摘来自异步社区<Python核心编程(第3版)>一书中的第2章,第2.5节,作者[美] Wesley Chun(卫斯理 春),孙波翔 李斌 李晗 译,更多章节内容可以访问云栖社区&q ...
- python网络编程-异常处理-异常捕获-抛出异常-断言-自定义异常-UDP通信-socketserver模块应用-03
python网络编程-异常处理-异常捕获-抛出异常-断言-自定义异常-UDP通信-socketserver模块应用-03 参考文章: (1)python网络编程-异常处理-异常捕获-抛出异常-断言-自 ...
- 网络编程进阶---- hamc模块 socketserver模块验证合法性 两者进行通信连接
我们在工作中经常遇到,你公司内的某一台电脑要去访问你的服务器或者一个服务端电脑,那么你是让每一台都进行连接吗? 那不可能的 你肯定要进行限定的 验证客户端链接的合法性: hamc模块 hamc也是 ...
- Python之网络编程(socketserver模块详解、链接认证)
文章目录 多线程对多客户端 socketserver 分类(2种) 12个类的继承关系(原理) socketserver的实现效果 认证链接的合法性 加盐(hmac) 对暗号 socketserver ...
- 网络编程中客户端链接的合法性,socketserver模块
客户端链接的合法性 验证合法性: 首先,我们来探讨一下,什么叫验证合法性, 举个例子:有一天,我开了一个socket服务端,只想让咱们这个班的同学使用,但是有一天,隔壁班的同学过来问了一下我开的这个服 ...
最新文章
- sqlserver 2014使用时有Cannot find one or more components
- 十二张图详解Redis的数据结构和对象系统
- Soft robotics:造仿生昆虫机器人柔性骨骼新技术,只需2小时,成本不到7块!
- android开发 java与c# 兼容AES加密
- 【Visual C++】一些开发心得与调试技巧
- Java类加载器(一)——类加载器层次与模型
- docker容器mysql头文件_在Docker容器中使用MySQL数据库
- 项目部署到tomcat6.0启动成功后访问页面报500_.net core IIS部署教程
- 【剑指offer】面试题47:礼物的最大价值(Java)
- solr索引大小对比
- 深入理解加密、解密、数字签名和数字证书
- ps保存psd后图层全没了_ps保存成psd格式后,再打开就是一张图片而不显图层,怎么办啊??急!!...
- SSD和FTL概述:SSD主控维护FTL
- 关于Error:Execution failed for task ':app:transformClassesWithDexForDebug'的解决方法
- 2020李宏毅学习笔记——14.Convolution Netural Network
- 微信小程序——从后台获取数据库数据并在前台显示(前端+后端+数据库)
- Gerrit代码审计系统实战-Gerrit 2.15.14版本快速搭建
- 蓝牙耳机选哪个?盘点双十二必入的高性价比蓝牙耳机
- 直播回放:快速上手,使用 Kotlin 把支付宝小程序装进自己的 App
- C51模拟PS2键盘(二)