web-dev-qa-db-ja.com

pythonで接続が停止しているかどうかを確認する方法

pythonアプリケーションに、反対側のソケットがいつドロップされたかを通知できるようにしたいのですが、この方法はありますか?

57
directedition

それは、「ドロップされた」という意味に依存します。 TCPソケットの場合、もう一方の端がclose()またはプロセスの終了によって接続を閉じた場合、ファイルの終わりを読み取るか、通常は読み取りエラーを取得することにより、 errnoは、オペレーティングシステムによる「ピアによる接続リセット」に設定されます。Pythonの場合、長さゼロの文字列を読み取るか、ソケットからの読み取りまたは書き込みを試みるとsocket.errorがスローされます。

36
Andy V

短い答え:

非ブロッキングrecv()、または非常に短いタイムアウトでブロッキングrecv()/ select()を使用します。

長い答え:

ソケット接続を処理する方法は、必要に応じて読み取りまたは書き込みを行い、接続エラーを処理する準備をすることです。

TCPは、接続の「ドロップ」の3つの形式、タイムアウト、リセット、クローズを区別します。

これらのうち、タイムアウトを実際に検出することはできません。TCPはまだ時間が切れていないことを通知するだけかもしれません。しかし、たとえそれがあなたに言ったとしても、時間がまだ切れる可能性があります。

また、shutdown()を使用すると、あなたまたはあなたのピア(接続のもう一方の端)が着信バイトストリームのみを閉じ、発信バイトストリームを実行し続けるか、発信ストリームを閉じて着信ストリームを実行し続けることを覚えておいてください。

厳密に言えば、読み取りストリームが閉じているか、書き込みストリームが閉じているか、または両方が閉じているかを確認する必要があります。

接続が「ドロップ」された場合でも、まだネットワークバッファーにあるデータを読み取ることができるはずです。バッファーが空になった後にのみ、recv()から切断されます。

接続が切断されたかどうかを確認することは、「現在バッファリングされているすべてのデータを読み取った後に何を受け取るのですか?」それを見つけるには、現在バッファリングされているすべてのデータを読み取るだけです。

Recv()をブロッキング関数と考えている人にとっては、「すべてのバッファリングされたデータの読み取り」が最後まで到達するのがどのように問題になるかがわかります。ブロッキングrecv()を使用すると、バッファーが既に空のときに読み取りを「チェック」するとブロックされ、「チェック」の目的が無効になります。

私の意見では、プロセス全体を無期限にブロックする可能性があると文書化されている機能は設計上の欠陥ですが、歴史的な理由で、通常のファイル記述子のようにソケットを使用することはクールなアイデアだったため、まだそこにあると思います。

できることは:

  • ソケットを非ブロックモードに設定しますが、受信バッファが空であるか、送信バッファがいっぱいであることを示すシステム依存のエラーが表示される場合があります
  • ブロックモードに固執しますが、非常に短いソケットタイムアウトを設定します。これにより、recv()でソケットを「ping」または「チェック」できるようになります。
  • select()呼び出しまたは非常に短いタイムアウトで非同期モジュールを使用します。エラー報告はまだシステム固有です。

問題の書き込み部分については、読み取りバッファを空のままにしておくことでほぼカバーできます。非ブロック読み取りの試行後に接続が「ドロップ」されたことがわかります。読み取りが閉じたチャネルを返した後、送信を停止することもできます。

送信されたデータがもう一方の端に到達している(そしてまだ送信バッファーにない)ことを確認する唯一の方法は、次のいずれかです。

  • 送信した正確なメッセージに対して、同じソケットで適切な応答を受け取ります。基本的には、より高いレベルのプロトコルを使用して確認を提供しています。
  • ソケットでshutdow()およびclose()を正常に実行します

python socket howtoは、チャネルが閉じられている場合、send()は0バイトを返します。ノンブロッキングまたはタイムアウトsocket.send()を使用できます。そのソケットでデータを送信する時間が長くなりますが、ゼロ以外の値が返される場合は、すでに何かを送信しています。

また、ここではOOB(アウトオブバンド)ソケットデータを問題に対処する手段として考えていませんが、OOBは意図したものではなかったと思います。

44
Toughy

Jweedeが投稿したリンクから:

例外socket.timeout:

This exception is raised when a timeout occurs on a socket
which has had timeouts enabled via a prior call to settimeout().
The accompanying value is a string whose value is currently
always “timed out”.

python docs のソケットモジュール用のデモサーバーとクライアントプログラムを次に示します。

# Echo server program
import socket

Host = ''                 # Symbolic name meaning all available interfaces
PORT = 50007              # Arbitrary non-privileged port
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((Host, PORT))
s.listen(1)
conn, addr = s.accept()
print 'Connected by', addr
while 1:
    data = conn.recv(1024)
    if not data: break
    conn.send(data)
conn.close()

そしてクライアント:

# Echo client program
import socket

Host = 'daring.cwi.nl'    # The remote Host
PORT = 50007              # The same port as used by the server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((Host, PORT))
s.send('Hello, world')
data = s.recv(1024)
s.close()
print 'Received', repr(data)

これらを引き出したドキュメントのサンプルページには、このアイデアを採用したより複雑な例がありますが、簡単な答えを次に示します。

あなたがクライアントプログラムを書いていると仮定すると、ドロップされる危険がある場合、tryブロック内にソケットを使用するすべてのコードを入れてください...

try:
    s.connect((Host, PORT))
    s.send("Hello, World!")
    ...
except socket.timeout:
    # whatever you need to do when the connection is dropped
14
Jiaaro

このブログ投稿のコードサンプルをPythonに翻訳しました: クライアントが接続を閉じたときの検出方法

from ctypes import (
    CDLL, c_int, POINTER, Structure, c_void_p, c_size_t,
    c_short, c_ssize_t, c_char, ARRAY
)


__all__ = 'is_remote_alive',


class pollfd(Structure):
    _fields_ = (
        ('fd', c_int),
        ('events', c_short),
        ('revents', c_short),
    )


MSG_DONTWAIT = 0x40
MSG_PEEK = 0x02

EPOLLIN = 0x001
EPOLLPRI = 0x002
EPOLLRDNORM = 0x040

libc = CDLL(None)

recv = libc.recv
recv.restype = c_ssize_t
recv.argtypes = c_int, c_void_p, c_size_t, c_int

poll = libc.poll
poll.restype = c_int
poll.argtypes = POINTER(pollfd), c_int, c_int


class IsRemoteAlive:  # not needed, only for debugging
    def __init__(self, alive, msg):
        self.alive = alive
        self.msg = msg

    def __str__(self):
        return self.msg

    def __repr__(self):
        return 'IsRemoteClosed(%r,%r)' % (self.alive, self.msg)

    def __bool__(self):
        return self.alive


def is_remote_alive(fd):
    fileno = getattr(fd, 'fileno', None)
    if fileno is not None:
        if hasattr(fileno, '__call__'):
            fd = fileno()
        else:
            fd = fileno

    p = pollfd(fd=fd, events=EPOLLIN|EPOLLPRI|EPOLLRDNORM, revents=0)
    result = poll(p, 1, 0)
    if not result:
        return IsRemoteAlive(True, 'empty')

    buf = ARRAY(c_char, 1)()
    result = recv(fd, buf, len(buf), MSG_DONTWAIT|MSG_PEEK)
    if result > 0:
        return IsRemoteAlive(True, 'readable')
    Elif result == 0:
        return IsRemoteAlive(False, 'closed')
    else:
        return IsRemoteAlive(False, 'errored')
5
kay

誤解しない限り、これは通常 timeout で処理されます。

4
Jon W