ソケットプログラミングHOWTO—Pythonドキュメント

提供:Dev Guides
< PythonPython/docs/3.8/howto/sockets
移動先:案内検索

ソケットプログラミングHOWTO

著者
ゴードン・マクミラン

概要

ソケットはほぼすべての場所で使用されていますが、最も深刻に誤解されているテクノロジーの1つです。 これは、ソケットの10,000フィートの概要です。 これは実際にはチュートリアルではありません。操作を開始するには、まだ作業が必要です。 それは細かい点をカバーしていません(そしてそれらはたくさんあります)が、それがあなたにそれらをきちんと使い始めるのに十分な背景を与えることを願っています。


ソケット

INETについてのみ説明します(つまり、 IPv4)ソケットですが、使用中のソケットの少なくとも99 % oを占めています。 そして、私はSTREAMについてのみ話します(すなわち TCP)ソケット-自分が何をしているのかを本当に理解していない限り(この場合、このHOWTOは適していません!)、STREAMソケットの動作とパフォーマンスは他の何よりも優れています。 ソケットとは何かという謎と、ブロッキングソケットと非ブロッキングソケットの操作方法に関するヒントを明らかにしようと思います。 しかし、私はソケットのブロックについて話すことから始めます。 非ブロッキングソケットを扱う前に、それらがどのように機能するかを知る必要があります。

これらのことを理解する上での問題の一部は、「ソケット」がコンテキストに応じて、微妙に異なる多くのことを意味する可能性があることです。 そこでまず、会話のエンドポイントである「クライアント」ソケットと、交換手に似た「サーバー」ソケットを区別しましょう。 クライアントアプリケーション(ブラウザなど)は、「クライアント」ソケットのみを使用します。 話しているWebサーバーは、「サーバー」ソケットと「クライアント」ソケットの両方を使用します。

歴史

IPC のさまざまな形式の中で、ソケットが群を抜いて最も人気があります。 どのプラットフォームでも、より高速な他の形式のIPCが存在する可能性がありますが、クロスプラットフォーム通信の場合、ソケットは町で唯一のゲームです。

それらは、UnixのBSDフレーバーの一部としてバークレーで発明されました。 彼らはインターネットで山火事のように広がった。 正当な理由があります—ソケットとINETの組み合わせにより、世界中の任意のマシンとの通信が信じられないほど簡単になります(少なくとも他のスキームと比較して)。


ソケットの作成

大まかに言えば、このページに移動したリンクをクリックすると、ブラウザは次のような動作をしました。

# create an INET, STREAMing socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# now connect to the web server on port 80 - the normal http port
s.connect(("www.python.org", 80))

connectが完了すると、ソケットsを使用して、ページのテキストの要求を送信できます。 同じソケットが応答を読み取り、その後破棄されます。 そうです、破壊されました。 クライアントソケットは通常、1つの交換(または順次交換の小さなセット)にのみ使用されます。

Webサーバーで何が起こるかは、もう少し複雑です。 まず、Webサーバーは「サーバーソケット」を作成します。

# create an INET, STREAMing socket
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# bind the socket to a public host, and a well-known port
serversocket.bind((socket.gethostname(), 80))
# become a server socket
serversocket.listen(5)

注意すべき点がいくつかあります。ソケットが外の世界から見えるように、socket.gethostname()を使用しました。 s.bind(('localhost', 80))またはs.bind(('127.0.0.1', 80))を使用した場合でも、「サーバー」ソケットはありますが、同じマシン内でのみ表示されます。 s.bind((, 80))は、マシンがたまたま持っている任意のアドレスからソケットに到達できることを指定します。

2つ目の注意点:通常、少数のポートは「既知の」サービス(HTTP、SNMPなど)用に予約されています。 遊んでいる場合は、高い数字(4桁)を使用してください。

最後に、listenの引数は、外部接続を拒否する前に、最大5つの接続要求(通常の最大値)をキューに入れるようにソケットライブラリに指示します。 コードの残りの部分が適切に記述されていれば、それで十分です。

ポート80でリッスンする「サーバー」ソケットができたので、Webサーバーのメインループに入ることができます。

while True:
    # accept connections from outside
    (clientsocket, address) = serversocket.accept()
    # now do something with the clientsocket
    # in this case, we'll pretend this is a threaded server
    ct = client_thread(clientsocket)
    ct.run()

このループが機能する一般的な方法は、実際には3つあります。スレッドをディスパッチしてclientsocketを処理する、新しいプロセスを作成してclientsocketを処理する、またはこのアプリを再構築して非ブロッキングソケットを使用する、マルチプレックスです。 「サーバー」ソケットと、selectを使用するアクティブなclientsocketの間。 これについては後で詳しく説明します。 ここで理解しておくべき重要なことは、これです。これは、「サーバー」ソケットが行うすべてです。 データは送信されません。 データを受信しません。 「クライアント」ソケットを生成するだけです。 各clientsocketは、バインドされているホストとポートに対してconnect()を実行する other 「クライアント」ソケットに応答して作成されます。 そのclientsocketを作成するとすぐに、さらに接続をリッスンすることに戻ります。 2人の「クライアント」は自由にチャットできます。動的に割り当てられたポートを使用しており、会話が終了すると再利用されます。

IPC

1台のマシン上の2つのプロセス間で高速IPCが必要な場合は、パイプまたは共有メモリを調べる必要があります。 AF_INETソケットを使用する場合は、「サーバー」ソケットを'localhost'にバインドします。 ほとんどのプラットフォームでは、これはネットワークコードのいくつかのレイヤーの周りにショートカットを取り、かなり高速になります。

も参照してください

マルチプロセッシングは、クロスプラットフォームIPCを高レベルのAPIに統合します。


ソケットの使用

最初に注意することは、Webブラウザの「クライアント」ソケットとWebサーバーの「クライアント」ソケットは同一の獣であるということです。 つまり、これは「ピアツーピア」の会話です。 言い換えれば、デザイナーとして、会話のエチケットのルールを決定する必要があります。 通常、connect ingソケットは、要求またはおそらくサインオンを送信することによって会話を開始します。 しかし、それは設計上の決定です-それはソケットのルールではありません。

これで、コミュニケーションに使用する動詞のセットが2つあります。 sendrecvを使用するか、クライアントソケットをファイルのような獣に変換してreadwriteを使用できます。 後者は、Javaがソケットを提示する方法です。 ソケットでflushを使用する必要があることを警告する場合を除いて、ここでは説明しません。 これらはバッファリングされた「ファイル」であり、よくある間違いはwrite何かをしてから、readに応答することです。 そこにflushがないと、要求がまだ出力バッファーにある可能性があるため、応答を永遠に待つ可能性があります。

ここで、ソケットの主要な障害に到達します。sendrecvはネットワークバッファで動作します。 それらの主な焦点はネットワークバッファの処理であるため、それらは必ずしもあなたがそれらに渡す(またはそれらに期待する)すべてのバイトを処理するわけではありません。 一般に、関連するネットワークバッファがいっぱいになるか(send)、空になると(recv)返されます。 次に、処理したバイト数を教えてくれます。 あなたのメッセージが完全に処理されるまで、彼らに再び電話をかけるのはあなたのの責任です。

recvが0バイトを返す場合、反対側が接続を閉じている(または閉じている途中である)ことを意味します。 この接続でこれ以上データを受信することはありません。 これまで。 データを正常に送信できる場合があります。 これについては後で詳しく説明します。

HTTPのようなプロトコルは、1回の転送にのみソケットを使用します。 クライアントは要求を送信してから、応答を読み取ります。 それでおしまい。 ソケットは破棄されます。 これは、クライアントが0バイトを受信することで応答の終了を検出できることを意味します。

ただし、ソケットを再利用してさらに転送する場合は、ソケットに EOT がないことを理解する必要があります。繰り返します。 X163X] またはrecvは、0バイトを処理した後に戻り、接続が切断されました。 接続が not で切断されている場合、ソケットは not で、これ以上読み取るものがないことを通知するため、recvを永遠に待つことができます(今のところ) 。 少し考えてみると、ソケットの基本的な真実に気付くでしょう。メッセージは固定長(yuck)、、または区切り(shrug)のいずれかでなければなりません。 またはそれらの長さを示します(はるかに良い)、または接続をシャットダウンして終了します。 選択は完全にあなた次第です(しかし、いくつかの方法は他の方法よりも正しいです)。

接続を終了したくないと仮定すると、最も簡単な解決策は固定長のメッセージです。

class MySocket:
    """demonstration class only
      - coded for clarity, not efficiency
    """

    def __init__(self, sock=None):
        if sock is None:
            self.sock = socket.socket(
                            socket.AF_INET, socket.SOCK_STREAM)
        else:
            self.sock = sock

    def connect(self, host, port):
        self.sock.connect((host, port))

    def mysend(self, msg):
        totalsent = 0
        while totalsent < MSGLEN:
            sent = self.sock.send(msg[totalsent:])
            if sent == 0:
                raise RuntimeError("socket connection broken")
            totalsent = totalsent + sent

    def myreceive(self):
        chunks = []
        bytes_recd = 0
        while bytes_recd < MSGLEN:
            chunk = self.sock.recv(min(MSGLEN - bytes_recd, 2048))
            if chunk == b'':
                raise RuntimeError("socket connection broken")
            chunks.append(chunk)
            bytes_recd = bytes_recd + len(chunk)
        return b''.join(chunks)

ここでの送信コードは、ほとんどすべてのメッセージングスキームで使用できます。Pythonでは文字列を送信し、len()を使用してその長さを決定できます(\0文字が埋め込まれている場合でも)。 より複雑になるのは、ほとんどの場合、受信コードです。 (Cでは、メッセージに\0が埋め込まれている場合、strlenを使用できないことを除いて、それほど悪くはありません。)

最も簡単な拡張機能は、メッセージの最初の文字をメッセージタイプのインジケータにし、タイプに長さを決定させることです。 これで、2つのrecvができました。1つ目は(少なくとも)最初の文字を取得して長さを調べ、2つ目はループ内で残りを取得します。 区切られたルートを使用する場合は、任意のチャンクサイズで受信し(4096または8192がネットワークバッファサイズに適していることがよくあります)、受信したものをスキャンして区切り文字を探します。

注意すべき1つの問題:会話型プロトコルで複数のメッセージを(何らかの応答なしで)連続して送信でき、recvを任意のチャンクサイズで渡すと、次のメッセージ。 必要になるまで、それを脇に置いて保持する必要があります。

メッセージの前に長さ(たとえば、5つの数字)を付けると、より複雑になります。これは、(信じられないかもしれませんが)1つのrecvで5文字すべてを取得できない場合があるためです。 遊んでみると、それでうまくいくでしょう。 ただし、ネットワークの負荷が高い場合、2つのrecvループを使用しない限り、コードはすぐに壊れます。最初のループは長さを決定し、2番目のループはメッセージのデータ部分を取得します。 汚い。 これは、sendが1回のパスですべてを取り除くことができるとは限らないことに気付くときでもあります。 そして、これを読んだにもかかわらず、あなたは最終的にそれによって噛まれるでしょう!

スペースの利益のために、あなたのキャラクターを構築し、そして(そして私の競争力を維持するために)、これらの強化は読者のための練習として残されています。 クリーンアップに移りましょう。

バイナリデータ

ソケットを介してバイナリデータを送信することは完全に可能です。 主な問題は、すべてのマシンがバイナリデータに同じ形式を使用しているわけではないことです。 たとえば、Motorolaチップは、値1が2つの16進バイト0001として16ビット整数を表します。 ただし、IntelとDECはバイト反転されており、同じ1は0100です。 ソケットライブラリには、16ビットと32ビットの整数を変換するための呼び出しがあります-ntohl, htonl, ntohs, htonsここで、「n」はネットワークを意味し、「h」はホストを意味し、「s」はを意味します短いおよび「l」は長いを意味します。 ネットワークの順序がホストの順序である場合、これらは何もしませんが、マシンがバイト反転されている場合、これらはバイトを適切に交換します。

最近の32ビットマシンでは、バイナリデータのASCII表現はバイナリ表現よりも小さいことがよくあります。 これは、驚くべき時間の長さで、これらのすべてのlongの値が0、またはおそらく1であるためです。 文字列「0」は2バイト、バイナリは4バイトになります。 もちろん、これは固定長のメッセージにはうまく適合しません。 決定、決定。


切断

厳密に言えば、closeを使用する前に、ソケットでshutdownを使用することになっています。 shutdownは、もう一方の端にあるソケットのアドバイスです。 あなたがそれを渡す議論に応じて、それは「私はもう送るつもりはないが、私はまだ聞く」または「私は聞いていない、いい馬鹿げている!」を意味することができます。 ただし、ほとんどのソケットライブラリは、このエチケットの使用を怠っているプログラマーに非常に慣れているため、通常はcloseshutdown(); close()と同じです。 したがって、ほとんどの場合、明示的なshutdownは必要ありません。

shutdownを効果的に使用する1つの方法は、HTTPのような交換です。 クライアントはリクエストを送信してからshutdown(1)を実行します。 これにより、サーバーに「このクライアントは送信が完了しましたが、引き続き受信できます」と通知されます。 サーバーは、0バイトの受信によって「EOF」を検出できます。 完全なリクエストがあると見なすことができます。 サーバーは応答を送信します。 sendが正常に完了した場合、実際、クライアントはまだ受信していました。

Pythonは自動シャットダウンをさらに一歩進め、ソケットがガベージコレクションされると、必要に応じて自動的にcloseを実行すると言います。 しかし、これに頼ることは非常に悪い習慣です。 closeを実行せずにソケットが消えた場合、もう一方の端のソケットは、速度が遅いと思って無期限にハングする可能性があります。 完了したら、ソケットをお願いします closeしてください。

ソケットが死ぬとき

おそらく、ブロッキングソケットを使用することの最悪のことは、反対側が(closeを実行せずに)激しくダウンしたときに何が起こるかです。 ソケットがハングする可能性があります。 TCPは信頼性の高いプロトコルであり、接続を断念するまでに長い間待機します。 スレッドを使用している場合、スレッド全体が本質的に死んでいます。 あなたがそれについてできることはあまりありません。 ブロッキング読み取りを実行しているときにロックを保持するなど、馬鹿げたことをしていない限り、スレッドはリソースをあまり消費していません。 スレッドを強制終了しようとしないでください。スレッドがプロセスよりも効率的である理由の1つは、リソースの自動リサイクルに関連するオーバーヘッドを回避することです。 言い換えれば、スレッドを強制終了することができた場合、プロセス全体が台無しになる可能性があります。


ノンブロッキングソケット

上記を理解していれば、ソケットの使用方法について知っておく必要のあることのほとんどをすでに知っています。 ほぼ同じ方法で、同じ呼び出しを引き続き使用します。 それは、あなたがそれを正しく行えば、あなたのアプリはほとんど裏返しになるということだけです。

Pythonでは、socket.setblocking(0)を使用して非ブロッキングにします。 Cでは、より複雑です(1つには、BSDフレーバーO_NONBLOCKと、 [とは完全に異なるほとんど区別できないPOSIXフレーバーO_NDELAYのどちらかを選択する必要があります。 X192X])、しかしそれはまったく同じ考えです。 これは、ソケットを作成した後、使用する前に行います。 (実際、あなたが気が狂っているなら、あなたは前後に切り替えることができます。)

主な機械的な違いは、sendrecvconnectacceptは何もしなくても戻ることができることです。 (もちろん)いくつかの選択肢があります。 リターンコードとエラーコードを確認して、一般的に自分を夢中にさせることができます。 あなたが私を信じていないなら、いつかそれを試してみてください。 アプリは大きくなり、バグが多く、CPUを消費します。 それでは、脳死の解決策をスキップして、正しく実行しましょう。

selectを使用してください。

Cでは、selectのコーディングはかなり複雑です。 Pythonでは簡単ですが、Cバージョンに十分近いので、Pythonでselectを理解していれば、Cではほとんど問題ありません。

ready_to_read, ready_to_write, in_error = \
               select.select(
                  potential_readers,
                  potential_writers,
                  potential_errs,
                  timeout)

selectの3つのリストを渡します。最初のリストには、読みたいと思う可能性のあるすべてのソケットが含まれています。 2番目は書き込みを試みる可能性のあるすべてのソケット、最後の(通常は空のまま)エラーをチェックするソケットです。 ソケットは複数のリストに入る可能性があることに注意してください。 select呼び出しはブロックされていますが、タイムアウトを与えることができます。 これは一般的に賢明なことです。他の方法で行う正当な理由がない限り、タイムアウトを長く(たとえば1分)与えてください。

その見返りに、3つのリストを取得します。 それらには、実際に読み取り可能、書き込み可能、およびエラーのあるソケットが含まれています。 これらの各リストは、渡した対応するリストのサブセット(おそらく空)です。

ソケットが出力読み取り可能リストにある場合、そのソケットのrecvを返すというビジネスに近いことがあります。何か。 書き込み可能なリストについても同じ考えです。 何かを送信できるようになります。 たぶんあなたが望むすべてではないかもしれませんが、何かは何もないよりはましです。 (実際には、適度に正常なソケットは書き込み可能として返されます。これは、アウトバウンドネットワークのバッファースペースが使用可能であることを意味します。)

「サーバー」ソケットがある場合は、それをpotential_readersリストに入れます。 読み取り可能なリストに含まれている場合、acceptは(ほぼ確実に)機能します。 他の誰かへのconnectへの新しいソケットを作成した場合は、それをpotential_writersリストに入れてください。 書き込み可能リストに表示されている場合は、接続されている可能性が十分にあります。

実際、selectは、ソケットをブロックしている場合でも便利です。 これは、ブロックするかどうかを決定する1つの方法です。バッファーに何かがあると、ソケットは読み取り可能として返されます。 ただし、これでも、もう一方の端が完了したのか、それとも他の何かで忙しいのかを判断する問題には役立ちません。

移植性アラート:Unixでは、selectはソケットとファイルの両方で機能します。 Windowsではこれを試さないでください。 Windowsでは、selectはソケットでのみ機能します。 また、Cでは、より高度なソケットオプションの多くがWindowsで異なる方法で実行されることにも注意してください。 実際、Windowsでは、通常、ソケットでスレッド(非常にうまく機能します)を使用します。