Python:网络编程:SocketServer

目录

SocketServer

类的继承关系

编程接口

测试代码

 总结:

实现EchoSerer

练习---改写ChatServer

总结


SocketServer

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

类的继承关系

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

它有4个同步类:TCPServer、UDPServer、UnixStramServer、UnixDatagramServer

2个Mixin类:ForkingMixin和ThreadingMixin,用来支持异步

Class ForkingUDPServer(ForkingMixin,UDPServer):pass        #UDPServer 多进程同异步编程

Class ForkingTCPServer(ForkingMixin,TCPServer):pass        #TCPServer多线程,同异步编程

Class  ThreadingUDPServer(ThreadingMixin,UDPServer):pass        #UDPServer 多线程、同异步编程

Class ThreadingTCPSever(ThreadingMixin,TCPServer):pass        #TCPServer 多线程编程

fork是创建多进程,thread是创建多进程

编程接口

socketserver.BaseServer(server_address, RequestHandlerClass)  #需要提供服务器绑定的地址信息,和用于处理请求的RequestHandlerClass类

RequestHandlerClass类必须是BaseRequestHandler类的子类,在BaseServer中代码如下

class BaseServver:
   

     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)

BaseRequestHandler类

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

服务端Server实例接受用户请求后,最后会实例化这个类
它被初始化后,送入3个参数:request、client_address、server自身
以后 就可以在BaseRequestHandler类的实例上使用以下属性:
self.request是和客户端的连接的socket对象
self.server是TCPServer本身
self.client_address是客户端地址

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

#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
import logging

FORMAT = "%(asctime)s %(threadName)s %(thread)d %(message)s"
logging.basicConfig(format=FORMAT,level=logging.INFO)

class MyHandler_my(socketserver.BaseRequestHandler):

    def setup(self):
        super().setup()
        self.event = threading.Event()

    def handle(self):
        #super().hanler()  #可以不调用,父类handler什么都没走
        print("~"*30)
        print(self.server) #服务
        print(self.request) #服务端负责客户端连接请求的socker对象
        print(self.client_address) #客户端地址
        print(self.__dict__)
        print(self.server.__dict__)  #能看到负责accept的socket

        print(threading.enumerate())
        print(threading.current_thread())
        print("~"*30)
        # logging.info()
        while not self.event.is_set():
            print("come")
            data = self.request.recv(1024)
            msg = "I`m comming  you msg {} ".format(data.decode())
            self.request.send(msg.encode())


addr=("127.0.0.1",12000)
server = socketserver.ThreadingTCPServer(addr,MyHandler_my)
print(server)
logging.info(11)
server.serve_forever() #永久
server.server_close()

结果:

<socketserver.ThreadingTCPServer object at 0x022CEF50>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<socketserver.ThreadingTCPServer object at 0x022CEF50>
<socket.socket fd=488, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 12000), raddr=('127.0.0.1', 51527)>
('127.0.0.1', 51527)
{'request': <socket.socket fd=488, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 12000), raddr=('127.0.0.1', 51527)>, 'client_address': ('127.0.0.1', 51527), 'server': <socketserver.ThreadingTCPServer object at 0x022CEF50>, 'event': <threading.Event object at 0x023653D0>}
{'server_address': ('127.0.0.1', 12000), 'RequestHandlerClass': <class '__main__.MyHandler_my'>, '_BaseServer__is_shut_down': <threading.Event object at 0x022D5230>, '_BaseServer__shutdown_request': False, 'socket': <socket.socket fd=484, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 12000)>, '_threads': [<Thread(Thread-1, started 704)>]}
[<_MainThread(MainThread, started 24636)>, <Thread(Thread-1, started 704)>]
<Thread(Thread-1, started 704)>
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
come
come

 总结:

将ThreadingTCPServer换成TCPServer,同时连接2个客户端观察效果
ThreadingTCPServer是异步的,可以同时处理多个连接

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


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

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

实现EchoSerer

import threading
from socketserver import ThreadingTCPServer , BaseRequestHandler
import sys

class EchoHandler(BaseRequestHandler):
    def setup(self) -> None:
        super().setup()
        self.event = threading.Event()  #初始话工作

    def finish(self) -> None:
        super().finish()
        self.event.set()

    def handle(self) -> None:
        super().handle()

        while not self.event.is_set():
            data = self.request.recv(1024).decode()
            msg = "{} {}".format(self.client_address,data).encode()
            self.request.send(msg)
        print("end")

addr = ("127.0.0.1",9999)
server = ThreadingTCPServer(addr,EchoHandler)
server_thread= threading.Thread(target=server.serve_forever,name="EchoHandle")
server_thread.start()

try:
    while True:
        cmd = input(">>>")
        if cmd.strip() == "quit":
            break
        print(threading.enumerate())
except Exception as e:
    print(e)
except KeyboardInterrupt:
    pass
finally:
    print("exit")
    sys.exit(0)

练习---改写ChatServer

使用ThreadingTCP改写ChatSserver

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

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

class ChatHandler(BaseRequestHandler):
    clients = { }

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

    def finish(self) -> None:
        super().finish() #清理工作
        self.clients.pop(self.client_address)   #能执行到吗?
        self.event.set()

    def handle(self) -> None:
        super().handle()
        while not self.event.is_set():
            data = self.request.recv(1024).decode()
            if data == "quit":
                break
            msg = "{} {} ".format(self.client_address,data).encode()
            logging.info(msg)
            for c in self.clients.values():
                self.request.send(msg)
        print("End")


addr = ("0.0.0.0",9999)
server_tcp = ThreadingTCPServer(addr,ChatHandler)

server_thread = threading.Thread(target=server_tcp.serve_forever,name="ChatSever",daemon=True)
server_thread.start()

try:
    while True:
        cmd = input(">>>")
        if cmd.strip() == 'quit':
            break
except Exception as e:
    print(e)
except KeyboardInterrupt:
    pass
finally:
    print("Exit")
    sys.exit()

结果:
>>>2021-07-17 10:47:09,845 Thread-1 8912 b"('127.0.0.1', 50991) 11111 "
End
2021-07-17 10:47:30,923 Thread-2 15940 b"('127.0.0.1', 50992) fsfs "

上述问题
上例 self.clients.pop(self.client_address)能执行到吗?如果连接的线程中handler方法抛出异常,例如客户端主动断开导致的异常,线程崩溃,self.clents的pop方法还能执行吗?

可以执行,基类源保证了即使异常,也能执行finish的方法,但不代表不应该不捕获客户端各种异常

修改客户端断开后的异常

  • 通过打印可以看到,客户端主动断开,会导致recv方法立即返回一个空bytes,并没有同时抛出异常,当循环回到recv这一句的时候就会产生异常,可以通过判断data数据是否为空来判断客户端是否断开
import threading
from socketserver import ThreadingTCPServer,BaseRequestHandler
import sys
import logging

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

class ChatHandler(BaseRequestHandler):
    clients = { }

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

    def finish(self) -> None:
        super().finish() #清理工作
        self.clients.pop(self.client_address)
        self.event.set()

    def handle(self) -> None:
        super().handle()
        while not self.event.is_set():
            data = self.request.recv(1024).decode()
            print(data,"~"*30)
            if  not data or data == "quit":
                break
            msg = "{} {} ".format(self.client_address,data).encode()
            logging.info(msg)
            for c in self.clients.values():
                self.request.send(msg)
        print("End")


addr = ("0.0.0.0",9999)
server_tcp = ThreadingTCPServer(addr,ChatHandler)

server_thread = threading.Thread(target=server_tcp.serve_forever,name="ChatSever",daemon=True)
server_thread.start()

try:
    while True:
        cmd = input(">>>")
        if cmd.strip() == 'quit':
            break
except Exception as e:
    print(e)
except KeyboardInterrupt:
    pass
finally:
    print("Exit")
    sys.exit()

总结

为每一个连接提供RequestHandlerClass类的实例,一次调用setup、handle、finish方法,且使用try……finally结构保证finish方法一定能被调用。这些方法依次执行完成,如果想维持这个连接和客户端通信,就需要在handler函数中使用循环

sockerserver模块提供的不同的类,但是编程接口是一样的,即使多线程,多进程的类也是一样,大大减少了编程的难度

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值