1. 程式人生 > >最大視角-從Unix底層 理解 python的io模型、python非同步IO、python的select、Unix的select、epoll

最大視角-從Unix底層 理解 python的io模型、python非同步IO、python的select、Unix的select、epoll

參考連結

最基本的文章
python之路——IO模型
一步步理解python的非同步IO
Unix底層文章
Linux-C網路程式設計之select函式 python 的 select
linux select函式詳解 unix的select
epoll簡介(一) unix的epoll【只是為了對比select,讓大家知道select是很普通的一種IO方式】
epoll.h 原始碼記錄 c語言的原始碼
其他
python非同步程式設計–回撥模型(selectors模組)

一圖瞭解

1.最大視角-瞭解相關概念

在這裡插入圖片描述

2.程式碼過程-看清內部執行過程

關鍵點:
1、客戶端、服務端 分別設定socket.recv(1)只接受一個字元可以很明顯的看懂事件回撥機制(select.select()每接受一個字元就會呼叫一次)。有select.select的地方就是事件發生的位置。
2、select.select()在沒有事件呼叫的時候就會阻塞(客戶端不輸入任何東西時就阻塞在這裡),和引數rlist、wlist裡有沒有元素無關
3、首先把server放入inputs列表裡,是為了之後客戶端來啟用select.select
4、至於什麼情況下、何時啟用select.select,是window Linux系統級別的io操作(如:Unix 的select、epoll等)決定的!!!這本例子中(windows中只能用於sockets)可讀寫的socket註冊到Windows的select,系統判斷讀寫事件,然後啟用select.select函式,最後Windows系統將socket刪除註冊,Windows的監控事件就停止了。
5、select.select一次啟用可同時包含可讀事件、可寫事件(在select.select位置跳一次,就能同時在is readable、is writeable跳轉)

在這裡插入圖片描述

3.所用的程式碼

服務端

# -*- coding: UTF-8 -*-

import select
import socket
import queue
import sys
import time

# Create a TCP/IP socket
server = socket.socket()
# set noblocking
server.setblocking(False)

# Bind the socket to the port
server_address = ('localhost', 9999)
print(sys.stderr, 'starting up on %s port %s'
% server_address) server.bind(server_address) # Listen for incoming connections server.listen() # 所有連線進來的物件都放在inputs inputs = [server, ] # 自己也要監控,因為server本身也是個物件 # 需要傳送資料的物件 outputs = [] # 對外發送資料的佇列,記錄到字典中 message_queues = {} while True: # select.poll() readable, writable, exceptional =
select.select(inputs, outputs, inputs) print(11111) # 如果沒有任何fd就緒,那程式就會一直阻塞在這裡 for s in readable: # 每一個s就是有個socket if s is server: # 別忘記,上面我們server自己也當做一個fd放在了inputs列表裡,傳給了select,如果這個s是server,代表server這個fd就緒了, # 就是有活動了, 什麼情況下它才有活動? 當然 是有新連線進來的時候 # 新連線進來了,接受這個連線 conn, client_addr = s.accept() print("new connection from", client_addr) conn.setblocking(0) inputs.append(conn) # 為了不阻塞整個程式,我們不會立刻在這裡開始接收客戶端發來的資料, 把它放到inputs裡, 下一次loop時,這個新連線 # 就會被交給select去監聽,如果這個連線的客戶端發來了資料 ,那這個連線的fd在server端就會變成就續的,select就會把這個連線返回, # 返回到readable 列表裡,然後你就可以loop readable列表,取出這個連線,開始接收資料了, 下面就是這麼幹的 message_queues[conn] = queue.Queue() # 接收到客戶端的資料後,不立刻返回 ,暫存在佇列裡,以後傳送 else: # s不是server的話,那就只能是一個 與客戶端建立的連線的fd了 # 客戶端的資料過來了,在這接收 data = s.recv(1) if data: print('received [%s] from %s' % (data, s.getpeername()[0])) message_queues[s].put(data) # 收到的資料先放到queue裡,一會返回給客戶端 if s not in outputs: outputs.append(s) # 為了不影響處理與其它客戶端的連線 , 這裡不立刻返回資料給客戶端 else: # 如果收不到data代表什麼呢? 代表客戶端斷開了 print("client [%s] closed", s) if s in outputs: # 既然客戶端都斷開了,我就不用再給它返回資料了, # 所以這時候如果這個客戶端的連線物件還在outputs列表中,就把它刪掉 outputs.remove(s) inputs.remove(s) # 這個連線必然在inputs中,也刪掉 s.close() # 關閉的連線在佇列中也刪除 del message_queues[s] for s in writable: try: next_msg = message_queues[s].get_nowait() except queue.Empty: # 沒有資料了,該連線物件佇列為空,停止檢測 print('output queue for [%s] is empty' % s.getpeername()[0]) outputs.remove(s) else: # time.sleep(2) print('send %s to %s' % (next_msg, s.getpeername()[0])) s.send(next_msg) for s in exceptional: print('handling exceptional condition for', s.getpeername()[0]) # 從inputs中刪除 inputs.remove(s) if s in outputs: outputs.remove(s) s.close() # 刪除佇列 del message_queues[s]

客戶端

# -*- coding: UTF-8 -*-
import socket

HOST = 'localhost'  # The remote host
PORT = 9999  # The same port as used by the server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
while True:
    msg = bytes(input(">>:"), encoding="utf8")
    s.sendall(msg)
    data = s.recv(1)
    # print(data)

    print('Received', repr(data))