非阻塞套接字與IO多路複用(轉,python實現版)

非阻塞:指在不能馬上獲得結果以前,該函數不會阻塞當前線程,而會馬上返回。epoll工做在非阻塞模式時,纔會發揮做用。編程

 

咱們瞭解了socket以後已經知道,普通套接字實現的服務端的缺陷:一次只能服務一個客戶端!服務器

而且,爲了使一個客戶端可以不斷收發消息,咱們還要使用while循環來輪詢,這極大地下降了咱們的效率多線程

accept阻塞!併發

在沒有新的套接字來以前,不能處理已經創建鏈接的套接字的請求app

recv 阻塞!socket

在沒有接受到客戶端請求數據以前,不能與其餘客戶端創建鏈接函數

能夠用非阻塞接口來嘗試解決這個問題!spa

阻塞IO模型操作系統

 

 

阻塞IO(blocking IO)的特色:就是在IO執行的兩個階段(等待數據和拷貝數據兩個階段)都被block了。線程

 什麼是阻塞呢?想象這種情形:你要去車站接朋友,到了車站以後發現車還沒到站,你如今有兩種選擇:

  1. 繼續在等着,直到朋友的車到站。
  2. 先去幹點別的,而後時不時地聯繫你的朋友詢問車是否到站,朋友說到了,你再去車站

 很明顯,大多數人都會選擇第二種方案。

而在計算機世界,這兩種情形就對應阻塞和非阻塞忙輪詢。

  • 非阻塞輪詢:數據沒來,進程就不停的去檢測數據,直到數據來。
  • 阻塞:數據沒來,啥都不作,直到數據來了,才進行下一步的處理。

 非阻塞IO模型

 

非阻塞套接字和阻塞套接字的區別:

把套接字設置爲非阻塞以後,若是沒有獲得想要的數據,就會拋出一個BlockingIOError的異常。咱們能夠經過捕獲處理這個異常,讓程序正常完成

非阻塞式IO中,用戶進程實際上是須要不斷的主動詢問kernel數據準備好了沒有

非阻塞如何利用

  • 吃滿 CPU !
  • 寧肯用 while True ,也不要阻塞發呆!
  • 只要資源沒到,就先作別的事!

 編程範式:

服務端

複製代碼
import socket

CONN_ADDR = ('127.0.0.1', 9999)
conn_list = []  # 鏈接列表
server = socket.socket()  # 開啓socket
server.setblocking(False)  # 設置爲非阻塞
server.bind(CONN_ADDR)  # 綁定IP和端口到套接字
server.listen(5)          # 監聽,5表示最大掛起數
print('start listen')
while True:
    try:
        conn,addr = server.accept() #等待客戶端鏈接,沒有就拋出BlockingIOError
        conn.setblocking(False)
        print('{}已鏈接'.format(addr))
        conn_list.append(conn)
    except BlockingIOError:
        pass
    conn_list = [x for x in conn_list] 
    for conn_socket in conn_list:#對已鏈接的套接字進行輪詢
        try:
            data = conn_socket.recv(1024) #若有客戶端發送消息,則打印並返回
        except BlockingIOError:
            pass
        else: #else在不報錯的時候才執行
            if data: #判斷客戶端發過來的是否是空
                print(data.decode())
                conn_socket.send(data)
            else: #若爲空,表示客戶端已斷開
                conn_socket.close()
                conn_list.remove(conn_socket)
                print('客戶端數目:{}'.format(len(conn_list)))
複製代碼

客戶端

複製代碼
import socket
client = socket.socket()
client.connect(('127.0.0.1',9999))
while True:
    data = input('>>>>>')
    if data == 'q': #按q退出
        break
    client.send(data.encode())

    response = client.recv(1024)

    print(response.decode())
複製代碼

非阻塞IO模型優勢:實現了同時服務多個客戶端,可以在等待任務完成的時間裏幹其餘活了(包括提交其餘任務,也就是 「後臺」 能夠有多個任務在「」同時「」執行)。

 可是非阻塞IO模型毫不被推薦

非阻塞IO模型缺點:

  • 不停地輪詢,佔用較多的CPU資源。
  • 對應BlockingIOError的異常處理也是無效的CPU花費 !

如何解決:多路複用IO

多路複用IO

什麼是IO多路複用技術呢,簡單來講,就是咱們把套接字交給操做系統去監控。

使用select函數進行IO請求和同步阻塞模型沒有太大的區別,甚至還多了添加監視socket,以及調用select函數的額外操做,感受效率更差。

可是,使用select之後最大的優點是用戶能夠在一個線程內同時處理多個socket的IO請求。用戶能夠註冊多個socket,而後不斷地調用select讀取被激活的socket,

便可達到在同一個線程內同時處理多個IO請求的目的。而在同步阻塞模型中,必須經過多線程的方式才能達到這個目的。

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

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

epoll實現併發服務器,處理多個客戶端

 

複製代碼
import socket
import selectors

# 註冊一個epllo事件參數
# 1. 須要操做系統監控的套接字
# 2.事件(可讀仍是可寫)
# 3.回調函數

def recv_data(conn):
    data = conn.recv(1024)

    if data:
        print('接收的數據是:%s' % data.decode())
        conn.send(data)
    else:
        print('斷開鏈接',conn)
        e_poll.unregister(conn)
        conn.close()

def accept_conn(p_server):
    conn, addr = p_server.accept()
    print('Connected by', addr)
    # 也要註冊一個事件
    e_poll.register(conn,selectors.EVENT_READ,recv_data)


CONN_ADDR = ('127.0.0.1', 9999)
server = socket.socket()
server.bind(CONN_ADDR)
server.listen(6)

# 生成一個epllo選擇器實例 I/O多路複用,監控多個socket鏈接
e_poll = selectors.DefaultSelector() # Linux是epoll,Windows是select
e_poll.register(server, selectors.EVENT_READ, accept_conn)

# 事件循環
while True:
    # 事件循環不斷地調用select獲取發生變化的socket
    events = e_poll.select()
    for key, mask in events:
        call_back = key.data #key.data就是回調函數
        call_back(key.fileobj) #key.fileobj是套接字
複製代碼
相關文章
相關標籤/搜索