1 socket编程弊端

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

2 SocketServer模块

SocketServer,网络通信服务器,是Python标准库中的一个高级模块,其作用是创建网络服务器。该模块简化了编写网络服务器的任务。

2.1 服务器类

SocketServer模块中定义了五种服务器类。

  • BaseServer(server_address, RequestHandlerClass):服务器的基类,定义了API。
  • TCPServer(server_address, RequestHandlerClass, bind_and_activate=True):使用TCP/IP套接字。
  • UDPServer:使用数据报套接字
  • UnixStreamServer:使用UNIX套接字,只适用UNIX平台
  • UnixDatagramServer:使用UNIX套接字,只适用UNIX平台

它们的继承关系:

        +------------+
| BaseServer |
+------------+
|
v
+-----------+ +------------------+
| TCPServer |------->| UnixStreamServer |
+-----------+ +------------------+
|
v
+-----------+ +--------------------+
| UDPServer |------->| UnixDatagramServer |
+-----------+ +--------------------+

除了基类为抽象基类意外,其他四个类都是同步阻塞的。

2.2 Mixin类

SocketServer模块中还提供了一些Mixin类,用于和同步类组成多线程或者多进程的异步方式。

  • class ForkingUDPServer(ForkingMixIn, UDPServer)
  • class ForkingTCPServer(ForkingMixIn, TCPServer)
  • class ThreadingUDPServer(ThreadingMixIn, UDPServer)
  • class ThreadingTCPServer(ThreadingMixIn, TCPServer)
  • class ThreadingUnixStreamServer(ThreadingMixIn, UnixStreamServer)
  • class ThreadingUnixDatagramServer(ThreadingMixIn, UnixDatagramServer)

fork是创建多进程,thread是创建多线程。fork需要操作系统支持,Windows不支持。

2.3 RequestHandlerClass是啥

要想使用服务器类,需要传入一个RequestHandlerClass类,为什么要传入这个RequestHandlerClass类,它是干什么的?下面一起来看看源码:

  1. TCPServer入口
# 446 行
def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True):
"""Constructor. May be extended, do not override."""
BaseServer.__init__(self, server_address, RequestHandlerClass) # 把RequestHandlerClass交给了BaseServer
self.socket = socket.socket(self.address_family,
self.socket_type)
  1. BaseServer
# 204 行
def __init__(self, server_address, RequestHandlerClass):
"""Constructor. May be extended, do not override."""
self.server_address = server_address
self.RequestHandlerClass = RequestHandlerClass # 将RequestHandlerClass当作类属性付给了实例本身
self.__is_shut_down = threading.Event()
self.__shutdown_request = False
  1. 开启的服务serve_forever方法
# 219 行
def serve_forever(self, poll_interval=0.5):
self.__is_shut_down.clear()
try:
... ...
if ready:
self._handle_request_noblock() # 这里执行了_handle_request_noblock()方法 self.service_actions()
finally:
self.__shutdown_request = False
self.__is_shut_down.set()
  1. _handle_request_noblock()方法
# 304行
def _handle_request_noblock(self):
... ...
try:
self.process_request(request, client_address) # 这里又调用了process_request方法
except Exception:
self.handle_error(request, client_address)
self.shutdown_request(request)
except:
self.shutdown_request(request)
raise
else:
self.shutdown_request(request)
  1. process_request方法
# 342 行
def process_request(self, request, client_address):
self.finish_request(request, client_address) # 这里又调用了finish_request方法
self.shutdown_request(request)
  1. finish_request方法
# 359 行
def finish_request(self, request, client_address):
"""Finish one request by instantiating RequestHandlerClass."""
self.RequestHandlerClass(request, client_address, self) # 实例化对象

通过观察源码,我们知道RequestHandlerClass接受了两个参数,根据上下文代码我们可知:

  1. request:客户端的socket连接
  2. client_address: 客户端的地址二元组信息

那么这个RequestHandlerClass到底是啥?

2.4 编程接口

        通过源码,我们可以知道,最后传入RequestHandlerClass的是socket和client的ip地址,是不是和我们前面写的TCP多线程版本的recv函数很想?没错,RequestHandlerClass在这里被称为编程接口,由于处理业务逻辑。

        但是RequestHandlerClass我们没有参照,该如何写呢? socketserver提供了抽象基类BaseRequestHandler,共我们继承来实现。

BaseRequestHandler 是和用户连接的用户请求处理类的基类,所以 RequestHandlerClass 必须是 BaseRequestHandler 的子类,

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

观察源码我们知道,setup、handler、finish在类初始化时就会被执行:

  1. setup():执行处理请求之前的初始化相关的各种工作。默认不会做任何事。
  2. handler():执行那些所有与处理请求相关的工作。默认也不会做任何事。
  3. finish():执行当处理完请求后的清理工作,默认不会做任何事。

这些参数到底是什么?

import socketserver

class MyRequestHandler(socketserver.BaseRequestHandler):
def handle(self):
print(self.server) # <socketserver.TCPServer object at 0x00000153270DA320>
print(self.client_address) # ('127.0.0.1', 62124)
print(self.request) # <socket.socket fd=296, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 999), raddr=('127.0.0.1', 62124)> s = socketserver.TCPServer(('127.0.0.1', 999), MyRequestHandler)
s.serve_forever()

根据上面的输出信息我们知道:

  • self.server:指代当前server本身
  • self.client_address:客户端地址
  • self.request: 和客户端连接的socket对象

我们总结一下,使用socketserver创建一个服务器需要以下几个步骤:

  1. 从BaseRequestHandler类派生出子类,并覆盖其handler()方法来创建请求处理程序类,此方法将处理传入请求。
  2. 实例化一个服务器类,传参服务器的地址和请求处理类
  3. 调用服务器实例的handler_request()或serve_forever()方法(启动服务)
  4. 调用server_close()方法(关闭服务)

3 实现EchoServer

顾名思义:Echo,即来什么消息就回显什么消息,即客户端发来什么消息,就返回什么消息。

import socketserver
import logging
import threading FORMAT = '%(asctime)s %(message)s'
logging.basicConfig(level=logging.INFO, format=FORMAT) class MyRequestHandler(socketserver.BaseRequestHandler): def setup(self): # 优先执行父类同名方法是一个很好的习惯,即便是父类不作任何处理
super(MyRequestHandler, self).setup()
self.event = threading.Event() def handle(self):
super(MyRequestHandler, self).handle()
while not self.event.is_set():
data = self.request.recv(1024)
if data == b'quit' or data == b'':
self.event.set()
break msg = '{}:{} {}'.format(*self.client_address, data.decode())
logging.info(msg)
self.request.send(msg.encode()) def finish(self):
super(MyRequestHandler, self).finish()
self.event.set() if __name__ == '__main__':
with socketserver.ThreadingTCPServer(('127.0.0.1', 9999), MyRequestHandler) as server: # 让所有启动的线程都为daemon进程
server.daemon_threads = True # serve_foreve会阻塞,所以交给子线程运行
threading.Thread(target=server.serve_forever, daemon=True).start()
while True:
cmd = input('>>>').strip()
if not cmd: continue
if cmd == 'quit':
server.server_close()
break
print(threading.enumerate())

ThreadingTCPServer是TCPServer的子类,它混合了ThreadingMixIn类使用多线程来接受客户端的连接。

4 聊天室

代码如下:

import socketserver
import logging
import threading FORMAT = '%(asctime)s %(message)s'
logging.basicConfig(level=logging.INFO, format=FORMAT) class ChatRequestHandler(socketserver.BaseRequestHandler):
clients = set() # 每个连接进来时的操作
def setup(self):
super(ChatRequestHandler, self).setup()
self.event = threading.Event()
self.lock = threading.Lock()
self.clients.add(self.request) # 每个连接的业务处理
def handle(self):
super(ChatRequestHandler, self).handle()
while not self.event.is_set(): # use Client code except
try:
data = self.request.recv(1024)
except ConnectionResetError:
self.clients.remove(self.request)
self.event.set()
break # use TCP tools except
if data.rstrip() == b'quit' or data == b'':
with self.lock:
self.clients.remove(self.request)
logging.info("{}:{} is down ~~~~~~".format(*self.client_address))
self.event.set()
break msg = "{}:{} {}".format(*self.client_address, data.decode()).encode()
logging.info('{}:{} {}'.format(*self.client_address, msg))
with self.lock:
for client in self.clients:
client.send(msg) # 每个连接关闭的时候,会执行
def finish(self): super(ChatRequestHandler, self).finish()
self.event.set()
self.request.close() class ChatTCPServer:
def __init__(self, ip, port):
self.ip = ip
self.port = port
self.sock = socketserver.ThreadingTCPServer((self.ip, self.port), ChatRequestHandler) def start(self):
self.sock.daemon_threads = True
threading.Thread(target=self.sock.serve_forever, name='server', daemon=True).start() def stop(self):
self.sock.server_close() if __name__ == '__main__':
cts = ChatTCPServer('127.0.0.1', 9999)
cts.start() while True:
cmd = input('>>>>:').strip()
if cmd == 'quit':
cts.stop()
if not cmd: continue
print(threading.enumerate())

使用TCP工具强制退出时会发送空串,而使用我们自己写的tcp client,则不会发送,所以这里所了两种异常的处理。socketserver只是用在服务端,客户端使用上一篇TCP client即可。

38 - 网络编程-socketserver的更多相关文章

  1. Socket网络编程-SocketServer

    Socket网络编程-SocketServer 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.SocketServer概述 socket编程过于底层,编程虽然有套路,但是想要写 ...

  2. python网络编程——SocketServer/Twisted/paramiko模块

    在之前博客C/S架构的网络编程中,IO多路复用是将多个IO操作复用到1个服务端进程中进行处理,即无论有多少个客户端进行连接请求,服务端始终只有1个进程对客户端进行响应,这样的好处是节省了系统开销(se ...

  3. 网络编程-socketserver

    网络编程使用socketserver,通常包括以下几步:一.定义类,并继承socketserver.BaseRequestHandler 二.重写handle方法 三.实例化TCPServer,并传递 ...

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

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

  5. 网络编程socketserver

    一.网络编程回顾 tcp是流式传输,字节流,数据与数据之间没有边界 优点:不限定长度,可靠传输 缺点:慢,和一端的通信连接conn会一直占用通信资源 udp协议式面向数据包的传输 优点:快,由于不需要 ...

  6. python基础(15)-socket网络编程&socketserver

    socket 参数及方法说明 初始化参数 sk = socket.socket(参数1,参数2,参数3) 参数1:地址簇 socket.AF_INET IPv4(默认) socket.AF_INET6 ...

  7. 【python】网络编程-SocketServer 实现客户端与服务器间非阻塞通信

    利用SocketServer模块来实现网络客户端与服务器并发连接非阻塞通信.首先,先了解下SocketServer模块中可供使用的类:BaseServer:包含服务器的核心功能与混合(mix-in)类 ...

  8. python网络编程-socketserver

    一:socketserver简化了网络服务器的编写. 它有4个类:TCPServer,UDPServer,UnixStreamServer,UnixDatagramServer. 这4个类是同步进行处 ...

  9. python网络编程--socketserver 和 ftp功能简单说明

    1. socketserver 我们之前写的tcp协议的socket是不是一次只能和一个客户端通信,如果用socketserver可以实现和多个客户端通信.它是在socket的基础上进行了一层封装,也 ...

随机推荐

  1. BZOJ 1237 配对(DP)

    给出两个长度为n的序列.这两个序列的数字可以连边当且仅当它们不同,权值为它们的绝对值,求出这个二分图的最小权值完全匹配.没有输出-1. n<=1e5.用KM会TLE+MLE. 如果连边没有限制的 ...

  2. CRM 建设方案(01):CRM基础

    CRM 客户关系管理系统基础 客户关系管理简称CRM(Customer Relationship Management).CRM概念引入中国已有数年,其字面意思是客户关系管理,但其深层的内涵却有着许多 ...

  3. [TJOI2008]彩灯 线性基

    题面 题面 题解 题意:给定n个01串,求互相异或能凑出多少不同的01串. 线性基的基础应用. 对于线性基中的01串,如果我们取其中一些凑成一个新的01串,有一个重要的性质:任意2个不同方案凑出的01 ...

  4. Netsh命令-网络禁用开启

    禁用无线网卡:netsh interface set interface wlan0 disabled 启用无线网卡:netsh interface set interface wlan0 enabl ...

  5. SCOI2014极水的题解- -

    话说SCOI都考了1个月了,终于拿出决心把题解补完了,但都说了是极水的题解,大家就看着玩吧- - DAY1 T1:目标是找最长不降子序列,先就有一个比较显然的结论,就是假如我们要拔高区间[L, R], ...

  6. C中 ->运算符说明

    岁数大了,记忆力不好!这里记下,以后忘了来查! ->运算符. 访问结构中的成员 用 点“.”运算符 Ex: typedef struct st { char a; int b; } st; 定义 ...

  7. Vue项目SEO优化的另一种姿态

    背景:当前项目首页和登陆后的平台在一个项目里,路由采用hash模式,现在要做SEO优化,这时候同构SSR(Server Side Rendering)服务端渲染代价显然太大,影响范围比较广,同样更改当 ...

  8. socketpair + signal + select 的套路

    1:起因 最近在看代码时连续两次看到这三个函数的组合使用,为方便以后借鉴和回忆,先记录下来. 这三个函数的应用场景是这样的: 1.1 首先socketpair函数创建一对已连接套接字,返回的两个描述符 ...

  9. 配置ntpd时钟同步服务

    ntpd时钟同步服务 目录 参考: CentOS配置时间同步NTP: http://www.crsay.com/wiki/wiki.php/server/centos/ntp-set 解决ntp的错误 ...

  10. wx.request 获取不到post传递的值

    微信小程序的wx.request请求,method设为POST并向后台传递数据,但从后台返回的信息来看后台并没有获得传递的数据 wx.request({              url: 'url' ...