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()