38 - 网络编程-socketserver

2024-04-13 06:32
文章标签 编程 网络 38 socketserver

本文主要是介绍38 - 网络编程-socketserver,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

38 - 网络编程-socketserver

 

目录

  • 1 socket编程弊端
  • 2 SocketServer模块
    • 2.1 服务器类
    • 2.2 Mixin类
    • 2.3 RequestHandlerClass是啥
    • 2.4 编程接口
  • 3 实现EchoServer
  • 4 聊天室

 

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交给了BaseServerself.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_addressself.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 = Falseself.__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)raiseelse: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 = requestself.client_address = client_addressself.server = serverself.setup()try:self.handle()finally:self.finish()def setup(self):    # 每一个连接初始化passdef handle(self):   # 每一次请求处理pass def finish(self):   # 每一个连接清理pass

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

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

这些参数到底是什么?

import socketserverclass 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 threadingFORMAT = '%(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()breakmsg = '{}:{} {}'.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: continueif cmd == 'quit':server.server_close()breakprint(threading.enumerate())

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

4 聊天室

代码如下:

import socketserver
import logging
import threadingFORMAT = '%(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 excepttry:data = self.request.recv(1024)except ConnectionResetError:self.clients.remove(self.request)self.event.set()break# use TCP tools exceptif 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()breakmsg = "{}:{} {}".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 = ipself.port = portself.sock = socketserver.ThreadingTCPServer((self.ip, self.port), ChatRequestHandler)def start(self):self.sock.daemon_threads = Truethreading.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: continueprint(threading.enumerate())

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

所有巧合的是要么是上天注定要么是一个人偷偷的在努力。

这篇关于38 - 网络编程-socketserver的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/899378

相关文章

MySQL的JDBC编程详解

《MySQL的JDBC编程详解》:本文主要介绍MySQL的JDBC编程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录前言一、前置知识1. 引入依赖2. 认识 url二、JDBC 操作流程1. JDBC 的写操作2. JDBC 的读操作总结前言本文介绍了mysq

Debian 13升级后网络转发等功能异常怎么办? 并非错误而是管理机制变更

《Debian13升级后网络转发等功能异常怎么办?并非错误而是管理机制变更》很多朋友反馈,更新到Debian13后网络转发等功能异常,这并非BUG而是Debian13Trixie调整... 日前 Debian 13 Trixie 发布后已经有众多网友升级到新版本,只不过升级后发现某些功能存在异常,例如网络转

Python异步编程之await与asyncio基本用法详解

《Python异步编程之await与asyncio基本用法详解》在Python中,await和asyncio是异步编程的核心工具,用于高效处理I/O密集型任务(如网络请求、文件读写、数据库操作等),接... 目录一、核心概念二、使用场景三、基本用法1. 定义协程2. 运行协程3. 并发执行多个任务四、关键

AOP编程的基本概念与idea编辑器的配合体验过程

《AOP编程的基本概念与idea编辑器的配合体验过程》文章简要介绍了AOP基础概念,包括Before/Around通知、PointCut切入点、Advice通知体、JoinPoint连接点等,说明它们... 目录BeforeAroundAdvise — 通知PointCut — 切入点Acpect — 切面

Python开发简易网络服务器的示例详解(新手入门)

《Python开发简易网络服务器的示例详解(新手入门)》网络服务器是互联网基础设施的核心组件,它本质上是一个持续运行的程序,负责监听特定端口,本文将使用Python开发一个简单的网络服务器,感兴趣的小... 目录网络服务器基础概念python内置服务器模块1. HTTP服务器模块2. Socket服务器模块

C#异步编程ConfigureAwait的使用小结

《C#异步编程ConfigureAwait的使用小结》本文介绍了异步编程在GUI和服务器端应用的优势,详细的介绍了async和await的关键作用,通过实例解析了在UI线程正确使用await.Conf... 异步编程是并发的一种形式,它有两大好处:对于面向终端用户的GUI程序,提高了响应能力对于服务器端应

Go语言网络故障诊断与调试技巧

《Go语言网络故障诊断与调试技巧》在分布式系统和微服务架构的浪潮中,网络编程成为系统性能和可靠性的核心支柱,从高并发的API服务到实时通信应用,网络的稳定性直接影响用户体验,本文面向熟悉Go基本语法和... 目录1. 引言2. Go 语言网络编程的优势与特色2.1 简洁高效的标准库2.2 强大的并发模型2.

C# async await 异步编程实现机制详解

《C#asyncawait异步编程实现机制详解》async/await是C#5.0引入的语法糖,它基于**状态机(StateMachine)**模式实现,将异步方法转换为编译器生成的状态机类,本... 目录一、async/await 异步编程实现机制1.1 核心概念1.2 编译器转换过程1.3 关键组件解析

Linux中压缩、网络传输与系统监控工具的使用完整指南

《Linux中压缩、网络传输与系统监控工具的使用完整指南》在Linux系统管理中,压缩与传输工具是数据备份和远程协作的桥梁,而系统监控工具则是保障服务器稳定运行的眼睛,下面小编就来和大家详细介绍一下它... 目录引言一、压缩与解压:数据存储与传输的优化核心1. zip/unzip:通用压缩格式的便捷操作2.

Go语言数据库编程GORM 的基本使用详解

《Go语言数据库编程GORM的基本使用详解》GORM是Go语言流行的ORM框架,封装database/sql,支持自动迁移、关联、事务等,提供CRUD、条件查询、钩子函数、日志等功能,简化数据库操作... 目录一、安装与初始化1. 安装 GORM 及数据库驱动2. 建立数据库连接二、定义模型结构体三、自动迁