在TCP服务端使用多线程技术能够提高响应的能力,但这种提高是有限的,因为你不可能无限制的创建多线程,更何况python的度线程还受到GIL锁的限制。想要更稳定的提高服务端性能,可以使用select模型。
select模型是多路复用模型的一种,windows和linux下都可以使用,还有更厉害的epoll模型,下一篇将会介绍。select模型允许进程指示内核等待多个事件的任何一个发生,并在只有一个或者多个事件发生或者经历一段事件后select函数才返回。select模型其实很好理解,我们给它三个数组,数组里存放的是socket文件,每一次执行select,模型会从这三个数组中分别挑出来可读的,可写的,发生异常的socket,并分别放入到三个数组中,这样,应用层遍历这三个数组,做相应的操作。
import select
import socket
def start_server(port):
HOST = '0.0.0.0'
PORT = port
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind((HOST, PORT)) # 套接字绑定的IP与端口
server.listen(10) # 开始TCP监听
inputs = [server] # 存放需要被检测可读的socket
outputs = [] # 存放需要被检测可写的socket
while inputs:
readable, writable, exceptional = select.select(inputs, outputs, inputs)
# 可读
for s in readable:
if s is server: # 可读的是server,说明有连接进入
connection, client_address = s.accept()
inputs.append(connection)
else:
data = s.recv(1024) # 故意设置的这么小
if data:
# 已经从这个socket当中收到数据, 如果你想写, 那么就将其加入到outputs中, 等到select模型检查它是否可写
print(data.decode(encoding='utf-8'))
if s not in outputs:
outputs.append(s)
else:
# 收到为空的数据,意味着对方已经断开连接,需要做清理工作
if s in outputs:
outputs.remove(s)
inputs.remove(s)
s.close()
# 可写
for w in writable:
w.send('收到数据'.encode(encoding='utf-8'))
outputs.remove(w)
# 异常
for s in exceptional:
inputs.remove(s)
if s in outputs:
outputs.remove(s)
s.close()
if __name__ == '__main__':
start_server(8801)
在select模型中,将server放入到inputs中,当执行select时就会去检查server是否可读,就说明在缓冲区里有数据,对于server来说,有连接进入。使用accept获得客户端socket文件后,首先要放入到inputs当中,等待其发送消息。
select会将所有可读的socket返回,包括server在内,假设一个客户端socket的缓冲区里有2000字节的内容,而这一次你只是读取了1024个字节,没有关系,下一次执行select模型时,由于缓冲区里还有数据,这个客户端socket还会被放入到readable列表中。因此,在读取数据时,不必再像之前那样使用一个while循环一直读取。
在每一次写操作执行后,都从socket从writable中删除,这样做的原因很简单,该写的数据已经写完了,如果不删除,下一次select操作时,又会把他放入到writable中,可是现在已经没有数据需要写了啊,这样做没有意义,只会浪费select操作的时间,因为它要遍历outputs中的每一个socket,判断他们是否可写以决定是否将其放入到writtable中
在exceptional中,是发生错误和异常的socket,有了这个数组,就在也不用操心错误和异常了,不然程序写起来非常的复杂,有了统一的管理,发生错误后的清理工作将变得非常简单
客户端代码与上一篇一致
import os
import time
import socket
def start_client(addr, port):
PLC_ADDR = addr
PLC_PORT = port
s = socket.socket()
s.connect((PLC_ADDR, PLC_PORT))
count = 0
while True:
msg = '进程{pid}发送数据'.format(pid=os.getpid())
msg = msg.encode(encoding='utf-8')
s.send(msg)
recv_data = s.recv(1024)
print(recv_data.decode(encoding='utf-8'))
time.sleep(3)
count += 1
if count > 20:
break
s.close()
if __name__ == '__main__':
start_client('127.0.0.1', 8801)
启动服务端后,你可以启动多个客户端来检查效果
QQ交流群: 211426309