各种概念及IO多路复用

同步、异步

函数或方法被调用的时候,调用者是否得到最终结果的。
直接得到最终结果的,就是同步调用
不直接得到最终结果的,就是异步调用。

阻塞、非阻塞

函数或方法调用的时候,是否立即返回
立即返回就是非阻塞调用
不立即返回就是阻塞调用。

区别

同步、异步,与阻塞、非阻塞不相关。
同步、异步强调的是,是否得到结果。
阻塞、非阻塞强调的是时间、是否等待。

同步与异步区别在于:调用者是否得到了想要的最终结果。
同步就是一直要执行到返回最终结果。
异步就是直接返回了,但是返回的不是最终结果。调用者不能通过这种调用得到结果,以后可以通过被调用者提供的某种方式(被调用者通知调用者,调用者反复查询、回调),来取回最终结果。

阻塞与非阻塞的区别在于,调用者是否还能干其他事。
阻塞,调用者就只能干等。
非阻塞,调用者可以先去忙会别的,不用一直等。

联系

同步阻塞,一直等到最终结果
同步非阻塞,得不到最终结果就一直反复询问。
异步阻塞:得到了不是最终结果,但是通过得到的结果等待可以得到最终结果,中间一直等待。
异步非阻塞:得到了不是最终结果,中间可以做其他事,直到得到结果。

操作系统知识
  • 在386之前,CPU工作在实模式下,之后,开始支持保护模式,对内存进行划分。
  • X86 CPU中有4种工作级别
    • Ring 0 级,可以执行特权指令,可以访问所有级别数 据,可以访问IO设备等
    • Ring 3 级,级别最低,只能访问本级别数据
    • 内核代码运行在Ring0 ,用户代码运行在Ring3。
  • 现代操作系统采用虚拟存储器,理论上,对32位系统来说,进程对虚拟内存地址的内存寻址空间为4G(232),64位操作系统理论上最大内存寻址空间(264)。
  • 操作系统中,内核程序独立且运行在较高的特权级别上,它们驻留在被保护的内存空间上,期间访问硬件设备的所有权限,这部分内存成为内核空间(内核态,最高地址1G)
  • 普通应用程序运行在用户空间(用户态)
  • 应用程序想访问某些硬件资源就需要通过操作系统提供的系统调用,系统调用可以使用特权指令在内核空间,此时进程陷入内核运行。系统调用完成,进程将返回用户态执行用户空间代码
同步IO、异步IO、IO多路复用
  • IO两个阶段:
    • 1、数据准备阶段
    • 2、内核空间复制回用户空间进程缓冲区阶段
  • 发生IO的时候
    • 1、内核从IO设备读、写数据
    • 2、进程从内核复制数据
IO模型

同步IO模型包括阻塞IO、非阻塞IO、IO多路复用

  • 阻塞IO
    在这里插入图片描述
    进程等待(阻塞),直到读写完成。(全程等待)

  • 非阻塞IO
    在这里插入图片描述
    进程调用read操作,如果IO设备没有准备好,立即返回ERROR,进程不阻塞。用户可以再次发起系统调用,如果内核已经准备好,就阻塞,然后复制数据到用户空间。
    第一阶段数据没有准备好,就先忙别的,等会再来看看,检查数据是否准备好了的过程是非阻塞的
    第二阶段是阻塞的,即内核空间和用户空间之间复制数据是阻塞的。

  • IO多路复用
    所谓IO多路复用,就是同时监控多个IO,有一个准备好了,就不需要等待开始处理,提高了处理IO的能力
    select几乎所有操作系统平台都支持,poll是对select的升级。
    epoll,Linux系统内核2.5+开始支持,对select和poll的增强,在监视的基础上,增加回调机制。
    在这里插入图片描述
    以select为例,将关注的IO操作告诉select函数并调用,进程阻塞,内核“监视”select关注的文件描述符fd,被关注的任何一个fd对应的IO准备好了数据,select返回,再使用read将数据复制到用户进程。
    一般情况下,select最多能监听1024fd,但是由于select采用轮询的方式,当管理IO多了,每次都要遍历fd,效率低下

  • 异步IO
    在这里插入图片描述
    进程发起异步IO请求,立即返回。内核完成IO的两个阶段,内核给进程发一个信号。

Python中IO多路复用
  • IO多路复用
    • 大多数操作系统都支持select和poll
    • Linux2.5+支持epoll
    • BSD、Mac支持kqueue
    • Windows的IOCP

Python的select库实现了select、poll系统调用,这个基本上操作系统都支持,部分实现了epoll。它是底层的IO多路复用模块。
开发中的选择:
1、完全跨平台,使用select、poll。但是性能较差
2、针对不同操作系统自行选择支持的技术,这样做会提高IO处理的性能
select维护一个文件描述符数据结构,单个进程使用有上限,通常是1024,线性扫描这个数据结构,效率低。
pool和select的却别是内部数据结构使用链表,没有最大限制,但是依然是线性遍历才知道哪个设备就绪了。
epool使用事件通知机制,使用回调机制提高效率。
select/pool还要从内核空间复制消息到用户空间,而epoll通过内核空间和用户空间分享一块内存来减少复制。

selectors库

3.4版本提供selectors库,高级IO复用库


类层次结构︰
BaseSelector
+-- SelectSelector 实现select
+-- PollSelector 实现poll
+-- EpollSelector 实现epoll
+-- DevpollSelector 实现devpoll
+-- KqueueSelector 实现kqueue

selectors.DefaultSelector返回当前最有效、性能最好的选择。
但是,由于没有实现Windows下的IOCP,所以,Windows下只能退化成select。
在这里插入图片描述

abstractmethod register(fileobj, events, data=None)

为selector注册一个文件对象,监视它的IO事件,返回selectKey对象。
fileobj被监视文件对象。
events事件,该对象文件必须等待的事件
data可选的与此文件对象相关联的不透明数据。

Event常量含义
EVENT_READ可读0b01,内核已经准备好输入输出设备,可以开始读了
EVENT_WRITE可写0b10,内核准备好了,可以往里写了

selectors.SelectorKey有4个属性:
1、fileobj注册的文件对象
2、fd文件描述符
3、events等待上面的文件描述符的文件对象的事件
4、data注册时关联的数据

import selectors
import threading
import socket
import logging
import time
FORMAT="%(asctime)s %(threadName)s %(thread)s %(message)s"
logging.basicConfig(format=FORMAT,level=logging.INFO)
#构建最优selector
selector=selectors.DefaultSelector()

sock=socket.socket()
sock.bind(("127.0.0.1",9999))
sock.listen()
logging.info(sock)

#回调函数,sock的读事件
def accept(sock:socket.socket,mak):
    con,raddr=sock.accept()
    con.setblocking(False)#非阻塞

    logging.info('new socket is {}'.format(con))
# 注册sock的被关注的事件,返回selectorKey对象
# key记录了fileobj ,fileobj的fd,events,data

key=selector.register(sock,selectors.EVENT_READ,accept)
logging.info("key====={}".format(key))

while True:
    # 监听注册的对象的事件,发生被关注事件则返回events
    events=selector.select()
    print(events,111111111111)
    # 表示关注对象的某事件发生了
    for key ,mask in events:
        callback=key.data
        callback(key.fileobj,mask)

在这里插入图片描述
增加客户端与服务器交互

import selectors
import threading
import socket
import logging
import time
FORMAT="%(asctime)s %(threadName)s %(thread)s %(message)s"
logging.basicConfig(format=FORMAT,level=logging.INFO)
#构建最优selector
selector=selectors.DefaultSelector()

sock=socket.socket()
sock.bind(("127.0.0.1",9999))
sock.listen()
logging.info(sock)

#回调函数,sock的读事件
def accept(sock:socket.socket,mak):
    con,raddr=sock.accept()
    con.setblocking(False)#非阻塞

    logging.info('new socket is {}'.format(con))
    key=selector.register(con,selectors.EVENT_READ,read)
    logging.info("accept----{}".format(key))
def read(con:socket.socket,mask):
    data=con.recv(1024)
    msg="msg===={}".format(data.decode())
    logging.info(msg)
    con.send(msg.encode())
# 注册sock的被关注的事件,返回selectorKey对象
# key记录了fileobj ,fileobj的fd,events,data

key=selector.register(sock,selectors.EVENT_READ,accept)
logging.info("key====={}".format(key))
datal=0
while True:
    print(datal)
    # 监听注册的对象的事件,发生被关注事件则返回events
    events=selector.select()
    print(events,111111111111)
   # 表示关注对象的某事件发生了
    for key ,mask in events:
        callback=key.data
        callback(key.fileobj,mask)
    datal+=1

完整版服务器

import selectors
import threading
import socket
import logging
import time
FORMAT="%(asctime)s %(threadName)s %(thread)s %(message)s"
logging.basicConfig(format=FORMAT,level=logging.INFO)
class ChatServer:
    def __init__(self,ip='127.0.0.1',port=9999):
        self.sock=socket.socket()
        self.addr=ip,port
        self.event=threading.Event()
        # 构建最优selector
        self.selector=selectors.DefaultSelector()
    def start(self):
        self.sock.bind(self.addr)
        self.sock.listen()
        self.sock.setblocking(False)
        self.selector.register(self.sock,selectors.EVENT_READ,self.accept)
        threading.Thread(target=self.select,name='selectlt',daemon=True).start()
    def select(self):
        while not self.event.is_set():
            events=self.selector.select()
            print(events)
            for key,mask in events:
                callback=key.data
                callback(key.fileobj,mask)
    def accept(self,sock:socket.socket,mak):
        con,raddr=sock.accept()
        con.setblocking(False)
        logging.info("newsock-------{}".format(con))
        key=self.selector.register(con,selectors.EVENT_READ,self.recv)
        logging.info("this is key------{}".format(key))
    def recv(self,con:socket.socket,mak):
        data=con.recv(1024)
        data=data.strip()
        if data==b"quit" or data==b"":
            self.selector.unregister(con)
            con.close()
            return
        msg="your msg====={}".format(data.decode()).encode()
        logging.info(msg)
        for key in self.selector.get_map().values():
            if key.data==self.recv:
                key.fileobj.send(msg)
    def stop(self):
        self.event.set()
        fobjs=[]
        for fd,key in self.selector.get_map().items():
            fobjs.append(key.fileobj)
        for fobj in fobjs:
            self.selector.unregister(fobj)
            fobj.close()
        self.selector.close()
if __name__=="__main__":
    cs=ChatServer()
    cs.start()
    while True:
        cmd=input('>>>')
        if cmd.strip()=='quit':
            logging.info('quit')
            cs.stop()
            break
        print(threading.enumerate())
  • 0
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值