python自动化开发学习-10 I/O多路复用

一. 简介

  socketserver在内部是由I/O多路复用,多线程和多进程,实现了并发通信。IO多路复用的系统消耗很小。

IO多路复用底层就是监听socket对象内部是否有变化,是否在收发消息,Python中select模块提供了select poll epoll 三种方式来实现IO多路复用,支持不同的操作系统。

  windows : 提供select

  Mac : 提供select

  Linux : 提供select, poll, epoll

(1)select简介:

  select 默认会将每一个变化的socket加入一个他自己维护的列表中,但是并不会明确是哪一个socket,它内部其实是通过一个for循环遍历整个列表,当列表中的socket很多的时候,for循环就会浪费资源。select默认可以监听1024个链接,它是由linux在同文件中定义的,可修改。select不是安全的线程,如果你把一个socket加入到select中,突然另外一个线程关闭了这个socket,那么接下来select的行为是随机的。

(2)poll简介:

  poll修复了select的一些问题,例如去掉了1024个链接限制,想要监听多少都可以,poll不在修改传入的数组,但是poll依然不是线程安全的。

(3)epoll简介:

  epoll不仅会告诉你socket组里面的数据,还会告诉你具体哪个socket有数据,无需自己寻找。而且epoll终于是线程安全的了。然而epoll局限于

Linux操作系统。

二 . 应用

select 方法

句柄列表11, 句柄列表22, 句柄列表33 = select.select(句柄序列1, 句柄序列2, 句柄序列3, 超时时间)<br>
参数: 可接受四个参数(前三个必须)
返回值:三个列表
  
select方法用来监视文件句柄,如果句柄发生变化,则获取该句柄。
1、当 参数1 序列中的句柄发生可读时(accetp和read),则获取发生变化的句柄并添加到 返回值1 序列中
2、当 参数2 序列中含有句柄时,则将该序列中所有的句柄添加到 返回值2 序列中
3、当 参数3 序列中的句柄发生错误时,则将该发生错误的句柄添加到 返回值3 序列中
4、当 超时时间 未设置,则select会一直阻塞,直到监听的句柄发生变化
   当 超时时间 = 1时,那么如果监听的句柄均无任何变化,则select会阻塞 1 秒,之后返回三个空列表,如果监听的句柄有变化,则直接执行。

 

(1)监听客户端连接

import socket
import select

sk = socket.socket()
sk.bind(("127.0.0.1", 9999,))
sk.listen(5)

while True:
    rlist, w, e = select.select([sk, ], [], [], 5)
    # 监听sk(服务器端)对象,如果sk对象发生变化,表示有客户端来连接了,此时rlist值为[sk]
    # 监听conn对象,如果conn发生变化,表示客户端有消息发送过来了,此时rlist的值为[客户端]
    print(rlist)

    for r in rlist:
        print(r)
        conn, address = r.accept()
        conn.sendall(bytes("hello", encoding="utf-8"))
服务端
import socket

sk = socket.socket()
sk.connect(("127.0.0.1", 9999,))

data = sk.recv(1024)
print(data)

while True:
    input(">>")

sk.close()
客户端

 

(2)监听客户端连接及接受客户端发送的数据

import socket
import select

sk = socket.socket()
sk.bind(("127.0.0.1", 9999,))
sk.listen(5)


inputs = [sk,]
while True:
    rlist, w, e = select.select(inputs, [], [], 1)
    # 监听sk(服务器端)对象,如果sk对象发生变化,表示有客户端来连接了,此时rlist值为[sk]
    # 监听conn对象,如果conn发生变化,表示客户端有消息发送过来了,此时rlist的值为[客户端]

    print(len(inputs), len(rlist))

    for r in rlist:
        print(r)
        if r == sk:
            # 有新的客户端连接进来
            conn, address = r.accept()
            inputs.append(conn)
            conn.sendall(bytes("hello", encoding="utf-8"))
        else:
            # 有客户端给我发送数据
            recv_data = r.recv(1024)
            print(recv_data)
服务端
import socket

sk = socket.socket()
sk.connect(("127.0.0.1", 9999,))

data = sk.recv(1024)
print(data)

while True:
    inp = input(">>")
    sk.sendall(bytes(inp, encoding='utf-8'))

sk.close()
客户端

 

(3)读写分离

import socket
import select

sk = socket.socket()
sk.bind(("127.0.0.1", 9999,))
sk.listen(5)


inputs = [sk,]
outputs = []

while True:
    rlist, wlist, e = select.select(inputs, outputs, [], 1)
    # 监听sk(服务器端)对象,如果sk对象发生变化,表示有客户端来连接了,此时rlist值为[sk]
    # 监听conn对象,如果conn发生变化,表示客户端有消息发送过来了,此时rlist的值为[客户端]

    print(len(inputs), len(rlist))

    for r in rlist:
        print(r)
        if r == sk:
            # 有新的客户端连接进来
            conn, address = r.accept()
            inputs.append(conn)
            conn.sendall(bytes("hello", encoding="utf-8"))
        else:
            # 有客户端给我发送数据
            try:
                recv_data = r.recv(1024)

                if not recv_data: # 某些操作系统,在socket断开的时候,会发送一个空给server,所以要判断空的情况
                    raise Exception("断开连接")
                else:
                    outputs.append(r)
            except Exception as e:
                inputs.remove(r)

    for w in wlist:
        w.sendall(bytes("response", encoding="utf-8"))
        outputs.remove(w)
服务端
import socket

sk = socket.socket()
sk.connect(("127.0.0.1", 9999,))

data = sk.recv(1024)
print(data)

while True:
    inp = input(">>")
    sk.sendall(bytes(inp, encoding='utf-8'))
    print(sk.recv(1024))
sk.close()
客户端

 

(4)读写分离之服务端根据客户端的输入返回对应的信息

import socket
import select

sk = socket.socket()
sk.bind(("127.0.0.1", 9999,))
sk.listen(5)


inputs = [sk, ]  #
outputs = []
messages = {}   # 用来存放客户端发送过来的消息,存放格式   连接客户端的对象: 消息

while True:
    rlist, wlist, elist = select.select(inputs, outputs, [sk, ], 1)
    # rlist  监听sk(服务器端)对象,如果sk对象发生变化,表示有客户端来连接了,此时rlist值为[sk]
    # wlist  监听conn对象,如果conn发生变化,表示客户端有消息发送过来了,此时rlist的值为[客户端]
    # elist  监听sk(服务器端)对象是否发生错误

    print(len(inputs), len(rlist))

    for r in rlist:
        print(r)
        if r == sk:
            # 有新的客户端连接进来
            conn, address = r.accept()
            inputs.append(conn)
            messages[conn] = []  # 在存储消息的字典中创建一个以客户端连接对象为key 空列表为value的键值对
            conn.sendall(bytes("hello", encoding="utf-8"))
        else:
            # 有客户端给我发送数据
            try:
                recv_data = r.recv(1024)

                if not recv_data: # 某些操作系统,在socket断开的时候,会发送一个空给server,所以要判断空的情况
                    raise Exception("断开连接")
                else:
                    outputs.append(r)
                    messages[r].append(recv_data)

            except Exception as e:
                inputs.remove(r)
                del messages[r]

    for w in wlist:
        print(messages[w])
        msg = messages[w].pop()
        resp = msg + bytes("response", encoding="utf-8")
        w.sendall(resp)
        outputs.remove(w)
服务端
import socket

sk = socket.socket()
sk.connect(("127.0.0.1", 9999,))

data = sk.recv(1024)
print(data)

while True:
    inp = input(">>")
    sk.sendall(bytes(inp, encoding='utf-8'))
    print(sk.recv(1024))
sk.close()
客户端

 

  

  

posted @ 2016-07-15 15:33  王永存ღ  阅读(329)  评论(0编辑  收藏  举报