IO多路复用

IO多路复用一共三种:select、poll、epoll

select,监听个数最多1024个,内部为for循环,所以效率较差,Windows只支持它。

poll为改进,没有了个数限制,内部依然是for循环

epoll,革新了多路复用,内部为异步方式,selectpoll去循环监听的对象,发现有变化的对象对其进行操作,epoll不进行循环,其中的对象有变化自动告诉它。

 

用select使socket同时监听三个端口:

服务器端:

import socket #创建第一个端口 sk1
= socket.socket() sk1.bind(("127.0.0.1",8001),) sk2.listen(5) #创建第二个端口 sk2 = socket.socket() sk2.bind(("127.0.0.1",8002,)) sk2.listen(5) 创建第三个端口 sk3 = socket.socket() sk3.bind(("127.0.0.1",8003,)) sk3.listen(5) #将三个对象放到一个列表中 inputs = [sk1, sk2, sk3]
import
select while True:
  #select内部自动监听sk1、sk2、sk3三个对象,一旦那个发生变化,就对其执行相关操作
  #如果有人连接8001,那么r_list = [sk1],如果8001和8002同时连接,r_list = [sk1,sk2]
r_list, w_list, e_list
= seletc.select(inputs,[],[],1) for sk in r_list:
     #每一个连接对象 conn,address
= sk.accept()' con.sendall(bytes("hello",encoding = "utf-8" )) conn.close()

客户端1:

import socket

obj = socket.socket()
obj.connect(("127.0.0.1",8001))
obj.recv(1024)
obj.close()

这样,三个客户端同时连接服务器,都可以收到服务器回复的"hello"。

讲解一下上面服务器的select的几个参数:

import socket
#创建第一个端口
sk1 = socket.socket()
sk1.bind(("127.0.0.1",8001),)
sk2.listen(5)
#创建第二个端口
sk2 = socket.socket()
sk2.bind(("127.0.0.1",8002,))
sk2.listen(5)
创建第三个端口
sk3 = socket.socket()
sk3.bind(("127.0.0.1",8003,))
sk3.listen(5)
#将三个对象放到一个列表中
inputs = [sk1, sk2, sk3] 

import select

while True:
  #select内部自动监听sk1、sk2、sk3三个对象,一旦那个发生变化,就对其执行相关操作
  #如果有人连接8001,那么r_list = [sk1],如果8001和8002同时连接,r_list = [sk1,sk2]
  #第一个参数传了"inputs",这会监听它其中包含的三个对象,如果对象有变化,就将其写入r_list。第二个指定什么参数,就不再变化,第三个是如果对象socket发生错误,将其写入e_list,后面一个"1"表示停留一秒,如果没有对象变化,继续执行 r_list, w_list, e_list
= seletc.select(inputs,[sk1,sk2],inputs,1) for sk in r_list:      #每一个连接对象 conn,address = sk.accept()' con.sendall(bytes("hello",encoding = "utf-8" )) conn.close()
  #e_list包含的是错误列表,所以可以循环这个错误列表,将其从inputs移除
  for sk in e_list:
    inputs.remove(sk)

 

下面是一个例子:

多个客户端都可以连接到服务器,但是实际上处理任务并不是并行的。

select监听服务器端和客户端的socket,服务端端socket是sk1,监听sk1,一旦有新的客户端socket连接,就获取其conn加入监听列表,如果conn有变化(发送数据),也会监听到,于是执行else模块,服务器端接收消息。

服务器端:

import socket
import select

sk1 = socket.socket()
sk1.bind(("127.0.0.1", 8009,))
sk1.listen()

inputs = [sk1, ]

while True:
r_list, w_list, e_list = select.select(inputs, [], inputs, 1)
print('正在监听%d个对象' % len(inputs))
print(r_list)
for sk_or_conn in r_list: #如果sk_or_conn = sk1,那就是有客户端第一次连接,获取连接的conn添加到select监听列表
if sk_or_conn == sk1:
conn, address = sk.accept()
inputs.append(conn)

else: #else表示sk_or_conn不等于sk1,那它就是感知到了conn有变化,就是客户端要发送数据,此时sk_or_conn里面到值是conn
try:
by = sk_or_conn.recv(1024)

except Exception as e: #客户端退出会报异常,我们截获异常,将退出的客户端移除
inputs.remove(sk_or_conn)
        else:
          str1 = str(by, encoding="utf-8")
          sk_or_conn.sendall(bytes(str1+"ok!", encoding="utf-8"))
特别注意r_list的值,因为select监听服务器端(sk1)和客户端两个(conn)socket,如果客户端第一次连接,select感知到到是sk1,那么r_list的值就是sk1,如果是已经连接的客户端发送消息,
那么感知到到是conn,那么r_list的值是conn
 
    

客户端1:
import socket

obj = socket.socket()
obj.connect(("127.0.0.1",8009))

while 1:
obj.sendall(bytes(input("input:"),encoding="utf-8"))
by =obj.recv(1024)
st = str(by,encoding="utf-8")
print(st)

 

上面的例子中,只用了select.select(arg1,arg2,arg3,agr4)第一个参数进行读写,下面用第一个参数读取客户端,第二个参数发送,实现读写分离:

服务器端:
import socket
import select

sk = socket.socket()
sk.bind(("127.0.0.1", 8129))
sk.listen()
inputs = [sk]
outputs = []
while True:
    r_list, w_list, e_list = select.select(inputs, outputs, inputs, 1)
    print("正在监听:", len(inputs))
    print("this is outputs",len(outputs))
    for con_or_sk in r_list:
        if con_or_sk == sk:
            conn, address = sk.accept()
            inputs.append(conn)
        else:
            try:
                con_or_sk.recv(1024)
            except Exception as ex:
                print("message:",ex)
                inputs.remove(con_or_sk)
            else:
                outputs.append(con_or_sk) #将con_or_sk添加到第二个参数outputs去发送消息给客户端
    for item in w_list:
        try:
            item.sendall(bytes("hello",encoding="utf-8"))
        except Exception as ex:
            print("无发送对象",ex)
            outputs.remove(item)

    for item in e_list:
        inputs.remove(item)

客户端:
import socket

while 1:
    obj = socket.socket()
    obj.connect(("127.0.0.1", 8129))
    obj.sendall(bytes(input("input:"),encoding="utf-8"))
    by =obj.recv(1024)
    st = str(by,encoding="utf-8")
    print(st)
    obj.close()

 

还是上面的例子扩展,服务器将客户端连接和消息放在一个字典中,服务器再调用字典将消息发送回去:

服务器端:
import socket
import select

sk = socket.socket()
sk.bind(("127.0.0.1", 8192))
sk.listen()
inputs = [sk]
outputs = []
#定义一个空字典接收客户端的连接和发送过来的消息,给客户端发送时获取这个字典,返回客户端发过来的消息
message = {}
while True:
    r_list, w_list, e_list = select.select(inputs, outputs, inputs, 1)
    print("正在监听:", len(inputs))
    print("this is outputs",len(outputs))
    for con_or_sk in r_list:
        if con_or_sk == sk:
            conn, address = sk.accept()
            inputs.append(conn)
            #将客户端连接作为字典的KEY存入字典
            message[conn] = []
        else:
            try:
                recby = con_or_sk.recv(1024)
                recstr = str(recby,encoding="utf-8")

            except Exception as ex:
                print("message:",ex)
                inputs.remove(con_or_sk)
            else:
                message[con_or_sk].append(recstr)
                outputs.append(con_or_sk)
                print(message)
    for item in w_list:
        try:
            recmeg = message[item][0]
            print("this is recmeg",type(recmeg))
            print("thhis is item", item)
            item.sendall(bytes(recmeg,encoding="utf-8"))

        except Exception as ex:
            print("无发送对象",ex)
            outputs.remove(item)
        else:
            del message[item][0]
            outputs.remove(item)

    for item in e_list:
        inputs.remove(item)

客户端:
import socket

while 1:
    obj = socket.socket()
    obj.connect(("127.0.0.1", 8192))
    obj.sendall(bytes(input("input:"),encoding="utf-8"))
    by =obj.recv(1024)
    st = str(by,encoding="utf-8")
    print(st)
    obj.close()

 

posted @ 2018-10-16 12:42  greenfan  阅读(153)  评论(0)    收藏  举报