天天看点

I O 分类和多路复用 select poll

IO(input output)

    在内存中存在数据交换的操作都可以认为是IO操作

    在终端交互: input   print

    和磁盘交互: read    write

    和网络交互: recv    send

    IO 密集型程序: 在程序执行过程中存在大量IO操作,而cpu运算操作较少.消耗cpu较少,运行效率较低

    计算密集型程序(cpu密集型程序):在程序执行中cpu运算较多,IO操作相对较少.

    消耗cpu大,运行速度快

IO分类:

    阻塞 IO   非阻塞IO    IO多路复用

    阻塞IO是IO的默认形态,是效率比较低的一种IO情形.

        阻塞情况

        *因为某种条件没有达成造成的阻塞

        eg:accept     input     recv

        *处理IO数据传输时间较长形成的阻塞

        eg:网络传输过程,文件读写过程

    非阻塞IO:通过修改IO事件的属性,使其变为非阻塞 状态,(让一些条件阻塞函数不再阻塞)

    *非阻塞和IO往往和循环判断一起使用

    s.setblocking(False)

    将套接字设置为非阻塞状态

    超时检测

        将原本阻塞的函数设置一个最长阻塞时间,如果时间内条件达成则正常运行,如果仍然阻塞则视为超时,继续向下运行或产生异常

    s.settimeout(sec)

    设置套接字的超时时间

from socket import *
from time import sleep, ctime

s = socket()
s.bind(('127.0.0.1', 8888))
s.listen(3)

# 将套接字设置为非阻塞
s.setblocking(False)

while True:
    print('waiting for connect.......')
    try:
        c, addr = s.accept()
    except BlockingIOError:
        sleep(2)
        print(ctime())
        continue

    else:
        print('connect from', addr)
        while True:
            data = c.recv(1024).decode()
            if not data:
                break
            print(data)
            c.send(ctime().encode)
        c.close()
    s.close()
           

IO多路复用

    定义:同时监控多个IO事件,当哪个IO事件准备就绪就执行哪个IO事件,以此形成可用同时操作多个IO     的并发行为,避免一个IO阻塞,造成多个IO都无法执行.

    IO准备就绪: 是一种IO必然要发生的临界状态

    IO多路复用的编程实现

        1.将IO设置为关注IO

        2.将关注IO提交给内核监测

        3.处理内核给我们反馈的准备就绪的IO

    具体方案:

        select---->window linux  unix 

        poll  ---->linux  unix

        epoll ---->linux  unix

    import select

    rs,ws,xs = select(rlist,wlist,xlist[,timeout])

    功能:监控IO事件,阻塞等待IO事件发生

    参数:rlist    列表     存放我们监控等待处理的IO事件

        wlist    列表     存放我们要主动操作的IO事件

        xlist    列表     我们要关注出错处理的IO事件

        timeout  超时时间 

    返回值: rs  列表  rlist中准备就绪的IO

           ws  列表  wlist中准备就绪的IO

           xs  列表  xlist中准备就绪的IO

    注意:1.wlist中如果有IO事件则select立即返回ws

        2.在处理IO过程中不要处理一个客户端长期占有服务端使服务端无法运行到select的情况

        3.IO多路复用占用计算机资源少,io效率高

eg:

#select_server.py
from select import select
from socket import *

# 创建套接字作为我们关注的IO
s = socket()
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
s.bind(('0.0.0.0', 8888))
s.listen(5)

rlist = [s]
wlist = []
xlist = []

# 提交监测我们关注的IO等待IO的发生
while True:
    rs, ws, xs = select(rlist, wlist, xlist)
    for r in rs:
        if r is s:
            c, addr = r.accept()
            print('Connect from', addr)
            rlist.append(c)  # 添加到关注列表
        else:
            data = r.recv(1024)
            if not data:
                rlist.remove(r)
                r.close()
            else:
                print(data.decode())
                # 将客户端套接字放在wlist中
                wlist.append(r)
    for w in ws:
        w.send(b'Receive your message:')
        wlist.remove(w)
    for x in xs:
        if x is s:
            s.close()
           

位运算

    整数按照二进制进行运算

    & 按位与    |按位或  ^按位异或

    << 左移     >> 右移

    11  1011

    14  1110

    &   1010 --->10   一0则0

    |   1111 --->15   一1则1 

    ^   0101 --->5    相同为0不同为1

    11  << 2  101100 --->44  左移2位,空出来的位置补0               

    14  >> 2  11 --->3

poll

    1.创建poll对象

        p = select.poll()

    2.添加注册事件

        p.register(s,POLLIN | POLLERR)

        POLLIN  POLLOUT  POLLERR  POLLHUP  POLLNVAL

        rlist    wlist    xlist     断开    无效数据      

        p.unregister(s) 从关注事件中移除

    3.阻塞等待IO发生

        events = p.poll()

        功能:阻塞等待IO发生

        返回值: events 是一个列表,列表中每一个元素都是一个元组,代表一个发生的IO事件

        [(fileno,       event),(),()....]

        就绪的IO的文件描述符      具体就绪事件

        * 需要通过文件描述符(fileno)找到对应的IO对象

        {s.fileno(): s}

    4.处理具体的IO

eg:

#poll_server.py
from socket import *
from select import *

# 创建套接字作为我们关注的IO
s = socket()
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
s.bind(('0.0.0.0', 8888))
s.listen(5)

# 创建poll对象
p = poll()
# fileno --->IO对象的字典
fdmap = {s.fileno(): s}
# 注册关注的IO
p.register(s, POLLIN | POLLERR)

while True:
    # 进行IO监控
    events = p.poll()
    for fd, event in events:
        if fd == s.fileno():
            c, addr = fdmap[fd].accept()
            print('Connect from', addr)
            # 添加新的关注事件
            p.register(c, POLLIN | POLLHUP)
            fdmap[c.fileno()] = c  # fdmap = {s.fileno(): s,c.fileno: c}
        elif event & POLLIN:
            data = fdmap[fd].recv(1024)
            if not data:
                # 客户端退出,从关注事件移除
                p.unregister(fd)
                fdmap[fd].close()
                del fdmap[fd]
            else:
                print(data.decode())
                fdmap[fd].send(b'Receive')
           

epoll

    使用方法:基本与poll方法相同

    *将生成对象poll改为 epoll()

    *将所有poll对象事件改为epoll对象事件

    区别:

    epoll的效率要比poll和select高

    epoll的事件触发方式更多

# epoll_server.py
from socket import *
from select import *

# 创建套接字作为我们关注的IO
s = socket()
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
s.bind(('0.0.0.0', 8888))
s.listen(5)

# 创建poll对象
p = epoll()
# fileno --->IO对象的字典
fdmap = {s.fileno(): s}
# 注册关注的IO
p.register(s, EPOLLIN | EPOLLERR)


while True:
    # 进行IO监控
    events = p.poll()
    for fd, event in events:
        if fd == s.fileno():
            c, addr = fdmap[fd].accept()
            print('Connect from', addr)
            # 添加新的关注事件
            p.register(c, EPOLLIN | EPOLLHUP)
            fdmap[c.fileno()] = c  # fdmap = {s.fileno(): s,c.fileno: c}
        elif event & POLLIN:
            data = fdmap[fd].recv(1024)
            if not data:
                # 客户端退出,从关注事件移除
                p.unregister(fd)
                fdmap[fd].close()
                del fdmap[fd]
            else:
                print(data.decode())
                fdmap[fd].send(b'Receive')



           

本地套接字

    linux文件

    b(块设备文件)  c(字符设备文件)    d(目录)

    -(普通文件)    l(链接) s(套接字) p(管道)

    作用:用于本地不同的程序间进行通信

    创建流程

        1.创建本地套接字

            sockfd= socket(AF_UNIX,SOCK_STREAM)

        2.绑定本地套接字文件

            *选定文件位置和名称

            *sockfd.bind(path)

        3.监听 listen()

        4.消息收发    recv    send

    cookie

        os.path.exists(path)

        功能:判断一个文件是否存在

        参数:目标文件

        返回值:存在返回True,否则返回False

        os.remove()   os.unlink()

        功能:删除一个文件

        参数:目标文件

# unix_recv.py

from socket import *
import os
sock_file = './sock_file'

# 判断文件是否已经存在
if os.path.exists(sock_file):
    os.remove(sock_file)

# 创建套接字
sockfd = socket(AF_UNIX, SOCK_STREAM)

# 绑定本地套接字
sockfd.bind(sock_file)

# 监听
sockfd.listen(3)

# 消息收发
while True:
    c, addr = sockfd.accept()
    while True:
        data = c.recv(1024)
        if data:
            print(data.decode())
            c.send(b'Reclive')
        else:
            break
    c.close()
scokfd.close()
           
# unix_send.py

from socket import *

# 确保通信两端用的同一个套接字文件
sock_file = './sock_file'

# 创建本地套接字
sockfd = socket(AF_UNIX, SOCK_STREAM)

# 连接另一端
sockfd.connect(sock_file)

# 消息收发

while True:
    msg = input('>>')
    if msg:
        sockfd.send(msg.encode())
        print(sockfd.recv(1024).decode())
    else:
        break
scokfd.close()


           

继续阅读