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