PythonでIO多重化を使う(入門編)
20696 ワード
IO操作はCPUを占有せず、IO多重化は、すべてのIO操作を管理する.IO多重化の典型的なシナリオは、socketオブジェクトの内部に変化が発生したか否かを傍受することである
ソケット内部はいつ変化しますか?接続を確立する 送信メッセージ ソケットインスタンス
ここではsocketを用いて簡単なEchoサーバの機能を実現する server.py client.py
以上のsocketコードは、同じ時間に1つのクライアントのリクエストしか処理できません.その後、接続されたクライアントは、最初のクライアントが切断されていない間、前のクライアントのリクエストが切断されるまで待機します.
select.selext()の最初のパラメータ
接続の確立
前述したように、socketが変わるのは接続を確立することです.
上記のコードでは、接続の確立に関するsocketはskオブジェクト(変数)skオブジェクトが
SOcketを作成し、バインドしてリスニングした後、socketは一般的に変化しません.新しいクライアントが接続されている場合にのみ、socketは変化します.リスニングが必要なのもこの段階の変化です.
結論:socketが作成され、バインドされ、傍受された後に変化すると、新しいクライアントが接続される server.py
上のコードはIO多重化におけるselectモデルを導入し、selectを用いる.select()メソッドは、3つの要素を持つ元祖を返します.
select.select()メソッドの最初のパラメータは,一時的に1つのサービス側のsocketオブジェクトのみを追加し,サービス側のsocketオブジェクトskが変化すれば(新しいクライアント接続)直ちに変化したsocketオブジェクトをrListリストに追加する.
リスニングされたsocketリストでは,skオブジェクトが変化する--->rList=[sk]
リスニングされたsocketリストには、socketが変更されていません--->rList=[]
上記のコードが走る効果:
新しい接続要求がないことを説明し、次にクライアント接続が発生した場合を試験する. client.py
クライアントがサービス側に接続されると、サービス側は次のように表示されます.
上から分かるように、クライアントが接続されています.サービス側のskオブジェクトの内部に変化が発生し、selectがskオブジェクトの変化を傍受すると、直ちに変化したオブジェクトをrListリスト(appendからリスト)に付与する印刷されたコンテンツから、リストの要素が変化したsocketオブジェクトであることがわかる
処理rList(サービス側socket)
rListには変化するすべてのsocketオブジェクトが保存されており,以上のコードではサービス側socketオブジェクトのみが傍受されているが,ここではサービス側socketの変化のみを一時的に議論する. server.py client.py
3つのクライアント接続を連続して実行する場合、サービス側のエコー:
各クライアントのエコー:
以上、rListにおけるサービス側socketオブジェクトに対してaccept()メソッドを実行することにより、接続されたクライアントごとにサービス側に要求を受け、「同時に」サービスを提供するという同時接続のような効果を実現した.
クライアントメッセージの受信
上述したように、接続の作成はsocketの変化をトリガするだけでなく、クライアントとの接続が確立されると、クライアントからメッセージが送られ、クライアントsocket接続の内部変化を引き起こす server.py
上記のコードでは、サービス側socketが新しいクライアントのために作成したsocketもリスニングリストに追加したが、クライアントからメッセージが来た場合、selectはクライアントsocket(conn)が変化してrListリストに追加されたことをリスニングした後、forループ処理ではクライアントのsocketにaccept()メソッドはなく、この方法も不要である.これはforサイクルで2種類のsocketを区別する必要がある. client.py
クライアントを実行し、Ctrl+Cが終了すると、サービス側はインタフェースを表示して狂った印刷メッセージを返します.問題は、サービス側が傍受するクライアントsocket接続です.クライアントがサービス側と接続を切断した場合、サービス側selectがsocketオブジェクトを傍受するリストからクライアントsocketオブジェクトを削除する必要があります. server.py
最新のserverを使用します.pyとclient.pyテストを行う場合、複数のクライアントを順次実行し、複数のクライアントを順次閉じ、サービス側のエコーは以下の通りです.
特に注意:ここでのサービス側定義では、クライアントから空の値を受信すると、クライアントがサービス側との接続を切断する必要があるとデフォルトで考えられます.サービス側のこのデフォルトルールでは、クライアントを書くときに、クライアントが入力した値が空の場合に注意しなければならない.
クライアントへのメッセージの返信 server.py
しかし、一般的には読み書き分離が行われ、selectにより読み書き分離(送受信分離)が実現される
select.selext()の2番目のパラメータ
select.select()の2番目のパラメータに何の値があるか、wListに何の値があるか
selectのこの特性により,メッセージに返信するクライアントsocketオブジェクトをselectの2番目のパラメータに割り当てることができる.
wList=サービス側にメッセージを送信するすべてのクライアントであり、メッセージを返信する必要があるクライアントのリストでもある client.py
プロセスの実行:は、3つのクライアント に順次接続する.最初のクライアントは、2回のメッセージ をサービスに順次送信する.
サービス側のエコー:
最初のクライアントのエコー:
以上のコードは简単な送受信メッセージの分离を実现して、今また少し需要が多くなって、现在すべてサービス端にメッセージを送信するクライアント、サービス端はすべて统一して同じ内容を返事して、今サービス端はEcho Serverの机能を実现して、つまりクライアントはどんな消息を送って、サービス端はクライアントにどんなメッセージを返事します
サービス側は以上の変更を行い、クライアントは変更する必要はありません. 11動作は、クライアントsocketオブジェクトがメッセージを受信し、メッセージを送信することに関連付けられ、新しいグローバル変数messages を導入する.440行は、新しいクライアントが接続するときに、そのsocketオブジェクトのためにmessagesに対応するkey を予め作成する.、254行は、オブジェクトにメッセージ を追加する.,264行はクライアントで接続を閉じる受信し、そのオブジェクトのメッセージリスト をクリーンアップする.,270行このオブジェクトを40行目に挿入するメッセージから取り出す .
まとめ
IO多重化を用いて,実際には同時効果のような擬似同時化を実現した.内部では、ブロック要求を効率的に処理するためにループが実際に使用されています.
Pythonにはselectモジュールがあり、select、poll、epollの3つの方法が提供され、システムのselect、poll、epollをそれぞれ呼び出してIO多重化を実現する. Windows Python:提供:select Mac Python:提供:select Linux Python:提供:select、poll、epoll 注意:ネットワークの操作、ファイルの操作、端末の操作などはすべてIOの操作に属して、windowsに対してSocketの操作だけを支持して、その他のシステムはすべてIOの操作を支持して、しかし普通のファイルの操作が自動的に最後に読み取ってすでに変化したかどうかを検出することができません
selectメソッドの場合:
追加:select poll epollの違い
IO多重化はシステムカーネルで実現され、システム内部ではforサイクルが維持され、オブジェクトに変化があるかどうかを一つ一つ検出する
まず明確にしなければならないのは、forサイクルの効率が高くないことです.
IO多重種別
実装の原理
リスニング対象の数
select
システム内部でforサイクルを維持
1024
poll
システム内部でforサイクルを維持
制限なし
epoll
ハンドルシーケンスが変化した場合にepollに自動的に通知
制限なし
ソケット内部はいつ変化しますか?
ここではsocketを用いて簡単なEchoサーバの機能を実現する
import socket
# socket , IP ,
sk = socket.socket()
sk.bind(('127.0.0.1', 1559))
sk.listen(5)
#
while True:
#
conn, address = sk.accept()
#
conn.sendall(bytes('hello', encoding='utf8'))
#
while True:
# Windows , Windows
try:
recv = conn.recv(1024)
# Linux recv , Linux
if not recv:
break
#
if str(recv, encoding='utf-8') == 'exit':
break
except Exception as ex:
break
#
conn.sendall(recv)
import socket
sk = socket.socket()
sk.connect(('127.0.0.1', 1559))
#
data = sk.recv(1024)
print(data)
while True:
i = input("> ")
#
sk.sendall(bytes(i, encoding='utf8'))
#
msg = sk.recv(1024)
print(str(msg, encoding='utf-8'))
sk.close()
以上のsocketコードは、同じ時間に1つのクライアントのリクエストしか処理できません.その後、接続されたクライアントは、最初のクライアントが切断されていない間、前のクライアントのリクエストが切断されるまで待機します.
select.selext()の最初のパラメータ
接続の確立
前述したように、socketが変わるのは接続を確立することです.
上記のコードでは、接続の確立に関するsocketはskオブジェクト(変数)skオブジェクトが
sk.accept()
まで実行されたときに、新しいクライアントの接続要求を受けたときにsocket内部が変化し、新しいクライアント側の接続を識別するためにこの変化を傍受する必要がある.SOcketを作成し、バインドしてリスニングした後、socketは一般的に変化しません.新しいクライアントが接続されている場合にのみ、socketは変化します.リスニングが必要なのもこの段階の変化です.
結論:socketが作成され、バインドされ、傍受された後に変化すると、新しいクライアントが接続される
import socket
import select
# socket , IP ,
sk = socket.socket()
sk.bind(('127.0.0.1', 1559))
sk.listen(5)
while True:
rList, w, e = select.select([sk,], [], [], 1)
print(rList)
上のコードはIO多重化におけるselectモデルを導入し、selectを用いる.select()メソッドは、3つの要素を持つ元祖を返します.
select.select()メソッドの最初のパラメータは,一時的に1つのサービス側のsocketオブジェクトのみを追加し,サービス側のsocketオブジェクトskが変化すれば(新しいクライアント接続)直ちに変化したsocketオブジェクトをrListリストに追加する.
リスニングされたsocketリストでは,skオブジェクトが変化する--->rList=[sk]
リスニングされたsocketリストには、socketが変更されていません--->rList=[]
上記のコードが走る効果:
[]
[]
[]
...
# []
# select.sekect()
# , ,
新しい接続要求がないことを説明し、次にクライアント接続が発生した場合を試験する.
import socket
sk = socket.socket()
sk.connect(('127.0.0.1', 1559))
sk.close()
クライアントがサービス側に接続されると、サービス側は次のように表示されます.
[]
[]
[]
...
# ...
上から分かるように、クライアントが接続されています.サービス側のskオブジェクトの内部に変化が発生し、selectがskオブジェクトの変化を傍受すると、直ちに変化したオブジェクトをrListリスト(appendからリスト)に付与する印刷されたコンテンツから、リストの要素が変化したsocketオブジェクトであることがわかる
処理rList(サービス側socket)
rListには変化するすべてのsocketオブジェクトが保存されており,以上のコードではサービス側socketオブジェクトのみが傍受されているが,ここではサービス側socketの変化のみを一時的に議論する.
import socket
import select
# socket , IP ,
sk = socket.socket()
sk.bind(('127.0.0.1', 1559))
sk.listen(5)
while True:
# socket sk
rList, w, e = select.select([sk,], [], [], 1)
print(rList)
# rList socket
# rList socket
for s in rList:
conn, address = s.accept()
conn.sendall(bytes('hello', encoding='utf8'))
import socket
sk = socket.socket()
sk.connect(('127.0.0.1', 1559))
data = sk.recv(1024)
print(data)
while True:
input("> ")
sk.close()
3つのクライアント接続を連続して実行する場合、サービス側のエコー:
[]
[]
[]
[]
[]
[]
[]
[]
[]
各クライアントのエコー:
b'hello'
>
以上、rListにおけるサービス側socketオブジェクトに対してaccept()メソッドを実行することにより、接続されたクライアントごとにサービス側に要求を受け、「同時に」サービスを提供するという同時接続のような効果を実現した.
クライアントメッセージの受信
上述したように、接続の作成はsocketの変化をトリガするだけでなく、クライアントとの接続が確立されると、クライアントからメッセージが送られ、クライアントsocket接続の内部変化を引き起こす
import socket
import select
# socket , IP ,
sk = socket.socket()
sk.bind(('127.0.0.1', 1559))
sk.listen(5)
inputs = [sk]
while True:
rList, w, e = select.select(inputs, [], [], 1)
print(rList)
for s in rList:
conn, address = s.accept()
# conn socket
# socket , socket
# socket
# socket
# ,
inputs.append(conn)
conn.sendall(bytes('hello', encoding='utf8'))
上記のコードでは、サービス側socketが新しいクライアントのために作成したsocketもリスニングリストに追加したが、クライアントからメッセージが来た場合、selectはクライアントsocket(conn)が変化してrListリストに追加されたことをリスニングした後、forループ処理ではクライアントのsocketにaccept()メソッドはなく、この方法も不要である.これはforサイクルで2種類のsocketを区別する必要がある.
import socket
import select
# socket , IP ,
sk = socket.socket()
sk.bind(('127.0.0.1', 1559))
sk.listen(5)
inputs = [sk]
while True:
rList, w, e = select.select(inputs, [], [], 1)
print("select socket >", len(inputs), " | socket >", len(rList))
for s in rList:
# socket socket
if s == sk:
conn, address = s.accept()
# conn socket
# socket , socket
# socket
# socket
# ,
inputs.append(conn)
conn.sendall(bytes('hello', encoding='utf8'))
# socket
else:
#
msg = s.recv(1024)
print(msg)
import socket
sk = socket.socket()
sk.connect(('127.0.0.1', 1559))
#
data = sk.recv(1024)
print(data)
while True:
i = input("> ")
#
sk.sendall(bytes(i, encoding='utf8'))
sk.close()
クライアントを実行し、Ctrl+Cが終了すると、サービス側はインタフェースを表示して狂った印刷メッセージを返します.問題は、サービス側が傍受するクライアントsocket接続です.クライアントがサービス側と接続を切断した場合、サービス側selectがsocketオブジェクトを傍受するリストからクライアントsocketオブジェクトを削除する必要があります.
import socket
import select
# socket , IP ,
sk = socket.socket()
sk.bind(('127.0.0.1', 1559))
sk.listen(5)
inputs = [sk]
while True:
rList, w, e = select.select(inputs, [], [], 1)
print("select socket >", len(inputs), " | socket >", len(rList))
for s in rList:
# socket socket
if s == sk:
conn, address = s.accept()
# conn socket
# socket , socket
# socket
# socket
# ,
inputs.append(conn)
conn.sendall(bytes('hello', encoding='utf8'))
# socket
else:
try:
#
msg = s.recv(1024)
# Linux
if not msg:
raise Exception(' ')
print(msg)
except Exception as ex:
# Windows
inputs.remove(s)
最新のserverを使用します.pyとclient.pyテストを行う場合、複数のクライアントを順次実行し、複数のクライアントを順次閉じ、サービス側のエコーは以下の通りです.
select socket > 1 | socket > 0
select socket > 1 | socket > 0
select socket > 1 | socket > 0
select socket > 1 | socket > 0
select socket > 1 | socket > 1
select socket > 2 | socket > 0
select socket > 2 | socket > 0
select socket > 2 | socket > 1
select socket > 3 | socket > 0
select socket > 3 | socket > 0
select socket > 3 | socket > 1
select socket > 4 | socket > 0
select socket > 4 | socket > 0
select socket > 4 | socket > 0
select socket > 4 | socket > 0
select socket > 4 | socket > 1
b''
select socket > 3 | socket > 0
select socket > 3 | socket > 0
select socket > 3 | socket > 1
b''
select socket > 2 | socket > 0
select socket > 2 | socket > 1
b''
select socket > 1 | socket > 0
select socket > 1 | socket > 0
select socket > 1 | socket > 0
select socket > 1 | socket > 0
特に注意:ここでのサービス側定義では、クライアントから空の値を受信すると、クライアントがサービス側との接続を切断する必要があるとデフォルトで考えられます.サービス側のこのデフォルトルールでは、クライアントを書くときに、クライアントが入力した値が空の場合に注意しなければならない.
クライアントへのメッセージの返信
import socket
import select
# socket , IP ,
sk = socket.socket()
sk.bind(('127.0.0.1', 1559))
sk.listen(5)
inputs = [sk]
while True:
rList, w, e = select.select(inputs, [], [], 1)
print("select socket >", len(inputs), " | socket >", len(rList))
for s in rList:
# socket socket
if s == sk:
conn, address = s.accept()
# conn socket
# socket , socket
# socket
# socket
# ,
inputs.append(conn)
conn.sendall(bytes('hello', encoding='utf8'))
# socket
else:
try:
#
msg = s.recv(1024)
# Linux
if not msg:
raise Exception(' ')
print(msg)
#
# ,
s.sendall(msg)
except Exception as ex:
# Windows
inputs.remove(s)
しかし、一般的には読み書き分離が行われ、selectにより読み書き分離(送受信分離)が実現される
select.selext()の2番目のパラメータ
rList, wList, e = select.select([], [], [], 1)
select.select()の2番目のパラメータに何の値があるか、wListに何の値があるか
selectのこの特性により,メッセージに返信するクライアントsocketオブジェクトをselectの2番目のパラメータに割り当てることができる.
import socket
import select
# socket , IP ,
sk = socket.socket()
sk.bind(('127.0.0.1', 1559))
sk.listen(5)
inputs = [sk]
outputs = []
while True:
rList, wList, e = select.select(inputs, outputs, [], 1)
print("---" * 20)
print("select inputs >", len(inputs), " | socket >", len(rList))
print("select outputs >", len(outputs), " | >", len(wList))
# rList( )
for s in rList:
# socket socket
if s == sk:
conn, address = s.accept()
# conn socket
# socket , socket
# socket
# socket
# ,
inputs.append(conn)
conn.sendall(bytes('hello', encoding='utf8'))
# socket
else:
try:
#
msg = s.recv(1024)
# Linux
if not msg:
raise Exception(' ')
else:
outputs.append(s)
print(msg)
#
# ,
# s.sendall(msg)
except Exception as ex:
# Windows
inputs.remove(s)
# wList( )
for s in wList:
#
s.sendall(bytes('server response', encoding='utf8'))
# , outputs socket
outputs.remove(s)
wList=サービス側にメッセージを送信するすべてのクライアントであり、メッセージを返信する必要があるクライアントのリストでもある
import socket
sk = socket.socket()
sk.connect(('127.0.0.1', 1559))
#
data = sk.recv(1024)
print(data)
while True:
i = input("> ")
#
sk.sendall(bytes(i, encoding='utf8'))
#
msg = sk.recv(1024)
print(str(msg, encoding='utf-8'))
sk.close()
プロセスの実行:
サービス側のエコー:
------------------------------------------------------------
select inputs > 1 | socket > 0
select outputs > 0 | > 0
------------------------------------------------------------
select inputs > 1 | socket > 0
select outputs > 0 | > 0
------------------------------------------------------------
select inputs > 2 | socket > 0
select outputs > 0 | > 0
------------------------------------------------------------
select inputs > 2 | socket > 1
select outputs > 0 | > 0
------------------------------------------------------------
select inputs > 3 | socket > 0
select outputs > 0 | > 0
------------------------------------------------------------
select inputs > 3 | socket > 1
select outputs > 0 | > 0
------------------------------------------------------------
select inputs > 4 | socket > 0
select outputs > 0 | > 0
------------------------------------------------------------
select inputs > 4 | socket > 1
select outputs > 0 | > 0
b'ps'
------------------------------------------------------------
select inputs > 4 | socket > 0
select outputs > 1 | > 1
------------------------------------------------------------
select inputs > 4 | socket > 0
select outputs > 0 | > 0
------------------------------------------------------------
select inputs > 4 | socket > 1
select outputs > 0 | > 0
b'ps'
------------------------------------------------------------
select inputs > 4 | socket > 0
select outputs > 1 | > 1
------------------------------------------------------------
select inputs > 4 | socket > 0
select outputs > 0 | > 0
------------------------------------------------------------
select inputs > 3 | socket > 0
select outputs > 0 | > 0
------------------------------------------------------------
select inputs > 3 | socket > 0
select outputs > 0 | > 0
------------------------------------------------------------
select inputs > 2 | socket > 0
select outputs > 0 | > 0
------------------------------------------------------------
select inputs > 2 | socket > 1
select outputs > 0 | > 0
------------------------------------------------------------
select inputs > 1 | socket > 0
select outputs > 0 | > 0
最初のクライアントのエコー:
b'hello'
> ps
server response
> ps
server response
>
以上のコードは简単な送受信メッセージの分离を実现して、今また少し需要が多くなって、现在すべてサービス端にメッセージを送信するクライアント、サービス端はすべて统一して同じ内容を返事して、今サービス端はEcho Serverの机能を実现して、つまりクライアントはどんな消息を送って、サービス端はクライアントにどんなメッセージを返事します
import socket
import select
# socket , IP ,
sk = socket.socket()
sk.bind(('127.0.0.1', 1559))
sk.listen(5)
inputs = [sk]
outputs = []
messages = {}
"""
messages = {
socket_obj1: [msg]
socket_obj2: [msg]
}
"""
while True:
rList, wList, e = select.select(inputs, outputs, [], 1)
print("---" * 20)
print("select inputs >", len(inputs), " | socket >", len(rList))
print("select outputs >", len(outputs), " | >", len(wList))
# rList( )
for s in rList:
# socket socket
if s == sk:
conn, address = s.accept()
# conn socket
# socket , socket
# socket
# socket
# ,
inputs.append(conn)
# messages key
messages[conn] = []
conn.sendall(bytes('hello', encoding='utf8'))
# socket
else:
try:
#
msg = s.recv(1024)
# Linux
if not msg:
raise Exception(' ')
else:
outputs.append(s)
messages[s].append(msg)
#
# ,
# s.sendall(msg)
except Exception as ex:
# Windows
inputs.remove(s)
# , messages
del messages[s]
# wList( )
for s in wList:
# messages
msg = messages[s].pop()
#
s.sendall(msg)
# , outputs socket
outputs.remove(s)
サービス側は以上の変更を行い、クライアントは変更する必要はありません.
まとめ
IO多重化を用いて,実際には同時効果のような擬似同時化を実現した.内部では、ブロック要求を効率的に処理するためにループが実際に使用されています.
Pythonにはselectモジュールがあり、select、poll、epollの3つの方法が提供され、システムのselect、poll、epollをそれぞれ呼び出してIO多重化を実現する.
selectメソッドの場合:
11, 22, 33 = select.select( 1, 2, 3, )
: ( )
:
select , , 。
1、 1 (accetp read), 1
2、 2 , 2
3、 3 , 3
4、 , select ,
= 1 , , select 1 , , , 。
追加:select poll epollの違い
IO多重化はシステムカーネルで実現され、システム内部ではforサイクルが維持され、オブジェクトに変化があるかどうかを一つ一つ検出する
まず明確にしなければならないのは、forサイクルの効率が高くないことです.
IO多重種別
実装の原理
リスニング対象の数
select
システム内部でforサイクルを維持
1024
poll
システム内部でforサイクルを維持
制限なし
epoll
ハンドルシーケンスが変化した場合にepollに自動的に通知
制限なし