socket编程过于底层,编程虽然有套路,但是想要写出健壮的代码还是比较困难的,所以很多语言都对socket底层 API进行封装,Python的封装就是——socketserver模块。它是网络服务编程框架,便于企业级快速开发。

  1. 类的继承关系

    +------------+
    | BaseServer |
    +------------+|v
    +-----------+        +------------------+
    | TCPServer |------->| UnixStreamServer |
    +-----------+        +------------------+|v
    +-----------+        +--------------------+
    | UDPServer |------->| UnixDatagramServer |
    +-----------+        +--------------------+
    
  2. SocketServer简化了网络服务器的编写

    • 它有4个同步类:

      1. TCPServer
      2. UDPServer
      3. UnixStreamServer
      4. UnixDatagramServer
    • 两个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不支持。
  • ThreadingUDPServer与ThreadingTCPServer类中的特有属性:
    1. daemon_threads=False #默认值是False表示创建的线程都不是daemon线程,改为True表示创建的所有线程都是daemon线程
    2. block_on_close=False #默认值为Fasle,如果为True,可以设置为守护线程3.7版本可以用

编程接口

  1. 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)
  1. 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类

  1. 是和用户连接的用户请求处理类的基类,
  2. BaseRequestHandler(request,client_address,server) #构造函数
    • request #是和客户端的连接的socket对象
    • client_address #是客户端地址
    • server #是TCPServer实例本身
  3. 服务端Server实例接收用户请求后,最后会实例化这个类。它被初始化时,送入3个构造参数:request, client_address, server自身 以后就可以在BaseRequestHandler类的实例上使用以下属性:
    • self.request是和客户端的连接的socket对象
    • self.server是TCPServer实例本身
    • self.client_address是客户端地址
  4. 这个类在初始化的时候,它会依次调用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方法执行完了,才能处理另一个连接,且只有主线程。

总结

创建服务器需要几个步骤:

  1. 从BaseRequestHandler类派生出子类,并覆盖其handle()方法来创建请求处理程序类,此方法将处理传入请求
  2. 实例化一个服务器类,传参服务器的地址和请求处理类
  3. 调用服务器实例的handle_request()或serve_forever()方法
  4. 调用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())

实战,使用socketserver实现群聊的server

使用ThreadingTCPServer改写ChatServer

使用BaseRequestHandler定义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.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())

总结

  1. 为每一个连接提供RequestHandlerClass类实例,依次调用setup、handle、finish方法,且使用了try…finally结构 保证finish方法一定能被调用。这些方法依次执行完成,如果想维持这个连接和客户端通信,就需要在handle函数 中使用循环。
  2. socketserver模块提供的不同的类,但是编程接口是一样的,即使是多进程、多线程的类也是一样,大大减少了编 程的难度。
  3. 将socket编程简化,只需要程序员关注数据处理本身,实现Handler类就行了。这种风格在Python十分常见。

python3 socketserver模块 网络服务编程框架相关推荐

  1. 高性能并发TCP网络服务-IOCP框架修正VC2008版本

    From: http://blog.csdn.net/lsfa1234/article/details/6223635 高性能并发TCP网络服务IOCP框架修正VC2008版本 从Source Cod ...

  2. 学习C++项目—— 搭建多进程网络服务框架,增加业务和日志,心跳机制

    学习计算机网络编程 一.思路和学习方法   本文学习于:C语言技术网(www.freecplus.net),在 b 站学习于 C 语言技术网,并加以自己的一些理解和复现,如有侵权会删除.   接下来对 ...

  3. python模块介绍- SocketServer 网络服务框架

    转载自http://my.oschina.net/u/1433482/blog/190612 摘要 SocketServer简化了网络服务器的编写.它有4个类:TCPServer,UDPServer, ...

  4. python socket tcp客户端_python网络编程socketserver模块(实现TCP客户端/服务器)

    摘录python核心编程 socketserver(python3.x版本重新命名)是标准库中的网络编程的高级模块.通过将创建网络客户端和服务器所必须的代码封装起来,简化了模板,为你提供了各种各样的类 ...

  5. python3 socketserver_《Python核心编程(第3版)》——2.5 *SocketServer模块

    本节书摘来自异步社区<Python核心编程(第3版)>一书中的第2章,第2.5节,作者[美] Wesley Chun(卫斯理 春),孙波翔 李斌 李晗 译,更多章节内容可以访问云栖社区&q ...

  6. python网络编程-异常处理-异常捕获-抛出异常-断言-自定义异常-UDP通信-socketserver模块应用-03

    python网络编程-异常处理-异常捕获-抛出异常-断言-自定义异常-UDP通信-socketserver模块应用-03 参考文章: (1)python网络编程-异常处理-异常捕获-抛出异常-断言-自 ...

  7. 网络编程进阶---- hamc模块 socketserver模块验证合法性 两者进行通信连接

    我们在工作中经常遇到,你公司内的某一台电脑要去访问你的服务器或者一个服务端电脑,那么你是让每一台都进行连接吗?  那不可能的  你肯定要进行限定的 验证客户端链接的合法性: hamc模块 hamc也是 ...

  8. Python之网络编程(socketserver模块详解、链接认证)

    文章目录 多线程对多客户端 socketserver 分类(2种) 12个类的继承关系(原理) socketserver的实现效果 认证链接的合法性 加盐(hmac) 对暗号 socketserver ...

  9. 网络编程中客户端链接的合法性,socketserver模块

    客户端链接的合法性 验证合法性: 首先,我们来探讨一下,什么叫验证合法性, 举个例子:有一天,我开了一个socket服务端,只想让咱们这个班的同学使用,但是有一天,隔壁班的同学过来问了一下我开的这个服 ...

最新文章

  1. sqlserver 2014使用时有Cannot find one or more components
  2. 十二张图详解Redis的数据结构和对象系统
  3. Soft robotics:造仿生昆虫机器人柔性骨骼新技术,只需2小时,成本不到7块!
  4. android开发 java与c# 兼容AES加密
  5. 【Visual C++】一些开发心得与调试技巧
  6. Java类加载器(一)——类加载器层次与模型
  7. docker容器mysql头文件_在Docker容器中使用MySQL数据库
  8. 项目部署到tomcat6.0启动成功后访问页面报500_.net core IIS部署教程
  9. 【剑指offer】面试题47:礼物的最大价值(Java)
  10. solr索引大小对比
  11. 深入理解加密、解密、数字签名和数字证书
  12. ps保存psd后图层全没了_ps保存成psd格式后,再打开就是一张图片而不显图层,怎么办啊??急!!...
  13. SSD和FTL概述:SSD主控维护FTL
  14. 关于Error:Execution failed for task ':app:transformClassesWithDexForDebug'的解决方法
  15. 2020李宏毅学习笔记——14.Convolution Netural Network
  16. 微信小程序——从后台获取数据库数据并在前台显示(前端+后端+数据库)
  17. Gerrit代码审计系统实战-Gerrit 2.15.14版本快速搭建
  18. 蓝牙耳机选哪个?盘点双十二必入的高性价比蓝牙耳机
  19. 直播回放:快速上手,使用 Kotlin 把支付宝小程序装进自己的 App
  20. C51模拟PS2键盘(二)

热门文章

  1. 用 C 语言开发一门编程语言 — 异常处理
  2. Windwos 08R2_DNS+AD安装图文
  3. Arduino方式开发ESP32笔记:使用Preferences保存数据
  4. 英国工会联盟报告:机器人技术和AI让三分之一的人失业
  5. 如何在linux下修改组权限
  6. maven中pom.xml中依赖包代码的查询地址
  7. zabbix配置外部邮件服务器进行邮件报警
  8. 如何使用Openssl 制作CA证书
  9. 在SQL和ERWIN中用自定义类型、规则和默认值实现check约束从而保证数据的完整性...
  10. CEGUI中文处理(补)