在多路復用的模型中,比較常用的有select模型和epoll模型。這兩個都是系統介面,由作業系統提供。當然,python的select模組進行了更高階的封裝。
網路通訊被unix系統抽象為檔案的讀寫,通常是乙個裝置,由裝置驅動程式提供,驅動可以知道自身的資料是否可用。支援阻塞操作的裝置驅動通常會實現一組自身的等待佇列,如讀/寫等待佇列用於支援上層(使用者層)所需的block或non-block操作。裝置的檔案的資源如果可用(可讀或者可寫)則會通知程序,反之則會讓程序睡眠,等到資料到來可用的時候,再喚醒程序。
這些裝置的檔案描述符被放在乙個陣列中,然後select呼叫的時候遍歷這個陣列,如果對於的檔案描述符可讀則會返回改檔案描述符。當遍歷結束之後,如果仍然沒有乙個可用裝置檔案描述符,select讓使用者程序則會睡眠,直到等待資源可用的時候在喚醒,遍歷之前那個監視的陣列。每次遍歷都是依次進行判斷的。
使用python的select模組很容易寫出下面乙個echo(回顯)伺服器:
importselect
import
socket
import
sysserver =socket.socket(socket.af_inet, socket.sock_stream)
server.bind((
'', 7788))
server.listen(5)
inputs =[server, sys.stdin]
running =true
while
true:
#呼叫 select 函式,阻塞等待
readable, writeable, exceptional =select.select(inputs, , )
#資料抵達,迴圈
for sock in
readable:
#監聽到有新的連線
if sock ==server:
conn, addr =server.accept()
#select 監聽的socket
#監聽到鍵盤有輸入
elif sock ==sys.stdin:
cmd =sys.stdin.readline()
running =false
break
#有資料到達
else
:
#讀取客戶端連線傳送的資料
data = sock.recv(1024)
ifdata:
sock.send(data)
else
:
#移除select監聽的socket
inputs.remove(sock)
sock.close()
#如果檢測到使用者輸入敲擊鍵盤,那麼就退出
ifnot
running:
break
server.close()
#coding=utf-8
import
socket
import
queue
from select import
select
server_ip = ('', 9999)
#儲存客戶端傳送過來的訊息,將訊息放入佇列中
message_queue ={}
input_list =
output_list =
if__name__ == "
__main__
":
server =socket.socket()
server.bind(server_ip)
server.listen(10)
#設定為非阻塞
server.setblocking(false)
#初始化將服務端加入監聽列表
while
true:
#開始 select 監聽,對input_list中的服務端server進行監聽
stdinput, stdoutput, stderr =select(input_list, output_list, input_list)
#迴圈判斷是否有客戶端連線進來,當有客戶端連線進來時select將觸發
for obj in
stdinput:
#判斷當前觸發的是不是服務端物件, 當觸發的物件是服務端物件時,說明有新客戶端連線進來了
if obj ==server:
#接收客戶端的連線, 獲取客戶端物件和客戶端位址資訊
conn, addr =server.accept()
print("
client %s connected!
"%str(addr))
#將客戶端物件也加入到監聽的列表中, 當客戶端傳送訊息時 select 將觸發
#為連線的客戶端單獨建立乙個訊息佇列,用來儲存客戶端傳送的訊息
message_queue[conn] =queue.queue()
else
:
#由於客戶端連線進來時服務端接收客戶端連線請求,將客戶端加入到了監聽列表中(input_list),客戶端傳送訊息將觸發
#所以判斷是否是客戶端物件觸發
try:
recv_data = obj.recv(1024)
#客戶端未斷開
ifrecv_data:
print("
received %s from client %s
"%(recv_data, str(addr)))
#將收到的訊息放入到各客戶端的訊息佇列中
message_queue[obj].put(recv_data)
#將回覆操作放到output列表中,讓select監聽
if obj not
inoutput_list:
except
connectionreseterror:
#客戶端斷開連線了,將客戶端的監聽從input列表中移除
input_list.remove(obj)
#移除客戶端物件的訊息佇列
delmessage_queue[obj]
print("
\n[input] client %s disconnected
"%str(addr))
#如果現在沒有客戶端請求,也沒有客戶端傳送訊息時,開始對傳送訊息列表進行處理,是否需要傳送訊息
for sendobj in
output_list:
try:
#如果訊息佇列中有訊息,從訊息佇列中獲取要傳送的訊息
ifnot
message_queue[sendobj].empty():
#從該客戶端物件的訊息佇列中獲取要傳送的訊息
send_data =message_queue[sendobj].get()
sendobj.send(send_data)
else
:
#將監聽移除等待下一次客戶端傳送訊息
output_list.remove(sendobj)
except
connectionreseterror:
#客戶端連線斷開了
delmessage_queue[sendobj]
output_list.remove(sendobj)
print("
\n[output] client %s disconnected
"%str(addr))
select目前幾乎在所有的平台上支援,其良好跨平台支援也是它的乙個優點。
select的乙個缺點在於單個程序能夠監視的檔案描述符的數量存在最大限制,在linux上一般為1024,可以通過修改巨集定義甚至重新編譯核心的方式提公升這一限制,但是這樣也會造成效率的降低。
一般來說這個數目和系統記憶體關係很大,具體數目可以cat /proc/sys/fs/file-max察看。32位機預設是1024個。64位機預設是2048.
對socket進行掃瞄時是依次掃瞄的,即採用輪詢的方法,效率較低。
當套接字比較多的時候,每次select()都要通過遍歷fd_setsize個socket來完成排程,不管哪個socket是活躍的,都遍歷一遍。這會浪費很多cpu時間。
Python 高階(十) 網路程式設計
網路程式設計主要的工作就是在傳送端將資訊通過指定的協議進行組裝包,在接收端按照規定好的協議對包進行解析並提取出對應的資訊,最終達到通訊的目的。傳輸協議主要有 tcp 和 udp,tcp 需要建立連線,是可靠的 基於位元組流的協議,通常與 ip 協議共同使用 udp 不需要建立連線,可靠性差,但速度更...
python基礎 網路程式設計十(遠端執行命令)
server 下發命令 client 執行命令 ssh協議 import os ret os.popen ls read print ret import subprocess 內建模組 和os模組的功能有相似之處 能執行作業系統的命令的功能 ret subprocess.popen dir 要執行...
Python第十課網路程式設計
cs架構 import socket 客戶端 sk socket.socket 建立客戶套接字 sk.connect 127.0.0.1 8080 嘗試連線伺服器 while true sk.send input 你要對劉奕銘說 encode utf 8 ret sk.recv 1024 對話 傳送...