39.IO多路复用(用select实现伪并发)

时间:2021-11-25 17:55:15

IO多路复用

1.用select实现多端口被多客户端访问的多路复用伪并发

  IO多路复用服务端:既读又写

# IO多路复用实现伪并发   用多个IO,可以监听多个文件句柄(socket对象)(一般是可以读了或者可以写了),
# 一旦文件句柄出现变化,就可以感应到
# 对于原生的socket 只能处理一个请求,只能监听一个端口
# 1.如何让server端监听两个端口
import socket


sk1 = socket.socket()
sk1.bind(('127.0.0.1', 8001,))
sk1.listen()

sk2 = socket.socket()
sk2.bind(('127.0.0.1', 8002,))
sk2.listen()

sk3 = socket.socket()
sk3.bind(('127.0.0.1', 8003,))
sk3.listen()


inputs = [sk3, sk1, sk2, ]

import select
# select,内部c语言实现,for循环监听多个句柄 最大1024个(window上只支持select)
# poll,内部依然用for循环检测无限制
# epool内部使用异步(谁有变化,谁告诉我,不一个一个去问),内部让每个句柄发生变化的时候去告知epoll,不用循环

while True:
r_list, w_list, e_list = select.select(inputs, [], [], 1)
'''
检测句柄变化
参数1:1为等待时间 1s后进去下次循环
第三个参数:感知句柄错误,从inputs传到e_list,并将错误句柄从第一个参数中去除,程序就不会发生错误
第二个参数:参数中的值,均会传到w_list,无论其是否发生变化
第一个参数:检测句柄变化,inputs中谁变化就将其放入r_list
'''
# [sk3, sk1, sk2] 内部自动监听sk3,sk1, sk2三个对象,一旦某个句柄发生变化
# 如果有人来连sk3,r_list = [sk]接收客户端的请求
# 如果有人来连sk1,r_list = [sk1]接收客户端的请求
for sk in r_list:
# 每一个连接对象
conn, address = sk.accept()
conn.sendall(bytes('hello',encoding='utf-8'))
conn.close()

  客户端1:

import socket

obj = socket.socket()

obj.connect(('127.0.0.1',8001,))

content = str(obj.recv(1024),encoding='utf-8')

print(content)

obj.close()

  客户端2:

import socket

obj = socket.socket()

obj.connect(('127.0.0.1',8002,))

content = str(obj.recv(1024),encoding='utf-8')

print(content)

obj.close()

 

2.用select实现,多个客户端的不同端口访问服务器的某一端口的伪并发,读写分离

  服务端:

import socket

sk1 = socket.socket()
sk1.bind(('127.0.0.1', 8001,))
sk1.listen()

inputs = [sk1, ]
outputs = [] # outputs的内容将会一直存在于w_list中
message = {}

import select

# select监听socket对象,一旦socket变换就能感知到
'''
服务端:
sk1 =socket.socket()
conn, address = sk1.accept()
conn.recv(1024)
客户端:
sk1 = socket.socket()
sk1.sendall()

在服务端对象sk1只负责接收用户的连接

而conn才负责专门与每一个客户端的sk1对象通信

'''

while True:
# 如果有人连接sk1
# r_list = [sk1,]
# 如果有人第一次连接,sk1发生变化
r_list, w_list, e_list = select.select(inputs, outputs, inputs, 1)
print('正在监听的socket对象%d' % len(inputs))
print(r_list)


for sk1_or_conn in r_list:
# 每一个连接对象
if sk1_or_conn == sk1:
# 表示有新用户来连接
conn, address = sk1_or_conn.accept()
inputs.append(conn) # inputs = [sk1,conn]
message[conn] = []
else:
try: # 有老用户发消息了
'''
2.7中,客户端断开连接,服务器会接收到空字符串
3.6中,断开后,服务器会报错
'''
data_bytes = sk1_or_conn.recv(1024)
except Exception as ex:
inputs.remove(sk1_or_conn)
# 从inputs中移除,不再联系
else:
data_str = str(data_bytes, encoding='utf-8')
outputs.append(sk1_or_conn) #w_list记录哪个客户端跟服务器发送过消息
message[sk1_or_conn].append(data_str )


# 本质上还是一个一个处理。只是事情一发生即可处理而已
# select的难点在于它的第一个参数,既放了服务端的socket对象,
# 还放了客户端与服务端的通道对象conn

# w_list仅仅保存了谁给我发过消息
for conn in w_list:
recv_str = message[conn][0]
del message[conn][0]
conn.sendall(bytes(recv_str + '好', encoding='utf-8'))
outputs.remove(conn)

  客户端:(在python中按快捷键shift+crtl+F10模拟不同的客户端)  

import socket

obj = socket.socket()
obj.connect(('127.0.0.1', 8001,))

while True:
inp = input('>>>')
obj.sendall(bytes(inp, encoding='utf-8'))
ret = str(obj.recv(1024), encoding='utf-8')
print(ret)