ホームページ > php教程 > php手册 > SocketServer モジュールの Python 徹底分析 (1) (V2.7.11)

SocketServer モジュールの Python 徹底分析 (1) (V2.7.11)

WBOY
リリース: 2016-09-08 08:29:06
オリジナル
1182 人が閲覧しました

1. 紹介(翻訳)

ユニバーサルソケットサーバークラス

このモジュールは、さまざまな側面からサーバーを定義するために最善を尽くします:

ソケットベースのサーバーの場合:
--アドレスファミリー:
- AF_INET{,6}: IP ソケット (デフォルト)
- AF_UNIX: Unix ドメインソケット
- その他、AF_DECNET ( を参照) (一般的には使用されません)
--ソケットタイプ:
- SOCK_STREAM (信頼性の高い接続 TCP)
- SOCK_DGRAM (UDP)

リクエストベースのサーバーの場合:
-- さらにリクエストを行う前に、クライアント アドレスを認証する必要があります (これにより、リクエストが認証されるまで、リクエストを行う必要があるすべてのプロセスが事実上ブロックされます)
-- 複数のリクエストを処理する方法:
- 同期 (一度に処理できるリクエストは 1 つだけ)
- フォーク (リクエストを処理するために新しいプロセスをフォーク)
- スレッド化 (リクエストを処理するための新しいスレッドの作成)

このモジュールのさまざまなクラスの中で、最も単純なサーバー タイプは同期 TCP/IP サーバーです。これは貧弱なクラス設計ですが、設計の型概念の一部も保持しています。

以下は 5 つのクラスの継承図であり、そのうちの 4 つは 4 種類の同期サーバーを表しています。 +-------------- +
|ベースサーバー |
+-------------- +
                                v
                                                                                                                                        | TCPサーバー |------>UnixStreamサーバー |
                                                                                                                                                                        v
                                                                                                | UDP サーバー |------>UnixDatagram サーバー |
                                                                                                                                   注: UnixDatagramServer は UnixStreamServer ではなく UDPServer を継承します。IP と Unix ストリーム サーバーの唯一の違いは、2 つのサーバー クラスの内容のほとんどが単純な繰り返しであることです。







フォークとスレッドは、ForkingMixIn および TreadingMixIn ミックスイン クラスに対して作成できます。例: スレッド UDP サーバー クラスは次のように作成されます: class ThreadingUDPServer(ThreadingMixIn, UDPServer): pass
(詳細については、以下の例を参照してください)


ミックスイン このクラスは UDPServer を定義するメソッドをオーバーライドするため、最初に実装する必要があります。異なるメンバー変数を設定すると、基本的なサーバーの構築方法も変わります。
サービスを実装するには、基本クラス BaseRequestHandler からそのハンドル メソッドを再定義する必要があります。次に、サービス クラスをオーバーライドされた Handle メソッド クラスと組み合わせて、新しいサービス クラスを実行します。
リクエスト処理クラスの TCP メソッドと UDP メソッドは異なります。これは、リクエスト処理サブクラス StreamRequestHandler または DatagramRequestHandler を使用することで非表示にすることができます。
もちろん、他の方法も考えられます。
たとえば、変更を要求されたメモリの状態がサービスに含まれている場合、フォーク サーバーを使用する意味がありません (子プロセスの変更は親プロセスの初期化状態に影響せず、親プロセスは初期化状態に影響を与えないためです)。この変更されたパラメータを他の子プロセスに渡さないでください)。この場合、スレッド サーバーを使用できますが、2 つのリクエストが同時に到着してサーバー状態の競合が発生するのを避けるために、「ロック」を使用する必要がある可能性が高くなります。
また、HTTPサーバーなどを構築している場合、クライアントからのリクエストを処理する際にすべてのデータが外部(ファイルシステムなど)に保存されるため、クライアントのデータの読み込みが遅い場合には同期クラスが使用されます。サービスが応答しなくなるため、時間がかかる場合があります。
場合によっては、リクエストの同期には適切なメソッドが必要ですが、子プロセスでリクエストを完了するためにリクエスト データの影響を受けることがあります。これは、同期サーバーを使用し、リクエスト処理クラスの Handle メソッドにフォークの処理を明示的に指定することで実現できます。
複数の同時リクエストを処理するもう 1 つの方法は、select() メソッドを使用して、完了したリクエストの明示的な形式を維持して、次にどのリクエストに応答するかを決定する (または新しいリクエストを処理するかどうかを決定する) ことです。これはストリーミングにとって非常に重要です。各クライアントが長時間接続を確立する必要がある場合のサービス。 (スレッドやサブプロセスは使用しないことが前提です)

2. 使用されるすべてのクラスメソッド
リーリー


3.BaseServer と BaseRequestHandler


Python はネットワーク サービスを 2 つの主要なクラスに抽象化します。1 つは接続関連のネットワーク操作を処理するために使用される Server クラスで、もう 1 つはデータ関連の操作を処理するために使用される RequestHandler クラスです。また、サーバーを拡張してマルチプロセスまたはマルチスレッドを実装するための 2 つの MixIn クラスを提供します。ネットワークサービスを構築する場合、ServerとRequestHandlerは分離されません。RequestHandlerのインスタンスオブジェクトはServerと連携して動作します。

3.1 BaseServer 分析
外部から呼び出すことができる BaseServer メソッド:
- __init__(server_address, RequestHandlerClass)
-serve_forever(poll_interval=0.5)
- シャットダウン()
- handle_request() #serve_forever()を使用しない場合
- fileno() -> int # for select()
つまり、init を通じて初期化し、serve_forever() メソッドと handle_request() メソッドを外部に提供できます

3.1.1 init初期化

リーリー

__init__ の主な機能は、サーバー オブジェクトを作成し、server_address と RequestHandlerClass を初期化することです。
server_address は、ホスト アドレスとポートを含むタプルです。


3.1.2serve_forever
リーリー

ここでは select() 関数が使用されています。つまり、server_forever は、選択ポーリングに使用される時間を表す poll_interval=0.5 パラメーターを受け取り、無限ループに入ります。このループでは、poll_interval 秒ごとに選択します。ポーリングは 1 回です (ブロック)。ここ) ネットワーク IO を監視します。新しいネットワーク接続要求が到着すると、新しい接続を処理するために _handle_request_noblock() メソッドが呼び出されます。


3.1.3 _handle_request_noblock()
リーリー

 

英文说明已经说的很明确,该方法处理的是一个非阻塞请求,首先通过get_request()方法获取连接,具体实现在其子类,一旦获取了连接,立即调用verify_request认证连接信息,通过认证,则调用process_request()方法处理请求,如果中途出现错误,则调用handle_error处理错误,同时,调用shutdown_request()方法结束这个连接。
那下面我们就先看上面提到的其他几个函数调用:
    <span style="color: #0000ff;">def</span><span style="color: #000000;"> verify_request(self, request, client_address):
        </span><span style="color: #800000;">"""</span><span style="color: #800000;">Verify the request.  May be overridden.

        Return True if we should proceed with this request.

        </span><span style="color: #800000;">"""</span>
        <span style="color: #0000ff;">return</span><span style="color: #000000;"> True


    </span><span style="color: #0000ff;">def</span><span style="color: #000000;"> process_request(self, request, client_address):
        </span><span style="color: #800000;">"""</span><span style="color: #800000;">Call finish_request.

        Overridden by ForkingMixIn and ThreadingMixIn.

        </span><span style="color: #800000;">"""</span><span style="color: #000000;">
        self.finish_request(request, client_address)
        self.shutdown_request(request)

    </span><span style="color: #0000ff;">def</span><span style="color: #000000;"> handle_error(self, request, client_address):
        </span><span style="color: #800000;">"""</span><span style="color: #800000;">Handle an error gracefully.  May be overridden.

        The default is to print a traceback and continue.

        </span><span style="color: #800000;">"""</span>
        <span style="color: #0000ff;">print</span> <span style="color: #800000;">'</span><span style="color: #800000;">-</span><span style="color: #800000;">'</span>*40
        <span style="color: #0000ff;">print</span> <span style="color: #800000;">'</span><span style="color: #800000;">Exception happened during processing of request from</span><span style="color: #800000;">'</span><span style="color: #000000;">,
        </span><span style="color: #0000ff;">print</span><span style="color: #000000;"> client_address
        </span><span style="color: #0000ff;">import</span><span style="color: #000000;"> traceback
        traceback.print_exc() </span><span style="color: #008000;">#</span><span style="color: #008000;"> XXX But this goes to stderr!</span>
        <span style="color: #0000ff;">print</span> <span style="color: #800000;">'</span><span style="color: #800000;">-</span><span style="color: #800000;">'</span>*40


    <span style="color: #0000ff;">def</span><span style="color: #000000;"> shutdown_request(self, request):
        </span><span style="color: #800000;">"""</span><span style="color: #800000;">Called to shutdown and close an individual request.</span><span style="color: #800000;">"""</span><span style="color: #000000;">
        self.close_request(request)</span><pre name=<span style="color: #800000;">"</span><span style="color: #800000;">code</span><span style="color: #800000;">"</span> <span style="color: #0000ff;">class</span>=<span style="color: #800000;">"</span><span style="color: #800000;">python</span><span style="color: #800000;">"</span>>    

    <span style="color: #0000ff;">def</span><span style="color: #000000;"> finish_request(self, request, client_address):
        </span><span style="color: #800000;">"""</span><span style="color: #800000;">Finish one request by instantiating RequestHandlerClass.</span><span style="color: #800000;">"""</span><span style="color: #000000;">
        self.RequestHandlerClass(request, client_address, self)</span>
ログイン後にコピー

 


ログイン後にコピー
verify_request()方法对request进行验证,通常会被子类重写。
process_request需要注意一下,它被ForkingMixIn 和 ThreadingMixIn重写,因此是mixin的入口,ForkingMixIn和ThreadingMixIn分别进行多进程和多线程的配置,并且调用finish_request()完成请求,调用shutdown_request()结束请求。
handle_error也可以被子类重写,打印错误的信息和客户端地址。
finish_request()处理完毕请求,在__init__中创建requestHandler对象,并通过requestHandler做出具体的处理。

3.1.4 handle_request()
    <span style="color: #0000ff;">def</span><span style="color: #000000;"> handle_request(self):
        </span><span style="color: #800000;">"""</span><span style="color: #800000;">Handle one request, possibly blocking.

        Respects self.timeout.
        </span><span style="color: #800000;">"""</span>
        <span style="color: #008000;">#</span><span style="color: #008000;"> Support people who used socket.settimeout() to escape</span>
        <span style="color: #008000;">#</span><span style="color: #008000;"> handle_request before self.timeout was available.</span>
        timeout =<span style="color: #000000;"> self.socket.gettimeout()
        </span><span style="color: #0000ff;">if</span> timeout <span style="color: #0000ff;">is</span><span style="color: #000000;"> None:
            timeout </span>=<span style="color: #000000;"> self.timeout
        </span><span style="color: #0000ff;">elif</span> self.timeout <span style="color: #0000ff;">is</span> <span style="color: #0000ff;">not</span><span style="color: #000000;"> None:
            timeout </span>=<span style="color: #000000;"> min(timeout, self.timeout)
        fd_sets </span>=<span style="color: #000000;"> _eintr_retry(select.select, [self], [], [], timeout)
        </span><span style="color: #0000ff;">if</span> <span style="color: #0000ff;">not</span><span style="color: #000000;"> fd_sets[0]:
            self.handle_timeout()
            </span><span style="color: #0000ff;">return</span><span style="color: #000000;">
        self._handle_request_noblock()</span>
ログイン後にコピー

 

上面已经提到,如果你没有用到server_forever()方法,说明你希望使用的是阻塞请求来处理连接,如英文描述所说,该方法只是处理一个阻塞的请求,仍然使用select()方法轮询来监听网络连接,但是需要考虑时间超时影响,一旦超时,调用handle_timeout()方法处理超时,一般在子类重写该方法;如果在超时之前监听到了网络的连接请求,则同server_forever一样,调用_handle_request_noblock()方法,完成对新的连接的请求处理。

3.2 BaseRequestHandler分析
<span style="color: #0000ff;">class</span><span style="color: #000000;"> BaseRequestHandler:

    </span><span style="color: #800000;">"""</span><span style="color: #800000;">Base class for request handler classes.

    This class is instantiated for each request to be handled.  The
    constructor sets the instance variables request, client_address
    and server, and then calls the handle() method.  To implement a
    specific service, all you need to do is to derive a class which
    defines a handle() method.

    The handle() method can find the request as self.request, the
    client address as self.client_address, and the server (in case it
    needs access to per-server information) as self.server.  Since a
    separate instance is created for each request, the handle() method
    can define arbitrary other instance variariables.

    </span><span style="color: #800000;">"""</span>

    <span style="color: #0000ff;">def</span> <span style="color: #800080;">__init__</span><span style="color: #000000;">(self, request, client_address, server):
        self.request </span>=<span style="color: #000000;"> request
        self.client_address </span>=<span style="color: #000000;"> client_address
        self.server </span>=<span style="color: #000000;"> server
        self.setup()
        </span><span style="color: #0000ff;">try</span><span style="color: #000000;">:
            self.handle()
        </span><span style="color: #0000ff;">finally</span><span style="color: #000000;">:
            self.finish()

    </span><span style="color: #0000ff;">def</span><span style="color: #000000;"> setup(self):
        </span><span style="color: #0000ff;">pass</span>

    <span style="color: #0000ff;">def</span><span style="color: #000000;"> handle(self):
        </span><span style="color: #0000ff;">pass</span>

    <span style="color: #0000ff;">def</span><span style="color: #000000;"> finish(self):
        </span><span style="color: #0000ff;">pass</span>
ログイン後にコピー

 

      以上描述说明,所有requestHandler都继承BaseRequestHandler基类,该类会处理每一个请求。在__init__中初始化实例变量request、client_address、server,然后调用handle()方法完成请求处理。那么,我们唯一需要做的就是重写好Handle()方法,处理所有的请求。
 
总结:构建一个网络服务,需要一个BaseServer用于处理网络IO,同时在内部创建requestHandler对象,对所有具体的请求做处理。

四、各种子类

4.1 由BaseServer衍生的子类

4.1.1 TCPServer
<span style="color: #0000ff;">class</span><span style="color: #000000;"> TCPServer(BaseServer):
    address_family </span>=<span style="color: #000000;"> socket.AF_INET

    socket_type </span>=<span style="color: #000000;"> socket.SOCK_STREAM

    request_queue_size </span>= 5<span style="color: #000000;">

    allow_reuse_address </span>=<span style="color: #000000;"> False

    </span><span style="color: #0000ff;">def</span> <span style="color: #800080;">__init__</span>(self, server_address, RequestHandlerClass, bind_and_activate=<span style="color: #000000;">True):
        </span><span style="color: #800000;">"""</span><span style="color: #800000;">Constructor.  May be extended, do not override.</span><span style="color: #800000;">"""</span><span style="color: #000000;">
        BaseServer.</span><span style="color: #800080;">__init__</span><span style="color: #000000;">(self, server_address, RequestHandlerClass)
        self.socket </span>=<span style="color: #000000;"> socket.socket(self.address_family,
                                    self.socket_type)
        </span><span style="color: #0000ff;">if</span><span style="color: #000000;"> bind_and_activate:
            </span><span style="color: #0000ff;">try</span><span style="color: #000000;">:
                self.server_bind()
                self.server_activate()
            </span><span style="color: #0000ff;">except</span><span style="color: #000000;">:
                self.server_close()
                </span><span style="color: #0000ff;">raise</span>

    <span style="color: #0000ff;">def</span><span style="color: #000000;"> server_bind(self):
        </span><span style="color: #800000;">"""</span><span style="color: #800000;">Called by constructor to bind the socket.

        May be overridden.

        </span><span style="color: #800000;">"""</span>
        <span style="color: #0000ff;">if</span><span style="color: #000000;"> self.allow_reuse_address:
            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, </span>1<span style="color: #000000;">)
        self.socket.bind(self.server_address)
        self.server_address </span>=<span style="color: #000000;"> self.socket.getsockname()

    </span><span style="color: #0000ff;">def</span><span style="color: #000000;"> server_activate(self):
        </span><span style="color: #800000;">"""</span><span style="color: #800000;">Called by constructor to activate the server.

        May be overridden.

        </span><span style="color: #800000;">"""</span><span style="color: #000000;">
        self.socket.listen(self.request_queue_size)

    </span><span style="color: #0000ff;">def</span><span style="color: #000000;"> server_close(self):
        </span><span style="color: #800000;">"""</span><span style="color: #800000;">Called to clean-up the server.

        May be overridden.

        </span><span style="color: #800000;">"""</span><span style="color: #000000;">
        self.socket.close()

    </span><span style="color: #0000ff;">def</span><span style="color: #000000;"> fileno(self):
        </span><span style="color: #800000;">"""</span><span style="color: #800000;">Return socket file number.

        Interface required by select().

        </span><span style="color: #800000;">"""</span>
        <span style="color: #0000ff;">return</span><span style="color: #000000;"> self.socket.fileno()

    </span><span style="color: #0000ff;">def</span><span style="color: #000000;"> get_request(self):
        </span><span style="color: #800000;">"""</span><span style="color: #800000;">Get the request and client address from the socket.

        May be overridden.

        </span><span style="color: #800000;">"""</span>
        <span style="color: #0000ff;">return</span><span style="color: #000000;"> self.socket.accept()

    </span><span style="color: #0000ff;">def</span><span style="color: #000000;"> shutdown_request(self, request):
        </span><span style="color: #800000;">"""</span><span style="color: #800000;">Called to shutdown and close an individual request.</span><span style="color: #800000;">"""</span>
        <span style="color: #0000ff;">try</span><span style="color: #000000;">:
            </span><span style="color: #008000;">#</span><span style="color: #008000;">explicitly shutdown.  socket.close() merely releases</span>
            <span style="color: #008000;">#</span><span style="color: #008000;">the socket and waits for GC to perform the actual close.</span>
<span style="color: #000000;">            request.shutdown(socket.SHUT_WR)
        </span><span style="color: #0000ff;">except</span><span style="color: #000000;"> socket.error:
            </span><span style="color: #0000ff;">pass</span> <span style="color: #008000;">#</span><span style="color: #008000;">some platforms may raise ENOTCONN here</span>
<span style="color: #000000;">        self.close_request(request)

    </span><span style="color: #0000ff;">def</span><span style="color: #000000;"> close_request(self, request):
        </span><span style="color: #800000;">"""</span><span style="color: #800000;">Called to clean up an individual request.</span><span style="color: #800000;">"""</span><span style="color: #000000;">
        request.close()</span>
ログイン後にコピー

 


 在BaseServer基础上增加了一个TCP的socket连接,使用server_bind、server_activate、server_close处理TCP启停等操作,同时增加了get_request、shutdown_request、close_request处理客户端请求。

4.1.2 UDPServer
<span style="color: #0000ff;">class</span><span style="color: #000000;"> UDPServer(TCPServer):

    </span><span style="color: #800000;">"""</span><span style="color: #800000;">UDP server class.</span><span style="color: #800000;">"""</span><span style="color: #000000;">

    allow_reuse_address </span>=<span style="color: #000000;"> False

    socket_type </span>=<span style="color: #000000;"> socket.SOCK_DGRAM

    max_packet_size </span>= 8192

    <span style="color: #0000ff;">def</span><span style="color: #000000;"> get_request(self):
        data, client_addr </span>=<span style="color: #000000;"> self.socket.recvfrom(self.max_packet_size)
        </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> (data, self.socket), client_addr

    </span><span style="color: #0000ff;">def</span><span style="color: #000000;"> server_activate(self):
        </span><span style="color: #008000;">#</span><span style="color: #008000;"> No need to call listen() for UDP.</span>
        <span style="color: #0000ff;">pass</span>

    <span style="color: #0000ff;">def</span><span style="color: #000000;"> shutdown_request(self, request):
        </span><span style="color: #008000;">#</span><span style="color: #008000;"> No need to shutdown anything.</span>
<span style="color: #000000;">        self.close_request(request)

    </span><span style="color: #0000ff;">def</span><span style="color: #000000;"> close_request(self, request):
        </span><span style="color: #008000;">#</span><span style="color: #008000;"> No need to close anything.</span>
        <span style="color: #0000ff;">pass</span>
ログイン後にコピー

 

继承自TCPServer,将socket改为了SOCK_DGRAM型,并修改了get_request,用于从SOCK_DGRAM中获取request。同时server_activate、shutdown_request、close_request都改成了空(UDP不需要),比TCP简单一些。

4.2 由BaseRequestHandler衍生的子类

4.2.1 StreamRequestHandler
<span style="color: #0000ff;">class</span><span style="color: #000000;"> StreamRequestHandler(BaseRequestHandler):
    rbufsize </span>= -1<span style="color: #000000;">
    wbufsize </span>=<span style="color: #000000;"> 0
    timeout </span>=<span style="color: #000000;"> None
    disable_nagle_algorithm </span>=<span style="color: #000000;"> False
    </span><span style="color: #0000ff;">def</span><span style="color: #000000;"> setup(self):
        self.connection </span>=<span style="color: #000000;"> self.request
        </span><span style="color: #0000ff;">if</span> self.timeout <span style="color: #0000ff;">is</span> <span style="color: #0000ff;">not</span><span style="color: #000000;"> None:
            self.connection.settimeout(self.timeout)
        </span><span style="color: #0000ff;">if</span><span style="color: #000000;"> self.disable_nagle_algorithm:
            self.connection.setsockopt(socket.IPPROTO_TCP,
                                       socket.TCP_NODELAY, True)
        self.rfile </span>= self.connection.makefile(<span style="color: #800000;">'</span><span style="color: #800000;">rb</span><span style="color: #800000;">'</span><span style="color: #000000;">, self.rbufsize)
        self.wfile </span>= self.connection.makefile(<span style="color: #800000;">'</span><span style="color: #800000;">wb</span><span style="color: #800000;">'</span><span style="color: #000000;">, self.wbufsize)

    </span><span style="color: #0000ff;">def</span><span style="color: #000000;"> finish(self):
        </span><span style="color: #0000ff;">if</span> <span style="color: #0000ff;">not</span><span style="color: #000000;"> self.wfile.closed:
            </span><span style="color: #0000ff;">try</span><span style="color: #000000;">:
                self.wfile.flush()
            </span><span style="color: #0000ff;">except</span><span style="color: #000000;"> socket.error:
                </span><span style="color: #008000;">#</span><span style="color: #008000;"> An final socket error may have occurred here, such as</span>
                <span style="color: #008000;">#</span><span style="color: #008000;"> the local error ECONNABORTED.</span>
                <span style="color: #0000ff;">pass</span><span style="color: #000000;">
        self.wfile.close()
        self.rfile.close()</span>
ログイン後にコピー

 

    最主要的功能是根据socket生成了读写socket用的两个文件对象(可以理解为句柄)rfile和wfile

4.2.2 DatagramRequestHandler
<span style="color: #0000ff;">class</span><span style="color: #000000;"> DatagramRequestHandler(BaseRequestHandler):

    </span><span style="color: #008000;">#</span><span style="color: #008000;"> XXX Regrettably, I cannot get this working on Linux;</span>
    <span style="color: #008000;">#</span><span style="color: #008000;"> s.recvfrom() doesn't return a meaningful client address.</span>

    <span style="color: #800000;">"""</span><span style="color: #800000;">Define self.rfile and self.wfile for datagram sockets.</span><span style="color: #800000;">"""</span>

    <span style="color: #0000ff;">def</span><span style="color: #000000;"> setup(self):
        </span><span style="color: #0000ff;">try</span><span style="color: #000000;">:
            </span><span style="color: #0000ff;">from</span> cStringIO <span style="color: #0000ff;">import</span><span style="color: #000000;"> StringIO
        </span><span style="color: #0000ff;">except</span><span style="color: #000000;"> ImportError:
            </span><span style="color: #0000ff;">from</span> StringIO <span style="color: #0000ff;">import</span><span style="color: #000000;"> StringIO
        self.packet, self.socket </span>=<span style="color: #000000;"> self.request
        self.rfile </span>=<span style="color: #000000;"> StringIO(self.packet)
        self.wfile </span>=<span style="color: #000000;"> StringIO()

    </span><span style="color: #0000ff;">def</span><span style="color: #000000;"> finish(self):
        self.socket.sendto(self.wfile.getvalue(), self.client_address)</span>
ログイン後にコピー

 

同样是生成rfile和wfile,但UDP不直接关联socket。这里的rfile是直接由从UDP中读取的数据生成的,wfile则是新建了一个StringIO,用于写数据。





(题目起的有点大,部分剖析的不好,等之后再往祖坟上刨。。。。^-^)

参考博客:http://www.cnblogs.com/tuzkee/p/3573210.html
             http://www.jianshu.com/p/357e436936bf


 







 
 
 

 


 

ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のおすすめ
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート