Python-Level2-day16:基于select/epoll方法实现IO多路复用并发模型;http协议概述与网页访问流程

3.3.4 IO多路复用

  • 定义

    系统同时监控多个IO事件,当哪个IO事件准备就绪就执行哪个IO事件。以此形成可以同时处理多个IO的行为,避免一个IO阻塞造成其他IO均无法执行,提高了IO执行效率。

  •  

  • 在单进程的状态下,IO想要实现跳过阻塞部分去执行非阻塞部分来提高程序的执行的效率,应用层是没有这样功能,需要借助于操作系统的轮循机制,不断寻找没有阻塞的部分去先执行。

  • 2个具体方案

    • select方法 : Windows Linux Unix

    • epoll方法: Linux --效率略高于select

  • select 方法

import select
rs, ws, xs=select(rlist, wlist, xlist,[timeout])
​
功能: 监控IO事件,阻塞等待IO的发生
    
参数: rlist  列表  读IO列表,添加等待发生的或者可读的IO事件
            读例如:read recv accept ---别的地方往程序里面搞东西的,经常被动阻塞 
      wlist  列表  写IO列表,存放要可以主动处理的或者可写的IO事件
            写例如:send write---程序往别的地方输出信息,经常主动不阻塞,实际很少用它。
      xlist  列表  异常IO列表,存放出现异常要处理的IO事件,基本在Linux下无用,系统不会帮你捕捉异常事              件,需要在我们应用层捕捉。因此基本不用他。
    
      timeout  超时时间---不写默认死等
    
返回值: rs 列表  rlist中准备就绪的IO
        ws 列表  wlist中准备就绪的IO
        xs 列表  xlist中准备就绪的IO
"""
    IO 多路复用方法  select
"""
from select import select
from socket import *
​
# 准备一些IO操作对象
​
file = open("../day15/my.log", 'rb')
# open创建的对象比较特殊,同时具备读与写事件
​
udp = socket(type=SOCK_DGRAM)  # 具有写事件已经就绪
​
tcp = socket()
tcp.bind(("0.0.0.0", 8888))
tcp.listen(5)  # 没有客户端连接就一直阻塞
​
# 监控IO
print("开始监控")
rs, ws, xs = select([tcp, file], [udp, file], [])
print("rlist:", rs)
print("wlist:", ws)
print("xlist:", xs)
​
from socket import *
​
# 服务器地址
ADDR = ("127.0.0.1",8888)
​
tcp_socket = socket()
tcp_socket.connect(ADDR)
​
while True:
    msg = input(">>")
    if not msg:
        break
    tcp_socket.send(msg.encode())
    data = tcp_socket.recv(1024)
    print("From server:",data.decode())
​
# 关闭
tcp_socket.close()
​
​

"""
单进程基于select 的IO多路复用并发模型
重点代码 !!
​
目标 : 服务端能够同时应对多个客户端发消息和连接
思路 : 将监听套接字与连接套接字一起监控
"""
from socket import *
from select import select
​
HOST = "0.0.0.0"
PORT = 8888
ADDR = (HOST, PORT)
​
# 监控列表
rlist = []
wlist = []
xlist = []
​
​
# 处理客户端连接
def connect_client(sock):
    connfd, addr = sock.accept()
    print("Connect from", addr)
    connfd.setblocking(False)  # 设置套接字为非阻塞IO
    rlist.append(connfd)
​
​
# 具体客户端发送请求
def handle(connfd):
    data = connfd.recv(1024)
    if not data:
        rlist.remove(connfd)  # 不关注
        connfd.close()
        return
    print(data.decode())
    connfd.send(b"OK")
​
​
# 启动服务函数
def main():
    # 创建监听套接字
    sock = socket()
    sock.bind(ADDR)
    sock.listen(5)
    sock.setblocking(False)  # 与非阻塞IO配合
    print("Listen the port %d" % PORT)
    rlist.append(sock)  # 初始关注监听套接字
    # 循环接收监控IO发生
    while True:
        rs, ws, xs = select(rlist, wlist, xlist)
        for r in rs:
            if r is sock:  # 判断对象用is最合适
                connect_client(r)  # 处理连接
            else:
                handle(r)  # 处理请求
​
​
if __name__ == '__main__':
    main()
​
"""
加入写到wlist
"""
from socket import *
from select import select
​
HOST = "0.0.0.0"
PORT = 8888
ADDR = (HOST, PORT)
​
# 启动服务函数
def main():
    # 创建监听套接字
    sock = socket()
    sock.bind(ADDR)
    sock.listen(5)
    sock.setblocking(False)  # 与非阻塞IO配合
    print("Listen the port %d" % PORT)
​
    # 监控列表
    rlist = [sock] # 初始关注监听套接字
    wlist = []
    xlist = []
​
    # 循环接收监控IO发生
    while True:
        rs, ws, xs = select(rlist, wlist, xlist)
        for r in rs:
            if r is sock:
                connfd, addr = r.accept() # 处理连接
                print("Connect from", addr)
                connfd.setblocking(False)
                rlist.append(connfd)
            else:
                data = r.recv(1024)
                if not data:
                    rlist.remove(r)  # 不关注
                    r.close()
                    continue
                print(data.decode())
                # r.send(b"OK")
                wlist.append(r) # 加入写到wlist
​
        for w in ws:
            w.send(b"OK")
            wlist.remove(w) # 移除
​
if __name__ == '__main__':
    main()
​

  • epoll方法

import select
ep = select.epoll()
功能 : 创建epoll对象
返回值: epoll对象
ep.register(fd,event)   
功能: 注册关注的IO事件
参数:fd  要关注的IO
     event  要关注的IO事件类型
           常用类型EPOLLIN  读IO事件(rlist)
                  EPOLLOUT 写IO事件 (wlist)
                  EPOLLERR 异常IO  (xlist)
e.g. ep.register(sockfd,EPOLLIN|EPOLLERR)#关注多个事件用 | 
​
ep.unregister(fd)
功能:取消对IO的关注
参数:IO对象 或者IO对象的fileno(即文件描述符)
​
  文件描述符 : Linux操作系统会给每个IO对象分配一个
             不重复的整数编号,这个编号就是文件描述符
events = ep.poll()
功能: 将关注的IO提交监控,阻塞等待监控的IO事件发生
返回值: 返回发生的IO
        events格式  [(fileno,event),()....]
        每个元组为一个就绪IO,其中元组第一项是该IO的fileno,第二项为该IO就绪的事件类型EPOLLIN,EPOLLOUT , EPOLLERR
"""
基于epoll 的IO多路复用并发模型
重点代码 !!
​
目标 : 服务端能够同时应对多个客户端发消息和连接
思路 : 将监听套接字与连接套接字一起监控
"""
from socket import *
from select import *
​
HOST = "0.0.0.0"
PORT = 8888
ADDR = (HOST, PORT)
​
# 查找字典 时刻与关注的IO对象一致 {fileno:object}
map = {}
​
​
# 处理客户端连接
def connect_client(ep, fd):
    connfd, addr = map[fd].accept()
    print("Connect from", addr)
    connfd.setblocking(False)
    # 添加新的关注 设置边缘触发
    ep.register(connfd, EPOLLIN|EPOLLET)
    map[connfd.fileno()] = connfd  # 维护字典
​
​
# 具体客户端发送请求
def handle(ep, fd):
    data = map[fd].recv(1024)
    if not data:
        ep.unregister(fd)
        map[fd].close()
        del map[fd]  # 从字典中移除
        return
    print(data.decode())
    map[fd].send(b"OK")
​
​
# 启动服务函数
def main():
    # 创建监听套接字
    sock = socket()
    sock.bind(ADDR)
    sock.listen(5)
    sock.setblocking(False)  # 与非阻塞IO配合
    print("Listen the port %d" % PORT)
​
    # 创建epoll对象
    ep = epoll()
    ep.register(sock, EPOLLIN)  # 初始关注监听套接字
    # 查找字典 时刻与关注的IO对象一致 {fileno:object}
    map[sock.fileno()] = sock
    # 循环接收监控IO发生
    while True:
        events = ep.poll()  # 监控函数 [(),()]
        print("你有新的IO需要处理哦",events)
        for fd, event in events:
            if fd == sock.fileno():
                connect_client(ep, fd)  # 处理连接
            # else:
            #     handle(ep, fd)  # 处理请求
​
​
if __name__ == '__main__':
    main()
​
"""
IO 多路复用方法  epoll
"""
from select import *
from socket import *
​
# 准备一些IO操作对象
file = open("../day15/my.log",'rb')
​
udp = socket(type=SOCK_DGRAM)
​
tcp = socket()
tcp.bind(("0.0.0.0",8888))
tcp.listen(5)
​
# 查找字典 {fileno : object}
map = {}
​
ep = epoll()
ep.register(tcp,EPOLLIN) # 读事件
map[tcp.fileno()] = tcp # 添加到字典
​
# 监控IO
print("开始监控")
events = ep.poll() # 阻塞等待
print("events:",events) # [(5,1)]
map[5].accept()
​
​
​

  • select 方法与epoll方法对比

    • epoll 效率比select要高

    • epoll 同时监控IO数量比select要多

    • epoll 支持EPOLLET触发方式

 

 

 

3.3.5 IO并发模型

利用IO多路复用等技术,同时处理多个客户端IO请求。

  • 优点 : 资源消耗少,能同时高效处理多个IO行为

  • 缺点 : 只针对处理并发产生的IO事件

  • 适用情况:HTTP请求,网络传输等都是IO行为,可以通过IO多路复用监控多个客户端的IO请求。

  • 网络并发服务实现过程

    【1】将套接字对象设置为关注的IO,通常设置为非阻塞状态。

    【2】通过IO多路复用方法提交,进行IO监控。

    【3】阻塞等待,当监控的IO有事件发生时结束阻塞。

    【4】遍历返回值列表,确定就绪的IO事件类型。

    【5】处理发生的IO事件。

    【6】继续循环监控IO发生。

IO与进程线程对比

两者表面上都是服务端应对多个服务端,前者是创建多进程线程实现,适合大型的底层框架结构,资源消耗多。后者是单进程,只用一个计算机内核,需要操作系统功能支持,利用等待阻塞的时间来完成,资源消耗少,轻量级,适合处理网络收发事件。工作中两者配合用,例如来一个用户开启一个进程专门应对客户端,客户端请求复杂情况下,在这个进程里面用IO多路复用减少多个请求的阻塞时间。

4. web服务

4.1 HTTP协议

4.1.1 协议概述

  • 用途 : 网页获取,数据的传输

  • 特点

    • 应用层协议,选择使用tcp进行数据传输

    • 简单,灵活,很多语言都有HTTP专门接口

    • 有丰富的请求类型

    • 可以传输的数据类型众多

4.1.2 网页访问流程

  1. 客户端(浏览器)通过tcp传输,发送http请求给服务端

  2. 服务端接收到http请求后进行解析

  3. 服务端处理请求内容,组织响应内容

  4. 服务端将响应内容以http响应格式发送给浏览器

  5. 浏览器接收到响应内容,解析展示

 

前端工程师用JavaScript(写行为动作)与HTML(写内容)与CSS(写样式),给到服务器的某个位置保存起来,因此网页就是一个文件。DNS是实现域名解析,把百度转换成IP地址,

 

 

前情回顾
​
1. 多进程多线程并发模型
   用函数编写 / 用类编写
​
2. ftp文件服务
​
   * 通信协议 响应: 响应情况  响应信息
   * 请求和处理请求的模型
    客户端发送请求-> 等待响应-> 根据响应不同分情况讨论
    接收请求->处理请求->根据处理情况不同发送响应
​
3. IO 模型
​
   输入(读)   输出(写)
   IO 密集 : IO多  耗时长  多阻塞  cpu消耗少
   计算密集 : 计算多 cpu消耗多 无阻塞  耗时短
​
4. 阻塞 IO  和  非阻塞 IO
​
   阻塞 IO : 默认 效率最低
​
   非阻塞IO : 将阻塞 --> 不阻塞
           sock.setblocking()
           sock.settimeout(3)
​
cookie :Python支持位运算
​
 位运算: 将整数转换为二进制数再按照位进行计算
​
   | 按位或  14 | 17
​
          1110
         10001
         11111 --> 31
​
​
   & 按位与  14 & 17
​
          1110
         10001
         00000 --> 0
​
Cookie :
​
  文件描述符 : Linux操作系统会给每个IO对象分配一个
  不重复的整数编号,这个编号就是文件描述符
​
  >=0的
​
​
训练 :  将select_server 改写为使用epoll方法来
实现.
​
​
select : 支持系统多,使用简单,只有水平出发
​
epoll : 效率比select高 , 同时监控IO数量多
         默认水平出发,但是支持边缘出发
​
水平触发 : 当有IO事件发生时如果不处理则一直提醒
​
​
作业 : 1. 聊天室和ftp文件服务器 最少二选一写一下
      2. 第二阶段 做一个 思维导图 把知识点梳理
​
​
​
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

dpq666dpq666

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值