I/O多路复用

I/O多路复用
# 同步:提交一个任务之后要等待这个任务执行完毕
#
# 异步:只管提交任务,不等待这个任务执行完毕就可以去做其他的事情
#
# 阻塞:recv、recvfrom、accept,线程阶段  运行状态-->阻塞状态-->就绪
#
# 非阻塞:没有阻塞状态
# 对于服务端在recv和send,accetpt的时候都会处于阻塞的状态,这导致了线程服务执行或者是
# 计算任何的网络请求
# low版解决方案:
# 在服务端使用多线程或者是多进程的方式,让每个连接都有自己的线程或者是进程
# 存在问题: 如果线路过多的时候,会严重占用系统资源,减低系统对外界的响应效率,且他们本生也存在假死的状态
#
# 改进方案》
# “线程池”旨在减少创建和销毁线程的频率,其维持一定合理数量的线程,并让空闲的线程重新承担新的执行任务。“连接池”维持连接的缓存池
# ,尽量重用已有的连接、减少创建和关闭连接的频率。
# 这两种技术都可以很好的降低系统开销,都被广泛应用很多大型系统,如websphere、tomcat和各种数据库等

# 改进方案后存在的问题》》
# 面临的可能同时出现的上千甚至上万次的客户端请求,“线程池”或“连接池”或许可以缓解部分压力,但是不能解决所有问题。
# 总之,多线程模型可以方便高效的解决小规模的服务请求,
# 但面对大规模的服务请求,多线程模型也会遇到瓶颈,可以用非阻塞接口来尝试解决这个问题(不推荐使用)

1.2非阻塞(不推荐)
import socket
import time
server = socket.socket()
ip = ('127.0.0.1',8080)
server.bind(ip)
server.listen()

server.setblocking(False)
connlist = []
while 1:
    while 1:
        try:
            con,add = server.accept()#设置为非阻塞的时候,如果没有人连接的直接报错,
            connlist.append(con)
            break
        except Exception:
            print('没有人连接我')
            time.sleep(0.1) #减少CPU的消耗
    for con in connlist:
        while 1:
            try:
                msg = con.recv(1024).decode('utf-8')
                print(msg)
                hh = 'qingshur '.encode('utf-8')
                con.send(hh)
                break
            except Exception:
                print('么有消息')
非阻塞服务端

import socket
client = socket.socket()
client.connect(('127.0.0.1',8080))
while 1:
    to_ser = input('我想说>>>>>').encode('utf-8')
    client.send(to_ser)
    msg = client.recv(1024).decode('utf-8')
    print(msg)
非阻塞客户端

 

使用的场景
1. 如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking
IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。

2. 在多路复用模型中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,
整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。
选择的场景

I/O 多路复用

1.3select的基本用法

# import select
#
# fd_r_list, fd_w_list, fd_e_list = select.select(rlist, wlist, xlist, [timeout])
#
# 参数: 可接受四个参数(前三个必须)
#     rlist: wait until ready for reading  #等待读的对象,你需要监听的需要获取数据的对象列表
#     wlist: wait until ready for writing  #等待写的对象,你需要写一些内容的时候,input等等,也就是说我会循环他看看是否有需要发送的消息,如果有我取出这个对象的消息并发送出去,一般用不到,这里我们也给一个[]。
#     xlist: wait for an “exceptional condition”  #等待异常的对象,一些额外的情况,一般用不到,但是必须传,那么我们就给他一个[]。
#     timeout: 超时时间
#     当超时时间 = n(正整数)时,那么如果监听的句柄均无任何变化,则select会阻塞n秒,之后返回三个空列表,如果监听的句柄有变化,则直接执行。
# 返回值:三个列表与上面的三个参数列表是对应的
#   select方法用来监视文件描述符(当文件描述符条件不满足时,select会阻塞),当某个文件描述符状态改变后,会返回三个列表
#     1、当参数1 序列中的fd满足“可读”条件时,则获取发生变化的fd并添加到fd_r_list中
#     2、当参数2 序列中含有fd时,则将该序列中所有的fd添加到 fd_w_list中
#     3、当参数3 序列中的fd发生错误时,则将该发生错误的fd添加到 fd_e_list中
#     4、当超时时间为空,则select会一直阻塞,直到监听的句柄发生变化
#
# # select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。
# # 它的基本原理就是select/epoll这个function会不断的轮询所负责的所有socket,
# # 当某个socket有数据到达了,就通知用户进程
select的基本用法

1.4简单版的多路复用

1.4.1,服务端

mport select
import socket
server = socket.socket()
server_ip  = ('127.0.0.1',8080)
server.bind(server_ip)
rlist = [server,]#要监听的对象
server.listen()

while 1:
    print('*****')
    r1,w1,e1 = select.select(rlist,[],[])
    print(r1)
    for sock in r1:
        print(r1)
        if sock == server:
            conn,addr = sock.accept()
            rlist.append(conn)
        else:
            from_client_msg = sock.recv(1024)
            print(from_client_msg.decode('utf-8'))
服务端
mport socket
client = socket.socket()
client.connect(('127.0.0.1',8080))
while 1:
    to_ser = input('我想说>>>>>').encode('utf-8')
    client.send(to_ser)
    msg = client.recv(1024).decode('utf-8')
    print(msg)
客户端

 




 

posted @ 2018-12-04 21:30  好大一个圈  阅读(197)  评论(0编辑  收藏  举报