1、SocketServer:

socket编程过于底层,编程虽然有套路,但是想要写出健壮的代码还是比较困难的,所以很多语言都对socket底层

API进行封装,Python的封装就是——socketserver模块。它是网络服务编程框架,便于企业级快速开发

2、类的继承关系:

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

SocketServer简化了网络服务器的编写。

它有4个同步类:

TCPServer,UDPServer,UnixStreamServer,UnixDatagramServer。
2个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是创建多线程

3、编程接口:

socketserver.BaseServer(server_address, RequestHandlerClass)
需要提供服务器绑定的地址信息,和用于处理请求的RequestHandlerClass类。
RequestHandlerClass类必须是BaseRequestHandler类的子类,在BaseServer中代码如下:

# BaseServer代码 ----》
class BaseServer:
  def __init__(self, server_address, RequestHandlerClass):
    """Constructor. May be extended, do not override."""
    self.server_address = server_address
    self.RequestHandlerClass = RequestHandlerClass
    self.__is_shut_down = threading.Event()
    self.__shutdown_request = False
  def finish_request(self, request, client_address): # 处理请求的方法
    """Finish one request by instantiating RequestHandlerClass."""
    self.RequestHandlerClass(request, client_address, self) # RequestHandlerClass构造

BaseRequestHandler类

它是和用户连接的用户请求处理类的基类,定义为BaseRequestHandler(request, client_address, server)

服务端Server实例接收用户请求后,最后会实例化这个类。
它被初始化时,送入3个构造参数:request, client_address, server自身
以后就可以在BaseRequestHandler类的实例上使用以下属性:

  • self.request是和客户端的连接的socket对象
  • self.server是TCPServer实例本身
  • self.client_address是客户端地址‘

这个类在初始化的时候,它会依次调用3个方法。子类可以覆盖这些方法。

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:579817333 
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
# 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

测试:

import threading
import socketserver

class MyHandler(socketserver.BaseRequestHandler):
    def handle(self):
        # super().handle() # 可以不调用,父类handle什么都没有做,一般习惯性的调用一下父类的
        print('-'*30)
        print(self.__dict__)
        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 = ('127.0.0.1', 9999)
server = socketserver.ThreadingTCPServer(addr, MyHandler) # 生成一个多线程的server
print(server.__class__.__name__)


server.serve_forever() # 永久开启

结果:

D:\python3.7\python.exe E:/code_pycharm/复习/t4.py
ThreadingTCPServer
------------------------------
{'request': <socket.socket fd=244, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999), raddr=('127.0.0.1', 63665)>, 'client_address': ('127.0.0.1', 63665), 'server': <socketserver.ThreadingTCPServer object at 0x000000000224C438>}
<socketserver.ThreadingTCPServer object at 0x000000000224C438>
<socket.socket fd=244, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999), raddr=('127.0.0.1', 63665)>
('127.0.0.1', 63665)
{'request': <socket.socket fd=244, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999), raddr=('127.0.0.1', 63665)>, 'client_address': ('127.0.0.1', 63665), 'server': <socketserver.ThreadingTCPServer object at 0x000000000224C438>}
{'server_address': ('127.0.0.1', 9999), 'RequestHandlerClass': <class '__main__.MyHandler'>, '_BaseServer__is_shut_down': <threading.Event object at 0x000000000224C860>, '_BaseServer__shutdown_request': False, 'socket': <socket.socket fd=228, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999)>, '_threads': [<Thread(Thread-1, started 8732)>]}
[<_MainThread(MainThread, started 8048)>, <Thread(Thread-1, started 8732)>]
<Thread(Thread-1, started 8732)>
------------------------------

Process finished with exit code 1

测试结果说明:

handler方法相当于socket的recv方法

生成的Baseserver 对象(ThreadingTCPServer),就包含了accept,可以通过server._dict-可以看到accept的socket

每个不同的连接上的请求过来后,

生成这个连接的socket对象,即self.request,

客户端地址是,self.client_address

而且没有请求进来,阻塞在server_forever()

但是,上面的代码,连接后就客户端 立即断开了

测试:客户端 和 服务器端持久连接

添加循环实现

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:579817333 
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
import threading
import socketserver

class MyHandler(socketserver.BaseRequestHandler):
    def handle(self):
        # super().handle() # 可以不调用,父类handle什么都没有做,一般习惯性的调用一下父类的
        print('-'*30)
        print(self.server) # 服务

        # print('=================')
        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)
            print(data)
            print(' ===== end ==== ')

addr = ('127.0.0.1', 9999)
server = socketserver.ThreadingTCPServer(addr, MyHandler) # 生成一个多线程的server

server.serve_forever() # 永久开启
# print(server.__dict__)

结果:每次都阻塞在recv,当循环结束,客户端 断开,服务器端一直没有断开

3次之后断开连接

D:\python3.7\python.exe E:/code_pycharm/复习/t4.py
------------------------------
<socketserver.ThreadingTCPServer object at 0x000000000294C438>
<socket.socket fd=244, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999), raddr=('127.0.0.1', 64303)>
('127.0.0.1', 64303)
{'request': <socket.socket fd=244, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999), raddr=('127.0.0.1', 64303)>, 'client_address': ('127.0.0.1', 64303), 'server': <socketserver.ThreadingTCPServer object at 0x000000000294C438>}
{'server_address': ('127.0.0.1', 9999), 'RequestHandlerClass': <class '__main__.MyHandler'>, '_BaseServer__is_shut_down': <threading.Event object at 0x000000000294C860>, '_BaseServer__shutdown_request': False, 'socket': <socket.socket fd=228, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999)>, '_threads': [<Thread(Thread-1, started 4756)>]}
[<_MainThread(MainThread, started 6112)>, <Thread(Thread-1, started 4756)>]
<Thread(Thread-1, started 4756)>
------------------------------
b'd'
 ===== end ==== 
b'd'
 ===== end ==== 
b'd'
 ===== end ====

将ThreadingTCPServer 换成TCPServer, 同时连接2个客户端观察效果发现,是串行的,一个结束之后,才能另外一个处理

并且每次都是阻塞到 recv 方法处

ThreadingTCPServer 是异步的,可以同时处理多个请求。

TCPServer 是同步的,一个连接处理完了,即一个连接的 handle方法 执行完了,才能处理另一个连接,且只有主线程。

总结:

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

  • 从BaseRequestHandler类派生出子类,并覆盖器handler() 方法来创建请求处理程序类,此方法将处理传入请求
  • 实例化一个服务器类,传参服务器的地址和请求处理类
  • 调用服务器实例的handle_request(),或server_forever() 方法
  • 调用server_close()关闭套接字

4、测试实例

4.1、显示EchoServer

顾名思义,回显消息

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:579817333 
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
import threading
from  socketserver import ThreadingTCPServer, TCPServer, 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()

        try:
            while not self.event.is_set():
                data = self.request.recv(1024).decode()
                msg = '{}{}'.format(self.client_address, data).encode()
                self.request.send(msg)
        except Exception as e:
            print(e)

        finally:
            print('=== end ====')


server = ThreadingTCPServer(('127.0.0.1', 9999), 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':
            server.shutdown()
            break
        print(threading.enumerate())
except Exception as e:
    print(e)
except KeyboardInterrupt:
    pass
finally:
    print('exit')
    sys.exit(0)

4.2、实现群聊

import threading
from socketserver import ThreadingTCPServer, BaseRequestHandler
import sys
import logging

FOMAT = '%(asctime)s %(thread)s %(threadName)s %(message)s'
logging.basicConfig(level=logging.INFO, format=FOMAT)

class ChatServerHanlder(BaseRequestHandler):
    clients = {}


    def setup(self):
        super().setup()
        self.event = threading.Event()
        self.clients[self.request] = self.client_address

    def finish(self):
        super().finish()
        self.clients.pop(self.request)
        self.event.set()
        print(self.clients)



    def handle(self):
        super().handle()

        while not self.event.is_set():
            data = self.request.recv(1024)

            if data.strip() == b'quit' or data == b'':
                break

            msg = ' your msg is {} '.format(data.decode()).encode()

            for s in self.clients:
                s.send(msg)


laddr = ('127.0.0.1', 9999)
server = ThreadingTCPServer(laddr, ChatServerHanlder)
print(server.socket)
threading.Thread(target=server.serve_forever, name='server').start()

try:
    while True:
        cmd = input(">>>>")
        if cmd == 'quit':
            server.shutdown()
            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模块提供的不同的,但是编程接口是一样的,即使多线程,多线程的类也是一样的 ,大大减少了编程难度。

socket 是由self.request 管理的,不需要自己去close,只需要清理自己定义的一些资源。

每一个请求,生成一个handler 类的实例。各自操作自己的实例属性,以及公用的类属性