天天看點

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


           

繼續閱讀