python_非阻塞套接字及I/O流

http://www.cnblogs.com/lixy-88428977/p/9638949.htmlhtml

 

首先,咱們要明確2個問題:編程

普通套接字實現的服務端有什麼缺陷嗎?瀏覽器

有,一次只能服務一個客戶端!緩存

這種缺陷是如何形成的?服務器

accept阻塞:當沒有套接字鏈接請求過來的時候會一直等待着網絡

recv阻塞:當鏈接的這個客戶端沒有發數據過來的時候,也會一直等待着併發

 

複製代碼
import socket

server = socket.socket()
server.bind(('127.0.0.1', 8888))
server.listen(5)
print("執行到這, 上面沒問題了")

while True:

    conn, addr = server.accept()   # 阻塞

    print(conn, addr)
    print("{}鏈接".format(addr))

    while True:
        data = conn.recv(1024)       # 阻塞
        print(data)

        if not data:
            break
複製代碼

 

當前I/O流app

 

 

 

那麼非阻塞套接字和普通套接字的區別?異步

非阻塞套接字在accept或recv的時候不會發生阻塞,要麼成功,要麼失敗拋出BlockingIOError異常socket

 

 

非阻塞IO模型

 

非阻塞套接字實現併發

併發是什麼?

在一個時間段,完成某件事,就是併發

對立的概念,什麼是並行?

同時發生,無論有幾件事,同時進行,就是並行

 

 

非阻塞套接字如何實現併發服務端?

配合try語句,將代碼順序重排,避開阻塞

 

實現併發服務多個客戶端 !

那麼如今用非阻塞套接字完善上一章博客的代碼:

服務端:

複製代碼
import socket
import time
# 併發操做

server = socket.socket()     # 建立一個socket
server.setblocking(False)    # 設置成非阻塞
server.bind(('0.0.0.0', 8888))
server.listen()
print("執行到這, 上面沒問題了")

all_connction = []  # 用來存放和鏈接客戶端通訊的套接字
while True:
    # 處理用戶的鏈接
    try:
        conn, addr = server.accept()   # 阻塞
        conn.setblocking(False)    # 設置成非阻塞
        print(conn, addr)
        print("{}鏈接".format(addr))
        all_connction.append(conn)
    except BlockingIOError as e:
        pass

    # 處理已經鏈接的客戶的消息
    time.sleep(1)
    new_li = all_connction.copy()
    for conn in new_li:
        try:
            data = conn.recv(1024)       # 阻塞
            if data == b'':
                all_connction.remove(conn)
                conn.close()
            else:
                print("接收到的數據: ", data.decode())
                conn.send(data)

        except BlockingIOError as e:
            pass

server.close()
複製代碼

客戶端:

複製代碼
#客戶端Linux、window系統下:輸入命令經過服務端返回
import  socket

#聲明協議類型,同時生成socket鏈接對象
client = socket.socket()

#連接地址和端口,元組(本地,端口)
client.connect(('127.0.0.1', 8888))

#使用input循環向服務端發送請求
while True:

    msg = input("-->>:").strip()
    if len(msg) == 0:
        continue

    #發送數據 b將字符串轉爲bys類型
    client.send(msg.encode("utf-8"))

    #接收服務器端的返回,須要聲明收多少,默認1024字節
    id = 1024
    data = client.recv(id).decode()

    #打印data是recv的data
    print("recv: %s" % data)

#關閉接口
client.close()
複製代碼

 

IO多路複用

 IO多路複用也是阻塞IO, 只是阻塞的方法是select/poll/epoll, 好處就是單個進程能夠處理多個socket

用select,poll,epoll監聽多個io對象,當io對象有變化(有數據)的時候,則當即通知相應程序進行讀或者寫操做。

但select,poll,epoll本質上都是同步I/O,由於他們都須要在讀寫時間就緒後本身負責進行讀寫,也就是說這個讀寫過程是阻塞的

 

由於阻塞I/O只能阻塞一個I/O操做,而I/O複用模型可以阻塞多個I/O操做, 因此才叫作多路複用

 

 

 

非阻塞套接字實現的服務端還有什麼不完美的地方嗎?

關鍵一: 任何操做都是要花CPU資源的!

關鍵二: 若是數據尚未到達。那麼accept, recv操做都是在作無用功!

關鍵三: 對異常BlockIOError的處理也是在作無用功!

總結:不完美的CPU利用率

 

I/O多路複用模型

 

 epoll  目前Linux上效率最高的IO多路複用技術!

 

epoll 基於惰性的事件回調機制

惰性的事件回調是由用戶本身調用的,操做系統只起到通知的做用

 

使用步驟

    導入IO多路複用選擇器

  註冊事件和回調

  查詢

     回調

 

 

應用實例:

複製代碼
import selectors  # 提供IO多路技術給咱們使用的
import socket
import time

server = socket.socket()         #建立一個套接字
server.bind(('0.0.0.0', 8888))
server.listen()
epoll = selectors.EpollSelector()  # 生成一個epoll

def newFunc(conn):
    data = conn.recv(1024)
    if data == b'':
        epoll.unregister(conn)  # 取消註冊
        conn.colse()
    else:
        print("收到的數據: ",data.decode())
        conn.send(data)

def func(ser):
    conn, addr = ser.accept()
    print("處理了鏈接")
    # 查看是否有數據
    epoll.register(conn, selectors.EVENT_READ, newFunc)

time.sleep(1)
epoll.register(server, selectors.EVENT_READ, func)    # 註冊 給他3個參數   1.想看什麼,看的是server   2.有沒有數據鏈接(EVENT_READ可讀,EVENT_WRITE可寫)  3.回調函數

while True:
    events = epoll.select()  # 查詢
    for key, mask in events:
        callback = key.data  # 函數的名字
        sock = key.fileobj  # 套接字

        callback(sock)    # 函數調用
複製代碼

 

複製代碼
import  socket

client = socket.socket()
client.connect(('127.0.0.1', 8888))

while True:
    data = input("請輸入要發送的數據:")
    client.send(data.encode())
    print("接收到的數據:", client.recv(1024).decode())
複製代碼

 

下面是一些理論的東西,你們有時間能夠讀一遍。

## 什麼是IO操做

​ IO在計算機中指Input/Output,也就是輸入和輸出 。因爲程序和運行時數據是放在內存中的,由CPU來執行的,涉及到數據交換的地方,一般是磁盤、網絡等,就須要IO接口。

​ 好比你打開瀏覽器,訪問百度,瀏覽器須要經過網絡獲取百度的網頁數據。瀏覽器首先發送數據給百度的服務器,告訴它我要訪問它,這個過程是往外發數據,就作Output。而後百度服務器在把網頁數據發過來。這個過程是從外面接收數據,就作input

​ 因此,一般,程序完成IO操做會有Input和Output 這兩個過程, 可是也可能只有一個,好比打開一個文件。就只是從磁盤讀取文件到內存,就只有Input操做 ,反過來,向文件中寫入數據,就只是一個Output操做。

## 1 流的概念

​ IO編程中,Stream(流)是一個很重要的概念,能夠把流想象成一個水管,數據就是水管裏的水,可是隻能單向流動。Input Stream就是數據從外面(磁盤、網絡)流進內存,Output Stream就是數據從內存流到外面去。對於瀏覽網頁來講,瀏覽器和百度服務器之間至少須要創建兩根水管,才能夠既能發數據,又能收數據。

​ 因爲CPU和內存的速度遠遠高於外設的速度,因此,在IO編程中,就存在速度嚴重不匹配的問題。 可能存在這樣的狀況:讀取數據的時候,流中尚未數據;寫入數據的時候,流中數據已經滿了,沒有空間寫入了。

​ 舉個例子,socket通訊, 經過recv讀取另外一方發過來的數據,可是對方還沒把數據準備好發過來。此時有兩種處理辦法:

- 阻塞,等待數據準備好了,再讀取出來返回;
- 非阻塞,經過輪詢的方式,查詢是否有數據能夠讀取,直到把數據讀取返回。

 

## 2. 同步,異步,阻塞, 非阻塞的概念

在IO操做過程當中,可能會涉及到同步(synchronous)、異步(asynchronous)、阻塞(blocking)、非阻塞(non-blocking)、IO多路複用(IO multiplexing)等概念。他們之間的區別是什麼呢?

以socket爲例子,在socket通訊過程當中,涉及到兩個對象:

1. 調用這個IO的進程(process)或線程(thread)
2. 操做系統內核(kernel)

好比服務端調用recv來接收客戶端的數據,會涉及兩個過程:

1. 等待數據準備好(Waiting for the data to be ready),也就是客戶端要經過網絡把數據發給服務端;
2. 客戶端把數據發送過來,首先會被操做系統內核接收到,程序裏面須要使用這個數據,要將數據從內核中拷貝到進程中( Copying the data from the kernel to the process))

根據這兩個階段中,不一樣階段是否發生阻塞,將產生不一樣的效果。

### 阻塞 VS 非阻塞

阻塞IO:

- 在一、2階段都發生阻塞;
- 調用阻塞IO會一直block住進程,直到操做完成

非阻塞IO:

- 在第1階段沒有阻塞,在第2階段發生阻塞;
- 當用戶進程發出IO請求時, 若是內核中的數據還沒由準備好,那麼它並不會block用戶進程,而是當即返回一個錯誤, 在程序看來,它發起一個請求後,並不須要等待,而是立刻就獲得一個結果。
- 非阻塞IO須要不斷輪詢,查看數據是否已經準備好了;

阻塞與非阻塞能夠簡單理解爲調用一個IO操做能不能當即獲得返回應答,若是不能當即得到返回,須要等待,那就阻塞了;不然就能夠理解爲非阻塞 。

 

### **同步VS異步**

同步與異步是針對應用程序與內核的交互而言的

同步:第二步數據從內核緩存寫入用戶緩存必定是由用戶線程自行讀取數據,處理數據。

異步:第二步數據是內核寫入的,並放在了用戶線程指定的緩存區,寫入完畢後通知用戶線程。

 

​ 同步和異步針對應用程序來,關注的是程序中間的協做關係;阻塞與非阻塞更關注的是單個進程的執行狀態。

​ 同步有阻塞和非阻塞之分,異步沒有,它必定是非阻塞的。

​ 阻塞、非阻塞、多路IO複用,都是同步IO,異步一定是非阻塞的,因此不存在異步阻塞和異步非阻塞的說法。真正的異步IO須要CPU的深度參與。換句話說,只有用戶線程在操做IO的時候根本不去考慮IO的執行所有都交給CPU去完成,而本身只等待一個完成信號的時候,纔是真正的異步IO。因此,拉一個子線程去輪詢、去死循環,或者使用select、poll、epool,都不是異步。

 

​ 同步:執行一個操做以後,進程觸發IO操做並等待(也就是咱們說的阻塞)或者輪詢的去查看IO操做(也就是咱們說的非阻塞)是否完成,等待結果,而後才繼續執行後續的操做。

​ 異步:執行一個操做後,能夠去執行其餘的操做,而後等待通知再回來執行剛纔沒執行完的操做。

​ 阻塞:進程給CPU傳達一個任務以後,一直等待CPU處理完成,而後才執行後面的操做。

​ 非阻塞:進程給CPU傳達任務後,繼續處理後續的操做,隔斷時間再來詢問以前的操做是否完成。這樣的過程其實也叫輪詢。

## 3. IO多路複用

​ I/O多路複用也是阻塞IO,只是阻塞的方法是select/poll/epoll ,好處就是單個進程能夠處理多個socket

用select, poll, epoll監聽多個io對象,當io對象有變化(有數據)的時候,則當即通知相應程序進行讀或寫操做。但select,poll,epoll本質上都是同步I/O,由於他們都須要在讀寫事件就緒後本身負責進行讀寫,也就是說這個讀寫過程是阻塞的

由於阻塞I/O只能阻塞一個I/O操做,而I/O複用模型可以阻塞多個I/O操做,因此才叫作多路複用。

 

到這裏整理完畢。

相關文章
相關標籤/搜索