マルチタスクサーバのいくつかの実装方法

14872 ワード

前の2つの文章はPythonの中でUDPソケットとTCPソケットを紹介して、そしてこの基礎の上で簡単なクライアントとサービス端を実現して、本文は次にサービス端のマルチタスク処理のいくつかの実現方式を紹介して、この方面の知識に対する1つの総括で、本文は以下のいくつかの実現方式を紹介します:
  • マルチプロセスサービス
  • マルチスレッドサービス
  • 単一プロセス/スレッド非ブロックサービス
  • select IO多重型サービス端末
  • を実現
  • 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メソッドを別のプロセス/スレッドに実行すると、ブロックが回避されます.accpetrecvの方法が詰まっていなければ、この問題を解決できるのではないでしょうか.はい、ソケットを作成した後、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つの定数で表される.
  • select.EPOLLIN(読み取り可能)
  • select.EPOLLOUT(書き込み可能)
  • select.EPOLLET(ETモード)
  • epollのファイル記述子の操作には、LT(level trigger)とET(edge trigger)の2つのモードがあります.LTモードはデフォルトモードであり、LTモードとETモードの違いは以下の通りである.
  • LTモード:epollがディスクリプタイベントの発生を検出し、このイベントをアプリケーションに通知すると、アプリケーションはすぐにイベントを処理しなくてもよい.次回epollを呼び出すと、アプリケーションに再応答してイベントが通知されます.
  • ETモード:epollがディスクリプタイベントの発生を検出し、このイベントをアプリケーションに通知すると、アプリケーションは直ちにイベントを処理する必要があります.処理しない場合、epollが次回呼び出されると、アプリケーションに応答してイベントが通知されません.
  • 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の使用の処理は、変化したソケットリストを取得し、対応する処理を行うことと一致していることがわかる.違いは、オペレーティングシステム内部のepollselectに対する処理方式の違いにすぎない.
    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実装を含む.一つの記録ですが、後で忘れたらいつでも振り返って調べることができます.
    終わります.