凡是'在内存中存在的数据交换的操作'都可以认为是IO操作,如:
默认形态,效率很低的一种IO;常见的阻塞场景:
通过修改IO事件的属性,使其变为非阻塞状态,以避免条件阻塞的情况。非阻塞IO往往和循环搭配使用,这样可以不断执行部分需要执行的代码,也不影响对阻塞事件的判断。以下示例通过s.setblocking(False)设置套接字为非阻塞套接字,并处理由此产生的BlockingIOError异常:
import socket
from time import sleep,ctime
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
ADDR = ("127.0.0.1",8888)
s.bind(ADDR)
s.listen(5)
#设置套接字为非阻塞
s.setblocking(False)
while True:
print(ctime(), 'waiting for connect...')
try:
connect, address = s.accept()
except BlockingIOError:
sleep(2)
continue
print("Connect to", address)
while True:
print(ctime(), 'waiting for receive...')
try:
data = connect.recv(1024).decode()
except BlockingIOError:
continue
if not data:
break
print(data)
n = connect.send(b"Receive your message!")
connect.close()
s.close()
实现非阻塞的另一种方式是将原本阻塞的IO设置一个最长等待时间,在规定的时间达到条件则正常执行;如果过时仍未达到条件则阻塞结束。下面的示例通过s.settimeout(sec)设置套接字超时时间,并处理socket.timeout异常:
import socket
from time import sleep,ctime
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
ADDR = ("127.0.0.1",8888)
s.bind(ADDR)
s.listen(5)
# 设置超时阻塞时间
s.settimeout(5)
while True:
try:
print(ctime(), 'waiting for connect...')
try:
connect, address = s.accept()
except socket.timeout:
print(ctime(), 'connect timeout...')
sleep(2)
continue
print(ctime(), 'connect to {}'.format(address))
while True:
try:
data = connect.recv(1024).decode()
except socket.timeout:
continue
if not data:
break
connect.send(b'receive your message.')
connect.close()
except KeyboardInterrupt:
s.close()
exit()
IO多路复用指的是同时交给内核监控多个IO事件,当哪个IO准备就绪,就立去执行哪个IO事件。以此来形成多个IO事件都可以操作的现象,而不必逐个等待执行。因此,当程序中有多个IO事件时,使用IO多路复用可以提高程序的执行效率。python中实现IO多路复用:
'''select IO多路复用
监控服服务端终端输入及socket网络套接字
提示:请在*nux系统下运行
'''
import socket
import select
import sys
SERVER = ("0.0.0.0",8888)
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
s.bind(SERVER)
s.listen(5)
i = sys.stdin
# 三个关注列表
rlist = [s,i]
wlist = []
xlist = [s,i]
while True:
print("Waiting for Connection...")
rt,wt,xt = select.select(rlist,wlist,xlist)
for x in rt:
if x == s:
connfd,addr = x.accept()
print("Connect to",addr)
rlist.append(connfd)
elif x == i:
data = x.readline()
wlist.append(x)
else:
data = x.recv(1024).decode()
if not data:
rlist.remove(x)
x.close()
else:
print(data)
wlist.append(x)
for x in wt:
if x == i:
data_l = ['From terminal:\n',data]
else:
x.send(b"Receive your message!")
data_l = ['From network:\n',data+'\n']
wlist.remove(x)
with open("记录.txt",'at') as f:
f.writelines(data_l)
s.close()
import socket
import select
ADDR = ("0.0.0.0",8888)
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
s.bind(ADDR)
s.listen(5)
#创建poll对象
p = select.poll()
#建立通过fileno文件描述符查找套接字的字典
fdmap = {s.fileno():s}
#注册关注的IO事件
p.register(s,select.POLLIN | select.POLLERR)
while True:
print("Waiting for connection...")
#开始监测
events = p.poll()
for fd,event in events:
if fd == s.fileno():
connfd,addr = s.accept()
print("Connect from:",addr)
p.register(connfd,select.POLLIN | select.POLLERR)
fdmap[connfd.fileno()] = connfd
elif event & select.POLLIN:
data = fdmap[fd].recv(1024)
if not data:
p.unregister(fd)
fdmap[fd].close()
del fdmap[fd]
continue
print(data.decode())
fdmap[fd].send(b"Receive your message!")
s.close()
使用方法与poll基本相同,生成对象使用epoll()而不是poll(),register注册IO事件类型改为EPOLL事件类型:
import socket
import select
ADDR = ("0.0.0.0",8888)
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
s.bind(ADDR)
s.listen(5)
p = select.epoll()
fdmap = {s.fileno():s}
p.register(s, select.EPOLLIN | select.EPOLLERR)
while True:
print("Waiting for Connection...")
events = p.poll()
for fd,event in events:
if fd == s.fileno():
#检测到新的客户端即将连入
c,addr = s.accept()
print("Connect to",addr)
p.register(c,select.EPOLLIN | select.EPOLLERR)
fdmap[c.fileno()] = c
elif event & select.EPOLLIN:
#套接字接收准备就绪
data = fdmap[fd].recv(1024)
if not data:
p.unregister(fd)
fdmap[fd].close()
del fdmap[fd]
continue
print(data.decode())
fdmap[fd].send(b"Receive your message!")
s.close()