マルチタスクサーバのいくつかの実装方法
14872 ワード
前の2つの文章はPythonの中でUDPソケットとTCPソケットを紹介して、そしてこの基礎の上で簡単なクライアントとサービス端を実現して、本文は次にサービス端のマルチタスク処理のいくつかの実現方式を紹介して、この方面の知識に対する1つの総括で、本文は以下のいくつかの実現方式を紹介します:マルチプロセスサービス マルチスレッドサービス 単一プロセス/スレッド非ブロックサービス select IO多重型サービス端末 を実現 epollイベント購読型サービス端末 geventコモン・サービス・エンド クライアント作成
サービス・エンドの作成を開始する前に、共通のクライアント・コードを準備し、作成したサービス・エンドはこのコードを使用してテストします.
マルチプロセスサービス
マルチプロセス・サービス・エンドの特徴は、Socket接続を受信するたびに、独立したプロセスを作成してサービスすることです.
マルチスレッドサーバ
上のコードを少し修正するだけで、マルチスレッドサーバを実現できます.
単一プロセス/スレッド非ブロックサービス
前のコードでは、なぜマルチプロセスまたはマルチスレッドを使用するのですか?これは、Socketオブジェクトの
なお、Socketオブジェクトを非ブロッキングに設定すると、その
selectはIO多重型サービス端子を実現
上記では、単一プロセス/スレッドの非ブロックサーバを非ブロックSocketとポーリングリストで実現しましたが、オペレーティングシステムの下部には
次に例を示します.
epollイベントサブスクリプション・サービス・エンド
以上、
次に、
Socketオブジェクトのファイル記述子を取得するには、次の手順に従います. select.EPOLLIN(読み取り可能) select.EPOLLOUT(書き込み可能) select.EPOLLET(ETモード) epollのファイル記述子の操作には、LT(level trigger)とET(edge trigger)の2つのモードがあります.LTモードはデフォルトモードであり、LTモードとETモードの違いは以下の通りである. LTモード:epollがディスクリプタイベントの発生を検出し、このイベントをアプリケーションに通知すると、アプリケーションはすぐにイベントを処理しなくてもよい.次回epollを呼び出すと、アプリケーションに再応答してイベントが通知されます. ETモード:epollがディスクリプタイベントの発生を検出し、このイベントをアプリケーションに通知すると、アプリケーションは直ちにイベントを処理する必要があります.処理しない場合、epollが次回呼び出されると、アプリケーションに応答してイベントが通知されません.
geventコラボレーション型サーバ
また、は、 を使用する必要がある.は、 を呼び出す必要がある.コード実行前に を呼び出す必要がある.
まとめ
この論文では、Pythonでマルチタスクサーバを実装するいくつかの一般的な方法を紹介し、マルチプロセス/マルチスレッド実装、非ブロックSocket実装、select実装、epoll実装、gevent実装を含む.一つの記録ですが、後で忘れたらいつでも振り返って調べることができます.
終わります.
サービス・エンドの作成を開始する前に、共通のクライアント・コードを準備し、作成したサービス・エンドはこのコードを使用してテストします.
from socket import *
def main():
cSocket = socket(AF_INET, SOCK_STREAM)
cSocket.connect(("192.168.2.142",3001))
while True:
msg = input("Enter Message:")
if msg == "q!":
break
else:
cSocket.send(msg.encode("utf-8"))
cSocket.close()
if __name__ == '__main__':
main()
マルチプロセスサービス
マルチプロセス・サービス・エンドの特徴は、Socket接続を受信するたびに、独立したプロセスを作成してサービスすることです.
from socket import *
from multiprocessing import Process
# Server ,
class Server():
@classmethod
def __prepareSocket(cls):
cls.sSocket = socket(AF_INET, SOCK_STREAM)
cls.sSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
cls.sSocket.bind(("",3001))
cls.sSocket.listen(5)
@classmethod
def startServer(cls):
cls.__prepareSocket()
while True:
#
clientSocket,clientAddr = cls.sSocket.accept()
print("%s , ..."%clientAddr[1])
# SocketHander ,
cp = SocketHander(clientSocket,clientAddr)
cp.start()
# SocketHander
class SocketHander(Process):
def __init__(self,clientSocket,clientAddr):
Process.__init__(self)
self.clientSocket = clientSocket
self.clientAddr = clientAddr
def run(self):
#
try:
while True:
recvMsg = self.clientSocket.recv(1024)
print("%s:%s"%(self.clientAddr[0],recvMsg.decode("utf-8")))
self.clientSocket.send("ding~".encode("utf-8"))
except:
print("%s ~"%self.clientAddr[0])
finally:
self.clientSocket.close()
if __name__ == '__main__':
Server.startServer()
マルチスレッドサーバ
上のコードを少し修正するだけで、マルチスレッドサーバを実現できます.
from socket import *
from threading import Thread
# Server ,
class Server():
@classmethod
def __prepareSocket(cls):
cls.sSocket = socket(AF_INET, SOCK_STREAM)
cls.sSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
cls.sSocket.bind(("",3001))
cls.sSocket.listen(5)
@classmethod
def startServer(cls):
cls.__prepareSocket()
while True:
#
clientSocket,clientAddr = cls.sSocket.accept()
print("%s , ..."%clientAddr[1])
# SocketHander ,
cp = SocketHander(clientSocket,clientAddr)
cp.start()
# SocketHander
class SocketHander(Thread):
def __init__(self,clientSocket,clientAddr):
Thread.__init__(self)
self.clientSocket = clientSocket
self.clientAddr = clientAddr
def run(self):
#
try:
while True:
recvMsg = self.clientSocket.recv(1024)
print("%s:%s"%(self.clientAddr[0],recvMsg.decode("utf-8")))
self.clientSocket.send("ding~".encode("utf-8"))
except:
print("%s ~"%self.clientAddr[0])
finally:
self.clientSocket.close()
if __name__ == '__main__':
Server.startServer()
単一プロセス/スレッド非ブロックサービス
前のコードでは、なぜマルチプロセスまたはマルチスレッドを使用するのですか?これは、Socketオブジェクトの
accept
メソッドとrecv
メソッドがブロックされているため、両方をプロセスまたはスレッドに配置すると、必ずブロックされます.そこで、accept
メソッドを1つのプロセス/スレッドに実行し、recv
メソッドを別のプロセス/スレッドに実行すると、ブロックが回避されます.accpet
とrecv
の方法が詰まっていなければ、この問題を解決できるのではないでしょうか.はい、ソケットを作成した後、setblocking
メソッドを呼び出して、パラメータFalse
に入力することができます.この場合、この2つのメソッドはブロックされません.また、クライアント接続が成功した後、すぐにサービス側にメッセージを送信するとは限らないため、Socketオブジェクトを呼び出すのに適したrecv
メソッドを特定することはできません.この問題を解決するために、クライアント接続が成功した後、作成したクライアントSocketをリストに読み込み、一定時間ごとにリストを巡回することができます.実装コードは次のとおりです.from socket import *
from time import sleep
class Server():
# Socket
clientSockets = []
@classmethod
def __prepareSocket(cls):
cls.sSocket = socket(AF_INET, SOCK_STREAM)
cls.sSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
# socket
cls.sSocket.setblocking(False)
cls.sSocket.bind(("",3001))
cls.sSocket.listen(5)
@classmethod
def startServer(cls):
cls.__prepareSocket()
#
while True:
#
try:
clientSocket,clientAddr = cls.sSocket.accept()
except:
pass
else:
print("%s , ..."%clientAddr[1])
# , , Socket
clientSocket.setblocking(False)
# ,
cls.clientSockets.append((clientSocket,clientAddr))
# 0.3 ,
sleep(0.3)
cls.__handleSocket()
@classmethod
def __handleSocket(cls):
for clientSocket,clientAddr in cls.clientSockets:
try:
recvMsg = clientSocket.recv(1024)
except:
pass
else:
if not len(recvMsg):
print("%s ..."%clientAddr[1])
# Socket
clientSocket.close()
# , Socket
cls.clientSockets.remove((clientSocket,clientAddr))
else:
clientSocket.send("ding~".encode("utf-8"))
print("%s:%s"%(clientAddr[1],recvMsg.decode("utf-8")))
if __name__ == '__main__':
Server.startServer()
なお、Socketオブジェクトを非ブロッキングに設定すると、その
accept
およびrecv
メソッドを使用する場合に例外処理を加える必要があります.これは、非ブロッキングSocketを使用すると、ポーリング時に新しい接続がないか、クライアントからメッセージが送信されないと異常が発生し、異常キャプチャが必要になるためです.selectはIO多重型サービス端子を実現
上記では、単一プロセス/スレッドの非ブロックサーバを非ブロックSocketとポーリングリストで実現しましたが、オペレーティングシステムの下部には
select
モジュールがあり、オペレーティングシステムの下部で完了したため、どのソケットが変化したかを検出するために使用されています.上記の手動サイクルよりも効率的です.select
の使用は簡単で、パラメータとして3つのリストを受信し、3つのリストをリスニングするselect
モジュールのselect
メソッドを呼び出すだけです.この3つのリストは、読み取り可能なソケットリスト、書き込み可能なソケットリスト、異常ソケットリストの順である.select
メソッドはブロックされ、受信した3つのリストに状態変化がある場合、3つのリストが返され、リストの要素は状態変化が発生した要素であり、返された3つのリストは、変化が発生したスケールソケットリスト、変化が発生した書き込み可能ソケットリスト、変化が発生した異常ソケットリストに一度に対応する.基本的な使い方:readble,writeable,exceptional = select(readbleList, writeableList, exceptionalList)
次に例を示します.
from socket import *
from select import select
class Server():
# Socket
readableSocketsList = []
@classmethod
def __prepareSocket(cls):
cls.sSocket = socket(AF_INET, SOCK_STREAM)
cls.sSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
cls.sSocket.bind(("",3001))
cls.sSocket.listen(5)
cls.readableSocketsList.append(cls.sSocket)
@classmethod
def startServer(cls):
cls.__prepareSocket()
#
while True:
readableSockets,writeableSockets,exceptionalSocket = select(cls.readableSocketsList,[],[])
cls.__handleSocket(readableSockets)
@classmethod
def __handleSocket(cls,readableSockets):
for sock in readableSockets:
# sSocket ,
if sock == cls.sSocket:
clientSocket,clientAddr = cls.sSocket.accept()
print("%s ..."%clientAddr[1])
# Sockets readableSocketsList
cls.readableSocketsList.append(clientSocket)
# sSocket ,
else:
try:
recvMsg = sock.recv(1024)
#
if not len(recvMsg):
sock.close()
cls.readableSocketsList.remove(sock)
else:
#
sock.send("ding~".encode("utf-8"))
print("%s:%s"%(sock.getpeername()[1],recvMsg.decode("utf-8")))
except:
pass
if __name__ == '__main__':
Server.startServer()
select
関数には、readableSocketsList
のリストが入力されています.このリストにSocketオブジェクトの状態が変化すると、すぐに変化したSocketオブジェクトのリストが得られ、このリストのSocketオブジェクトを操作できます.epollイベントサブスクリプション・サービス・エンド
以上、
select
モジュールを用いてIO多重化を完了し、ソケットオブジェクトの変化の検出はオペレーティングシステム内部で検出されたが、本質的にはソケットリストを遍歴操作し、効率は高くなく、select
を用いて同時発生を実現する際には並列量制限があり、一般的に32ビットマシンは1024個、64ビットマシンは2048個である.select
の役割は何ですか?ソケットリストの遍歴を通じて、状態の変化が発生したソケットを見つけることにほかならない.それでは、アクティブにソケットを遍歴するのではなく、どのソケットが変化したときにシステムに通知し、システムが変化したソケットを手に入れた後、私たちのPythonプログラムを知ることができ、効率は自然に高い.epoll
の使用は依然としてselect
に依存し、epoll
を使用する前にepoll
オブジェクトを作成する必要があります.epoll = select.epoll()
次に、
epoll
にイベントを登録します.epoll.register( Socket , )
Socketオブジェクトのファイル記述子を取得するには、次の手順に従います.
fno = socketObj.fileno()
epoll
のファイル記述子に対する動作は、3つの定数で表される.from socket import *
import select
class Server():
# Socket
clientSocktsList = {}
#
clietnAddrList = {}
@classmethod
def __prepareSocket(cls):
cls.sSocket = socket(AF_INET, SOCK_STREAM)
cls.sSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
cls.sSocket.bind(("",3001))
cls.sSocket.listen(5)
# epoll
cls.epoll = select.epoll()
# Socket epoll
cls.epoll.register(cls.sSocket.fileno(),select.EPOLLIN | select.EPOLLET)
@classmethod
def startServer(cls):
cls.__prepareSocket()
# ,
while True:
# poll
# , ,
socketList = cls.epoll.poll()
cls.__handleSocket(socketList)
@classmethod
def __handleSocket(cls,socketList):
for fno,event in socketList:
# Socket ,
if fno == cls.sSocket.fileno():
clientSocket,clientAddr = cls.sSocket.accept()
print("%s ..."%clientAddr[1])
# Socket clientSocktsList clietnAddrList
# key
socketFno = clientSocket.fileno()
cls.clientSocktsList[socketFno] = clientSocket
cls.clietnAddrList[socketFno] = clientAddr
# Socket epoll
cls.epoll.register(clientSocket.fileno(),select.EPOLLIN | select.EPOLLET)
# Socket Socket ,
# event ,
elif event == select.EPOLLIN:
clientSocket = cls.clientSocktsList[fno]
addr = cls.clietnAddrList[fno][1]
try:
recvMsg = clientSocket.recv(1024)
if not len(recvMsg):
print("%s "%addr)
clientSocket.close()
del cls.clientSocktsList[fno]
else:
print("%s:%s"%(addr,recvMsg.decode("utf-8")))
except:
pass
if __name__ == '__main__':
Server.startServer()
epoll
の使用とselect
の使用の処理は、変化したソケットリストを取得し、対応する処理を行うことと一致していることがわかる.違いは、オペレーティングシステム内部のepoll
とselect
に対する処理方式の違いにすぎない.geventコラボレーション型サーバ
また、
gevent
というコパスライブラリを使用して、マルチタスク処理のサーバを実装することもできます.まず、gevent
をインストールする必要があります.pip install gevent
gevent
を使用してサーバを実装する場合、gevent
ライブラリが提供するsocket
を使用する必要があります.システムが付属しているsocket
ではありません.コードは次のとおりです.from gevent import socket,monkey,spawn
# gevent , monkey patch_all
monkey.patch_all()
class Server():
@classmethod
def __prepareSocket(cls):
# genvent socket
cls.sSocket = socket.socket()
# socket
cls.sSocket.bind(("",3001))
cls.sSocket.listen(5)
@classmethod
def startServer(cls):
cls.__prepareSocket()
while True:
clientSocket,clientAddr = cls.sSocket.accept()
print("%s ..."%clientAddr[1])
# , gevent spawn
spawn(cls.__handleSocket,clientSocket,clientAddr)
@classmethod
def __handleSocket(cls,clientSocket,clientAddr):
while True:
try:
recvMsg = clientSocket.recv(1024)
if not len(recvMsg):
print("%s ..."%clientAddr[1])
clientSocket.close()
break
else:
print("%s:%s"%(clientAddr[1],recvMsg.decode("utf-8")))
except:
pass
if __name__ == '__main__':
Server.startServer()
gevent
コプロセッサを使用してマルチタスク・サーバを実装するには、次のいくつかの注意点があります.gevent
モジュールで提供するsocket
gevent
モジュールのspawn
関数を使用してターゲット関数monkey
の下のpatch_all
メソッドまとめ
この論文では、Pythonでマルチタスクサーバを実装するいくつかの一般的な方法を紹介し、マルチプロセス/マルチスレッド実装、非ブロックSocket実装、select実装、epoll実装、gevent実装を含む.一つの記録ですが、後で忘れたらいつでも振り返って調べることができます.
終わります.