【Python】使用socketserver建立一个异步TCP服务器

摘要:
概述本文介绍了如何使用套接字服务器建立异步TCP服务器。Python版本为3.5.1。套接字服务器模块的主要类如下:1.BaseServer包含服务器的核心函数和混合类的钩子函数。h、 请求包含请求,h客户端_地址包含客户端地址,h服务器包含调用处理程序的实例。官方例程:[python]viewplaincopyimportsocketimportthreadingimportsocketserverclassThreadTCPRequestHandler:defhandle:data=strcur_thread=线程。current_thread()response=bytesself.request.sendallclassThreadTCPServer:passdefclient:withsocket.sockassock:sock.connectsock。sendallresponse=strprintif__name__==“__main__”:#Port0表示选择未使用的端口HOST,PORT=“localhost”,0server=线程TCPServerip,PORT=服务器。server_address#使用服务器开始线程读取--然后该线程将为每个请求启动一个#线程server_thread=线程。线程#在服务器线程终止时立即退出服务器。daemon=Trueserver_thread.start()printclientclientserver.shutdown()服务器。server_close()客户端函数用于创建客户端,可以忽略。主要部分是主函数ThreadTCPServer类和ThreadTCPRequestHandler类。
概述

这篇文章是讲解如何使用socketserver建立一个异步TCP服务器,其中Python版本为3.5.1。

socketserver主要的类

socketserver模块中的类主要有以下几个:
1、BaseServer 包含服务器的核心功能与混合类(mix-in)的钩子功能。这个类主要用于派生,不要直接生成这个类的类对象,可以考虑使用TCPServer和UDPServer类。
2、TCPServer:基本的网络同步TCP服务器
3、UDPServer:基本的网络同步UDP服务器
4、ForkingMixIn:实现了核心的进程化功能,用于与服务器类进行混合(mix-in),以提供一些异步特性。不要直接生成这个类的对象。
5、ThreadingMixIn:实现了核心的线程化功能,用于与服务器类进行混合(mix-in),以提供一些异步特性。不要直接生成这个类的对象。
6、ForkingTCPServer: ForkingMixIn与TCPServer的组合
7、ForkingUDPServer:ForkingMixIn与UDPServer的组合
8、BaseRequestHandler:基本的请求处理类
9、StreamRequestHandler:TCP请求处理类的一个实现
10、DataStreamRequestHandler:UDP请求处理类的一个实现

BaseRequestHandler类

BaseRequestHandler类的实例h可以实现以下方法:

1、h.handle() 调用该方法执行实际的请求操作。调用该函数可以不带任何参数,但是几个实例变量包含有用的值。h.request包含请求,h.client_address包含客户端地址,h.server包含调用处理程序的实例。对于TCP之类的数据流服务,h.request属性是套接字对象。对于数据报服务,它是包含收到数据的字节字符串。

2、h.setup() 该方法在handle()之前调用。默认情况下,它不执行任何操作。如果希望服务器实现更多连接设置(如建立SSL连接),可以在这里实现。

3、h.finish() 调用本方法可以在执行完handle()之后执行清除操作。默认情况下,它不执行任何操作。如果setup()和handle()方法都不生成异常,则无需调用该方法。

官方例程

首先上官方给出的例程

[python] view plain copy
 
<span style="font-size:14px;">import socket  

import threading  
import socketserver  
class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):  
def handle(self):  
        data = str(self.request.recv(1024), 'ascii')  
        cur_thread = threading.current_thread()  
        response = bytes("{}: {}".format(cur_thread.name, data), 'ascii')  
self.request.sendall(response)  
class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):  
pass  
def client(ip, port, message):  
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:  
        sock.connect((ip, port))  
        sock.sendall(bytes(message, 'ascii'))  
        response = str(sock.recv(1024), 'ascii')  
print("Received: {}".format(response))  
if __name__ == "__main__":  
# Port 0 means to select an arbitrary unused port  
    HOST, PORT = "localhost", 0  
    server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)  
    ip, port = server.server_address  
# Start a thread with the server -- that thread will then start one  
# more thread for each request  
    server_thread = threading.Thread(target=server.serve_forever)  
# Exit the server thread when the main thread terminates  
    server_thread.daemon = True  
    server_thread.start()  
print("Server loop running in thread:", server_thread.name)  
    client(ip, port, "Hello World 1")  
    client(ip, port, "Hello World 2")  
    client(ip, port, "Hello World 3")  
    server.shutdown()  
    server.server_close()</span>  


client函数是建立一个客户端,可以不用管它。主要部分是在于主函数,ThreadedTCPServer类和ThreadedTCPRequestHandler类。ThreadedTCPServer类继承了BaseRequestHandler类,ThreadedTCPRequestHandler继承了ThreadingMixIn和TCPServer

正常输入如下:

$ python ThreadedTCPServer.py
Server loop running in thread: Thread-1
Received: Thread-2: Hello World 1
Received: Thread-3: Hello World 2
Received: Thread-4: Hello World 3

增加功能

上面部分主要是讲解官方的例程,下面这一部分是博主自己增加的功能。

1、获取客户端的ip和port

如果想在TCP建立连接后打印「<ip>:<port> is connect!」信息出来,并获取客户端的ip地址和端口信息,可以在ThreadedTCPRequestHandler类里面改写setup函数。
 1 [python] view plain copy
 2  
 3 client_addr = []  
 4   
 5 class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):  
 6   
 7     def setup(self):  
 8         ip = self.client_address[0].strip()     # 获取客户端的ip  
 9         port = self.client_address[1]           # 获取客户端的port  
10         print(ip+":"+str(port)+" is connect!")  
11         client_addr.append(self.client_address) # 保存到队列中  
12   
13     def handle(self):  
14         data = str(self.request.recv(1024), 'ascii')  
15         cur_thread = threading.current_thread()  
16         response = bytes("{}: {}".format(cur_thread.name, data), 'ascii')  
17         self.request.sendall(response)  
 
在主函数中添加下面语句,即可打印出连接过的客户端信息:
1 [python] view plain copy
2  
3 print("
client_addr:"+str(client_addr))  
 

2、保持TCP长连接

官方例程中是建立了TCP连接后就马上断开,如果想建立长连接,可以在handle函数中添加while循环,同时修改代码为:先判断缓冲区是否有数据,有数据才进行响应;改写finish函数,可以看到finish的信息并没有打印出来。如果注释掉while循环语句,可以看到finish的信息会打印出来。
 1 [python] view plain copy
 2  
 3 class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):  
 4   
 5     def setup(self):  
 6         ip = self.client_address[0].strip()     # 获取客户端的ip  
 7         port = self.client_address[1]           # 获取客户端的port  
 8         print(ip+":"+str(port)+" is connect!")  
 9         client_addr.append(self.client_address) # 保存到队列中  
10   
11     def handle(self):  
12         while True: # while循环  
13             data = str(self.request.recv(1024), 'ascii')  
14             if data:    # 判断是否接收到数据  
15                 cur_thread = threading.current_thread()  
16                 response = bytes("{}: {}".format(cur_thread.name, data), 'ascii')  
17                 self.request.sendall(response)  
18   
19     def finish(self):  
20         print("client is disconnect!") 
 
 
感谢评论区歇业的渔夫的建议,while True 循环建立长连接的方式非常占用CPU资源,最好在循环里面增加一个time.sleep(0.1)的休眠。

3、服务器给客户端发送请求

现在的例程是在ThreadedTCPRequestHandler类里面调用self.request.sendall方法来给客户端发送数据,而且只能被动发送数据,如果我想主动给客户端发送数据,又该怎么办呢?下面是实现服务器主动给客户端发送请求的功能。
 
TCP连接想要发送数据,只要找到相关的方法直接调用即可,于是我对ThreadedTCPServer这个类的实例server的方法找了好久,也没有找到发送的方法。后来我查资料注意到了一句话:「对于TCP之类的数据流服务,h.request属性是套接字对象。」我觉得我可以这样做:使用这个套接字对象发送数据。经过尝试后,验证成功。下面只放上核心代码:
 1 [python] view plain copy
 2  
 3 client_addr = []  
 4 client_socket = []  
 5   
 6 class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):  
 7   
 8     def setup(self):  
 9         ip = self.client_address[0].strip()     # 获取客户端的ip  
10         port = self.client_address[1]           # 获取客户端的port  
11         print(ip+":"+str(port)+" is connect!")  
12         client_addr.append(self.client_address) # 保存到队列中  
13         client_socket.append(self.request)      # 保存套接字socket  
14   
15     def handle(self):  
16         while True: # while循环  
17             data = str(self.request.recv(1024), 'ascii')  
18             if data:    # 判断是否接收到数据  
19                 cur_thread = threading.current_thread()  
20                 response = bytes("{}: {}".format(cur_thread.name, data), 'ascii')  
21                 self.request.sendall(response)  
22   
23     def finish(self):  
24         print("client is disconnect!")  
25         client_addr.remove(self.client_address)  
26         client_socket.remove(self.request)  
 

之后在主函数中通过client_socket队列调用sendall或sendto方法即可。例如我在主函数这样写(已经注释掉client函数调用):
1 [python] view plain copy
2  
3 message = bytes("clientTest
", "ascii")  
4 while True:  
5     time.sleep(2)  
6     if client_addr:  
7         client_socket[0].sendall(message)  
 

修改服务器ip地址为空及端口为8080,使用socket调试工具连接该服务器,即可每隔2s接收到「clientTest」字符串。
【Python】使用socketserver建立一个异步TCP服务器第1张
 
 

4、服务器接收客户端数据超时后断开

下面继续添加新的功能,假设客户端每隔一段时间发送数据给服务器(心跳包),如果在一定时间内服务器没有接受到心跳包,表明客户端已经断开了连接,这个时候服务器可以主动断开客户端的连接了。那么我们在原有的代码增加此功能。实际上,只需要修改ThreadedTCPRequestHandler类即可。
 1 [python] view plain copy
 2  
 3 class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):  
 4   
 5     ip = ""  
 6     port = 0  
 7     timeOut = 6     # 设置超时时间变量  
 8   
 9     def setup(self):  
10         self.ip = self.client_address[0].strip()     # 获取客户端的ip  
11         self.port = self.client_address[1]           # 获取客户端的port  
12         self.request.settimeout(self.timeOut)        # 对socket设置超时时间  
13         print(self.ip+":"+str(self.port)+"连接到服务器!")  
14         client_addr.append(self.client_address) # 保存到队列中  
15         client_socket.append(self.request)      # 保存套接字socket  
16   
17     def handle(self):  
18         while True: # while循环  
19             try:  
20                 data = str(self.request.recv(1024), 'ascii')  
21             except socket.timeout:  # 如果接收超时会抛出socket.timeout异常  
22                 print(self.ip+":"+str(self.port)+"接收超时!即将断开连接!")  
23                 break       # 记得跳出while循环  
24   
25             if data:    # 判断是否接收到数据  
26                 cur_thread = threading.current_thread()  
27                 response = bytes("{}: {}".format(cur_thread.name, data), 'ascii')  
28                 self.request.sendall(response)  
29   
30     def finish(self):  
31         print(self.ip+":"+str(self.port)+"断开连接!")  
32         client_addr.remove(self.client_address)  
33         client_socket.remove(self.request)  
 

使用socket调试工具连接该服务器后,不发送任何数据,过了6秒钟后,服务器端主要打印如下数据:
192.168.10.53:26408连接到服务器!
192.168.10.53:26408接收超时!即将断开连接!
192.168.10.53:26408断开连接!

免责声明:文章转载自《【Python】使用socketserver建立一个异步TCP服务器》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇001.mysql-炸裂函数的实现微信小程序跑步计时器下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

计算机网络基本知识

今天来讲讲网络,主要将传输层和应用层的一些基础知识 OSI七层模型:应用层(application)、表示层(presentation)、会话层(session)、传输层(transport)、网络层(network)、数据链路层(data)、物理层(physical/base)。用一句英语总结就是 all people seem to need data...

Python学习————包

一 包介绍 例: pool/ #顶级包 ├── __init__.py ├── futures #子包 │ ├── __init__.py │ ├── process.py │ └── thread.py └── versions.py #子模块 强调: 1. 在python...

systemd 和 systemctl 使用深入理解

https://www.linuxidc.com/Linux/2018-08/153545.htm Linux系统的启动方式有点复杂,而且总是有需要优化的地方。传统的Linux系统启动过程主要由著名的init进程(也被称为SysV init启动系统)处理,而基于init的启动系统被认为有效率不足的问题,systemd是Linux系统机器的另一种启动方式,宣...

Python多进程和多线程(跑满CPU)及IO模型详解

目录 Python多进程和多线程(跑满CPU)IO模型详解 Python多进程和多线程(跑满CPU) 转载自:https://www.liaoxuefeng.com/wiki/1016959663602400/1017627212385376 Python多进程和多线程(跑满CPU****) 概念 任务可以理解为进程(process),如打开一...

npm 安装依赖包提示找不到python环境和Could not load the Visual C++ component “VCBuild.exe”

1 提示找不到python环境变量:             一般错误信息中会提示找不到python2,需要安装python2,并且配置python环境变量。然后设置npm常用的python版本,用以下命令npm config set python python2.7  或者 npm install --python=python2.7 2 如果安装完之后...

Nginx系列一:正向代理和反向代理、Nginx工作原理、Nginx常用命令和升级、搭建Nginx负载均衡

转自https://www.cnblogs.com/leeSmall/p/9351343.html 仅供个人学习 一、什么是正向代理、什么是反向代理 1. 正向代理,意思是一个位于客户端和原始服务器(origin server)之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内...