高并发 python socket send 异步_【Python】使用socketserver建立一个异步TCP服务器

概述

这篇文章是讲解如何使用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 copyimportsocketimportthreadingimportsocketserverclassThreadedTCPRequestHandler(socketserver.BaseRequestHandler):defhandle(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)classThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):pass

defclient(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()

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建立连接后打印「: is connect!」信息出来,并获取客户端的ip地址和端口信息,可以在ThreadedTCPRequestHandler类里面改写setup函数。

1 [python] view plain copy2

3 client_addr =[]4

5 classThreadedTCPRequestHandler(socketserver.BaseRequestHandler):6

7 defsetup(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 defhandle(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 copy2

3 print("\nclient_addr:"+str(client_addr))

2、保持TCP长连接

官方例程中是建立了TCP连接后就马上断开,如果想建立长连接,可以在handle函数中添加while循环,同时修改代码为:先判断缓冲区是否有数据,有数据才进行响应;改写finish函数,可以看到finish的信息并没有打印出来。如果注释掉while循环语句,可以看到finish的信息会打印出来。

1 [python] view plain copy2

3 classThreadedTCPRequestHandler(socketserver.BaseRequestHandler):4

5 defsetup(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 defhandle(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 deffinish(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 copy2

3 client_addr =[]4 client_socket =[]5

6 classThreadedTCPRequestHandler(socketserver.BaseRequestHandler):7

8 defsetup(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 defhandle(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 deffinish(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 copy2

3 message = bytes("clientTest\n", "ascii")4 whileTrue:5 time.sleep(2)6 ifclient_addr:7 client_socket[0].sendall(message)

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

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

下面继续添加新的功能,假设客户端每隔一段时间发送数据给服务器(心跳包),如果在一定时间内服务器没有接受到心跳包,表明客户端已经断开了连接,这个时候服务器可以主动断开客户端的连接了。那么我们在原有的代码增加此功能。实际上,只需要修改ThreadedTCPRequestHandler类即可。

1 [python] view plain copy2

3 classThreadedTCPRequestHandler(socketserver.BaseRequestHandler):4

5 ip = ""

6 port =07 timeOut = 6 #设置超时时间变量

8

9 defsetup(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 defhandle(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 deffinish(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断开连接!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值