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

概述

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

 

socketserver主要的类

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

 

BaseRequestHandler类

BaseRequestHandler类的实例h能够实现如下方法:

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

二、h.setup() 该方法在handle()以前调用。默认状况下,它不执行任何操做。若是但愿服务器实现更多链接设置(如创建SSL链接),能够在这里实现。

三、h.finish() 调用本方法能够在执行完handle()以后执行清除操做。默认状况下,它不执行任何操做。若是setup()和handle()方法都不生成异常,则无需调用该方法。

网络

 

官方例程

 

首先上官方给出的例程app

 

[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异步

正常输入以下:socket

$ 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函数

 

增长功能

上面部分主要是讲解官方的例程,下面这一部分是博主本身增长的功能。工具

一、获取客户端的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("\nclient_addr:"+str(client_addr))
 

二、保持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)的休眠。

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

如今的例程是在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\n", "ascii") 4 while True: 5     time.sleep(2) 6     if client_addr: 7         client_socket[0].sendall(message)

 

 

修改服务器ip地址为空及端口为8080,使用socket调试工具链接该服务器,便可每隔2s接收到「clientTest」字符串。
 
 

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

下面继续添加新的功能,假设客户端每隔一段时间发送数据给服务器(心跳包),若是在必定时间内服务器没有接受到心跳包,代表客户端已经断开了链接,这个时候服务器能够主动断开客户端的链接了。那么咱们在原有的代码增长此功能。实际上,只须要修改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断开链接!
相关文章
相关标签/搜索