SocketServer模块

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

类的继承关系

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

他有四个同步类:TCPServer,UDPServer,UNIXStreamServer,UNIXDatagramServer。

2个mixin类,ForKingMIXIN.和ThreadingMixln类,用来支持异步。

class ForkingUDPServer(ForkingMixln,UDPServer):pass

class ForkingTCPServer(ForkingMixln,TCPServer):pass

class ThreadingUDPServer(ThreadingMixln,UDPServer):pass

class ThreadingTCPServer(ThreadingMixln,TCPServer):pass

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

编程接口

socketserver.BaseServer(server_address,RequestHandlerClass)

需要提供务器绑定的地址信息,和用于处理请求的RequestHandlerClass类。

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

 BaseRequestHandler类

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

服务端server实例接收用户晴请求后,最后会实例化这个类。

它被初始化时,送入3个构造参数,request,client_address,server自身。

以后就可以在BaseRequestHandler类的实例上使用以下属性:

self.request是和客户端的链接的socket对象

self.server是TCPServer本身

self.client_address是客户端地址

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

import socketserver

class MyHandler(socketserver.BaseRequestHandler):
    def handle(self):
        super().handle()
        #todo
        print("come")

addr = ("127.0.0.1",9999)
server = socketserver.ThreadingTCPServer(addr,MyHandler)

server.serve_forever()

server.server_close()

上面的代码运行起来,测试可以看到服务已经启动起来了。处于监听的状态。

再继续修改代码。

import socketserver
import threading

class MyHandler(socketserver.BaseRequestHandler):
    def setup(self):
        super().setup()
        self.event = threading.Event()


    def handle(self):
        super().handle()
        print(self.server,self.client_address,self.request)
        while not self.event.wait(1):
            print("come")

addr = ("127.0.0.1",9999)
server = socketserver.ThreadingTCPServer(addr,MyHandler)

server.serve_forever()

server.server_close()

结果为:
<socketserver.ThreadingTCPServer object at 0x00000000026BA8D0> ('127.0.0.1', 55533) <socket.socket fd=212, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9999), raddr=('127.0.0.1', 55533)>

在继续修改代码、

import socketserver
import threading

class MyHandler(socketserver.BaseRequestHandler):
    def setup(self):
        super().setup()
        self.event = threading.Event()


    def handle(self):
        super().handle()
        print(self.server,self.client_address,self.request)
        while not self.event.is_set():
            data = self.request.recv(1024)
            print(data)

addr = ("127.0.0.1",9999)
server = socketserver.ThreadingTCPServer(addr,MyHandler)

server.serve_forever()

server.server_close()

上面的代码运行起来,由小工具发送数据就可以直接显示了。

import socketserver
import threading

class MyHandler(socketserver.BaseRequestHandler):
    def setup(self):
        super().setup()
        self.event = threading.Event()


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

addr = ("127.0.0.1",9999)
server = socketserver.ThreadingTCPServer(addr,MyHandler)

server.serve_forever()

server.server_close()

上面就可以回消息了。上面可以连接几个客户端。

 测试代码

import threading 
import socketserver

class MyHandler(socketsever.BasequestHandler):
    def handler(self):
        #super().handle()可以不调用,父类handle什么都没有做
        print("_"*30)
        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 = ("192.168.142.1",9999)
server = socketserver.ThreadingTCPServer(addr,MyHandler)

server.serve_forever()#永久

测试结果说明,handle方法相当于socket的recv方法。

每个不同的链接上的请求过来后,生成这个链接的socket对象即self.request,客户端渎职是self.client_address。

问题

测试过程中,上面代码,连接后立即断开了,为什么?

怎么样才能客户端和服务器端长时间连接

import threading 
import socketserver
import logging

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

class MyHandler(socketsever.BaseRequestHandler):
    def handler(self):
        #super().handle()可以不调用,父类handle什么都没有做
        print("_"*30)
        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)
        for i in range(3):
            data = self.request.recv(1024)
            logging.info(data)
        logging.info("====end====")
        
addr = ("192.168.142.1",9999)
server = socketserver.ThreadingTCPServer(addr,MyHandler)

server.serve_forever()#永久

将ThreadingTCPServer换成TCPServer,同时连接2个客户端观察效果。

ThreadingTCPServer是异步的,可以同时处理多个连接。

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

总结

创建服务器需要几个步骤

  1. 从BaseRequestHandler类派生出子类,并覆盖起handle方法来创建请求处理程序类,此方法将处理传入请求。
  2. 实例化一个服务器类,传参服务器的地址和请求处理类。
  3. 调用服务器实例的handle_request()或serve——forever()方法。
  4. 调用server_close()关闭套接字

实现EchoServer

顾名思义,Echo,来什么消息回显什么消息。

客户端发来什么消息,返回什么消息。

import threading
from socketserver import ThreadingTCPServer,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()

        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="echoserver",daemon=True)
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)

上面的代码启动起来,就可以回显消息了,主程序不会有任何显示。

#小工具结果

10:38:59 发送数据:123[1次]
10:38:59 收到数据:('127.0.0.1', 51338) 123
10:39:11 发送数据:xpffsw
[1次]
10:39:11 收到数据:('127.0.0.1', 51338) xpffsw

练习——改写ChatServer

使用ThreadingTCPServer改写ChatServer

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):
        super().setup()
        self.event = threading.Event()#初始工作
        self.clients[self.client_address] = self.request

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

    def handle(self):
        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 = ThreadingTCPServer(addr,ChatHandler)

server_thread = threading.Thread(target=server.serve_forever,name="chatserver",daemon=True)
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)

问题,self.clients.pop(self.client_address)#能执行到吗?

如果连接的线程中handle方法中抛出异常,例如客户端主动断开导致的异常,线程崩溃,self.clients的pop方法还能执行吗?

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

解决客户端主动连接断开问题

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):
        super().setup()
        self.event = threading.Event()#初始工作
        self.clients[self.client_address] = self.request

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

    def handle(self):
        super().handle()
        while not self.event.is_set():
           data = self.request.recv(1024).decode()
           print(data,"____________________")#增加
           if data == "quit":
               break
            msg = "{} {}".format(self.client_address,data).encode()
           logging.info(msg)
           for c  in self.clients.values():
               print("++++++++++++++")#增加
               self.request.send(msg)
        print("end")

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

server_thread = threading.Thread(target=server.serve_forever,name="chatserver",daemon=True)
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)

通过打印可以看到,客户端主动断开,会导致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):
        super().setup()
        self.event = threading.Event()#初始工作
        self.clients[self.client_address] = self.request

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

    def handle(self):
        super().handle()
        while not self.event.is_set()
           data = self.request.recv(1024).decode()
           print(data,"____________________")#增加
           if  not data or data == "quit":
               print("broken pipe")
               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 = ThreadingTCPServer(addr,ChatHandler)

server_thread = threading.Thread(target=server.serve_forever,name="chatserver",daemon=True)
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)

上面的代码可以增加一个代码,处理一个异常。

总结:

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

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

原文地址:https://www.cnblogs.com/xpc51/p/11966469.html