前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python IO 操作详解

Python IO 操作详解

原创
作者头像
大发明家
发布2021-12-15 15:37:59
8970
发布2021-12-15 15:37:59
举报
文章被收录于专栏:技术博客文章

#一. IO操作

凡是'在内存中存在的数据交换的操作'都可以认为是IO操作,如:

内存和磁盘的交互:read write

内存和终端的交互:print input

内存和网络的交互:recv send

1.1 阻塞IO

默认形态,效率很低的一种IO;常见的阻塞场景:

因为某种条件没有达到造成的阻塞,如:input accept recv

处理IO事件的时间消耗较长带来的阻塞,如:文件的读写过程,网络数据的发送过程

1.2 非阻塞IO

通过修改IO事件的属性,使其变为非阻塞状态,以避免条件阻塞的情况。非阻塞IO往往和循环搭配使用,这样可以不断执行部分需要执行的代码,也不影响对阻塞事件的判断。以下示例通过s.setblocking(False)设置套接字为非阻塞套接字,并处理由此产生的BlockingIOError异常:

代码语言:txt
复制
import socket
代码语言:txt
复制
from time import sleep,ctime
代码语言:txt
复制
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
代码语言:txt
复制
s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
代码语言:txt
复制
ADDR = ("127.0.0.1",8888)
代码语言:txt
复制
s.bind(ADDR)
代码语言:txt
复制
s.listen(5)
代码语言:txt
复制
#设置套接字为非阻塞
代码语言:txt
复制
s.setblocking(False)
代码语言:txt
复制
while True:
代码语言:txt
复制
    print(ctime(), 'waiting for connect...')
代码语言:txt
复制
    try:
代码语言:txt
复制
        connect, address = s.accept()
代码语言:txt
复制
    except BlockingIOError:
代码语言:txt
复制
        sleep(2)
代码语言:txt
复制
        continue
代码语言:txt
复制
    print("Connect to", address)
代码语言:txt
复制
    while True:
代码语言:txt
复制
        print(ctime(), 'waiting for receive...')
代码语言:txt
复制
        try:
代码语言:txt
复制
            data = connect.recv(1024).decode()
代码语言:txt
复制
        except BlockingIOError:
代码语言:txt
复制
            continue
代码语言:txt
复制
        if not data:
代码语言:txt
复制
            break
代码语言:txt
复制
        print(data)
代码语言:txt
复制
        n = connect.send(b"Receive your message!")
代码语言:txt
复制
    connect.close()
代码语言:txt
复制
s.close()

实现非阻塞的另一种方式是将原本阻塞的IO设置一个最长等待时间,在规定的时间达到条件则正常执行;如果过时仍未达到条件则阻塞结束。下面的示例通过s.settimeout(sec)设置套接字超时时间,并处理socket.timeout异常:

代码语言:txt
复制
import socket
代码语言:txt
复制
from time import sleep,ctime
代码语言:txt
复制
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
代码语言:txt
复制
s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
代码语言:txt
复制
ADDR = ("127.0.0.1",8888)
代码语言:txt
复制
s.bind(ADDR)
代码语言:txt
复制
s.listen(5)
代码语言:txt
复制
# 设置超时阻塞时间
代码语言:txt
复制
s.settimeout(5)
代码语言:txt
复制
while True:
代码语言:txt
复制
    try:
代码语言:txt
复制
        print(ctime(), 'waiting for connect...')
代码语言:txt
复制
        try:
代码语言:txt
复制
            connect, address = s.accept()
代码语言:txt
复制
        except socket.timeout:
代码语言:txt
复制
            print(ctime(), 'connect timeout...')
代码语言:txt
复制
            sleep(2)
代码语言:txt
复制
            continue
代码语言:txt
复制
        print(ctime(), 'connect to {}'.format(address))
代码语言:txt
复制
        while True:
代码语言:txt
复制
            try:
代码语言:txt
复制
                data = connect.recv(1024).decode()
代码语言:txt
复制
            except socket.timeout:
代码语言:txt
复制
                continue
代码语言:txt
复制
            if not data:
代码语言:txt
复制
                break
代码语言:txt
复制
            connect.send(b'receive your message.')
代码语言:txt
复制
        connect.close()
代码语言:txt
复制
    except KeyboardInterrupt:
代码语言:txt
复制
        s.close()
代码语言:txt
复制
        exit()

二. IO多路复用

IO

多路复用指的是同时交给内核监控多个IO事件,当哪个IO准备就绪,就立去执行哪个IO事件。以此来形成多个IO事件都可以操作的现象,而不必逐个等待执行。因此,当程序中有多个IO事件时,使用IO多路复用可以提高程序的执行效率。python中实现IO多路复用:

select

poll

epoll

2.1 select

r,w,x = select(rlist,wlist,xlist,timeout):向内核发起IO监控请求,阻塞等待IO事件发生。

参数说明:

rlist: 被动等待处理的IO事件列表

wlist:需要主动处理的IO列表

xlist:发生异常时需要处理的IO列表

timeout:可选参数,超时时间

返回值说明:

r : rlist中准备就绪的IO列表

w: wlist中准备就绪的IO列表

x: xlist中准备就绪的IO列表

注意事项:

IO多路复用不应该有死循环出现,使一个客户端长期占有服务端

IO多路复用是一种并发行为,但是是单进程程序,效率较高

示例:

代码语言:txt
复制
'''select IO多路复用
代码语言:txt
复制
监控服服务端终端输入及socket网络套接字
代码语言:txt
复制
提示:请在*nux系统下运行
代码语言:txt
复制
'''
代码语言:txt
复制
import socket
代码语言:txt
复制
import select
代码语言:txt
复制
import sys
代码语言:txt
复制
SERVER = ("0.0.0.0",8888)
代码语言:txt
复制
s = socket.socket()
代码语言:txt
复制
s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
代码语言:txt
复制
s.bind(SERVER)
代码语言:txt
复制
s.listen(5)
代码语言:txt
复制
i = sys.stdin
代码语言:txt
复制
# 三个关注列表
代码语言:txt
复制
rlist = [s,i]
代码语言:txt
复制
wlist = []
代码语言:txt
复制
xlist = [s,i]
代码语言:txt
复制
while True:
代码语言:txt
复制
    print("Waiting for Connection...")
代码语言:txt
复制
    rt,wt,xt = select.select(rlist,wlist,xlist)
代码语言:txt
复制
    for x in rt:
代码语言:txt
复制
        if x == s:
代码语言:txt
复制
            connfd,addr = x.accept()
代码语言:txt
复制
            print("Connect to",addr)
代码语言:txt
复制
            rlist.append(connfd)
代码语言:txt
复制
        elif x == i:
代码语言:txt
复制
            data = x.readline()
代码语言:txt
复制
            wlist.append(x)
代码语言:txt
复制
        else:
代码语言:txt
复制
            data = x.recv(1024).decode()
代码语言:txt
复制
            if not data:
代码语言:txt
复制
                rlist.remove(x)
代码语言:txt
复制
                x.close()
代码语言:txt
复制
            else:
代码语言:txt
复制
                print(data)
代码语言:txt
复制
                wlist.append(x)
代码语言:txt
复制
    for x in wt:
代码语言:txt
复制
        if x == i:
代码语言:txt
复制
            data_l = ['From terminal:\n',data]
代码语言:txt
复制
        else:
代码语言:txt
复制
            x.send(b"Receive your message!")
代码语言:txt
复制
            data_l = ['From network:\n',data+'\n']
代码语言:txt
复制
        wlist.remove(x)
代码语言:txt
复制
        with open("记录.txt",'at') as f:
代码语言:txt
复制
            f.writelines(data_l)
代码语言:txt
复制
s.close()

2.2 poll

p = poll() 创建poll对象

常见的poll IO事件分类

POLLIN,被动等待处理的IO

POLLOUT,主动处理的IO

POLLERR,相当于xlist

使用按位或连接注册多种IO事件:p.register(s,POLLIN | POLLERR)

取消对IO的关注:p.unregister(s)

进行监控

events = p.poll(),监控关注的IO,阻塞等待IO发生

返回值:events是一个列表,列表中每个元素为一个元组:

代码语言:txt
复制
格式:[ (fileno, event), (), ()... ]
代码语言:txt
复制
fileno 就绪事件的文件描述符,event就绪的事件

因为要获取IO对象以调用函数,需要建立比照字典 {s.fileno(): s},通过fileno来获取对象;

代码语言:txt
复制
import socket
代码语言:txt
复制
import select
代码语言:txt
复制
ADDR = ("0.0.0.0",8888)
代码语言:txt
复制
s = socket.socket()
代码语言:txt
复制
s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
代码语言:txt
复制
s.bind(ADDR)
代码语言:txt
复制
s.listen(5)
代码语言:txt
复制
#创建poll对象
代码语言:txt
复制
p = select.poll()
代码语言:txt
复制
#建立通过fileno文件描述符查找套接字的字典
代码语言:txt
复制
fdmap = {s.fileno():s}
代码语言:txt
复制
#注册关注的IO事件
代码语言:txt
复制
p.register(s,select.POLLIN | select.POLLERR)
代码语言:txt
复制
while True:
代码语言:txt
复制
    print("Waiting for connection...")
代码语言:txt
复制
    #开始监测
代码语言:txt
复制
    events = p.poll()
代码语言:txt
复制
    for fd,event in events:
代码语言:txt
复制
        if fd == s.fileno():
代码语言:txt
复制
            connfd,addr = s.accept()
代码语言:txt
复制
            print("Connect from:",addr)
代码语言:txt
复制
            p.register(connfd,select.POLLIN | select.POLLERR)
代码语言:txt
复制
            fdmap[connfd.fileno()] = connfd
代码语言:txt
复制
        elif event & select.POLLIN:
代码语言:txt
复制
            data = fdmap[fd].recv(1024)
代码语言:txt
复制
            if not data:
代码语言:txt
复制
                p.unregister(fd)
代码语言:txt
复制
                fdmap[fd].close()
代码语言:txt
复制
                del fdmap[fd]
代码语言:txt
复制
                continue
代码语言:txt
复制
            print(data.decode())
代码语言:txt
复制
            fdmap[fd].send(b"Receive your message!")
代码语言:txt
复制
s.close()

2.3 epoll

使用方法与poll基本相同,生成对象使用epoll()而不是poll(),register注册IO事件类型改为EPOLL事件类型:

代码语言:txt
复制
import socket
代码语言:txt
复制
import select
代码语言:txt
复制
ADDR = ("0.0.0.0",8888)
代码语言:txt
复制
s = socket.socket()
代码语言:txt
复制
s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
代码语言:txt
复制
s.bind(ADDR)
代码语言:txt
复制
s.listen(5)
代码语言:txt
复制
p = select.epoll()
代码语言:txt
复制
fdmap = {s.fileno():s}
代码语言:txt
复制
p.register(s, select.EPOLLIN | select.EPOLLERR)
代码语言:txt
复制
while True:
代码语言:txt
复制
    print("Waiting for Connection...")
代码语言:txt
复制
    events = p.poll()
代码语言:txt
复制
    for fd,event in events:
代码语言:txt
复制
        if fd == s.fileno():
代码语言:txt
复制
            #检测到新的客户端即将连入
代码语言:txt
复制
            c,addr = s.accept()
代码语言:txt
复制
            print("Connect to",addr)
代码语言:txt
复制
            p.register(c,select.EPOLLIN | select.EPOLLERR)
代码语言:txt
复制
            fdmap[c.fileno()] = c
代码语言:txt
复制
        elif event & select.EPOLLIN:
代码语言:txt
复制
            #套接字接收准备就绪
代码语言:txt
复制
            data = fdmap[fd].recv(1024)
代码语言:txt
复制
            if not data:
代码语言:txt
复制
                p.unregister(fd)
代码语言:txt
复制
                fdmap[fd].close()
代码语言:txt
复制
                del fdmap[fd]
代码语言:txt
复制
                continue
代码语言:txt
复制
            print(data.decode())
代码语言:txt
复制
            fdmap[fd].send(b"Receive your message!")
代码语言:txt
复制
s.close()

2.4. select poll epoll三者的区别

  1. epoll比select和poll效率高,select和poll差不多。
  • EPOLL内核每次仅返回给应用层“准备就绪的IO事件”;
  • select和poll则内核会将所有的IO事件返回,再由应用层去筛选准备就绪的IO事件。
  1. epoll提供了更多的触发方式,IO就绪的类别更多,如EPOLLET边缘触发。
  2. 在并发高同时连接活跃度不是很高的请看下,epoll比select好(网站或web系统中,用户请求一个页面后随时可能会关闭);并发性不高,同时连接很活跃,select比epoll好。(比如说游戏中数据一但连接了就会一直活跃,不会中断)。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
作者已关闭评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档