最大視角-從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))