GNU/Linux >> Znalost Linux >  >> Linux

Zjistit zablokování zásuvky bez odesílání nebo přijímání?

Měl jsem opakující se problém s komunikací se zařízením, které mělo samostatné TCP spojení pro odesílání a přijímání. Základní problém je v tom, že zásobník TCP vám obecně neřekne, že je soket uzavřen, když se jen pokoušíte číst – musíte zkusit a napsat, abyste se dozvěděli, že druhý konec spojení byl zrušen. Částečně je to přesně tak, jak byl TCP navržen (čtení je pasivní).

Hádám, že Blairova odpověď funguje v případech, kdy byla zásuvka na druhém konci pěkně vypnuta (tj. odeslali správné zprávy o odpojení), ale ne v případě, kdy druhý konec nezdvořile právě přestal poslouchat.

Je na začátku vaší zprávy hlavička v poměrně pevném formátu, kterou můžete začít odesláním, než bude připravena celá odpověď? např. XML doctype? Dokážete se také zbavit toho, že v některých bodech zprávy pošlete nějaké další mezery - jen některá nulová data, která můžete odeslat, abyste se ujistili, že je zásuvka stále otevřená?


Modul select obsahuje to, co budete potřebovat. Pokud potřebujete pouze podporu Linuxu a máte dostatečně aktuální jádro, select.epoll() by vám měl poskytnout informace, které potřebujete. Většina unixových systémů bude podporovat select.poll() .

Pokud potřebujete podporu napříč platformami, standardním způsobem je použití select.select() zkontrolovat, zda je soket označen jako s dostupnými daty ke čtení. Pokud ano, ale recv() vrátí nula bajtů, druhý konec zavěsil.

Vždy jsem považoval Beej's Guide to Network Programming za dobrý (všimněte si, že je napsán pro C, ale je obecně použitelný pro standardní operace soketů), zatímco Socket Programming How-To má slušný přehled v Pythonu.

Upravit :Následuje příklad toho, jak lze jednoduchý server zapsat do fronty příchozích příkazů, ale ukončit zpracování, jakmile zjistí, že spojení bylo na vzdáleném konci uzavřeno.

import select
import socket
import time

# Create the server.
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.bind((socket.gethostname(), 7557))
serversocket.listen(1)

# Wait for an incoming connection.
clientsocket, address = serversocket.accept()
print 'Connection from', address[0]

# Control variables.
queue = []
cancelled = False

while True:
    # If nothing queued, wait for incoming request.
    if not queue:
        queue.append(clientsocket.recv(1024))

    # Receive data of length zero ==> connection closed.
    if len(queue[0]) == 0:
        break

    # Get the next request and remove the trailing newline.
    request = queue.pop(0)[:-1]
    print 'Starting request', request

    # Main processing loop.
    for i in xrange(15):
        # Do some of the processing.
        time.sleep(1.0)

        # See if the socket is marked as having data ready.
        r, w, e = select.select((clientsocket,), (), (), 0)
        if r:
            data = clientsocket.recv(1024)

            # Length of zero ==> connection closed.
            if len(data) == 0:
                cancelled = True
                break

            # Add this request to the queue.
            queue.append(data)
            print 'Queueing request', data[:-1]

    # Request was cancelled.
    if cancelled:
        print 'Request cancelled.'
        break

    # Done with this request.
    print 'Request finished.'

# If we got here, the connection was closed.
print 'Connection closed.'
serversocket.close()

Chcete-li jej použít, spusťte skript a v jiném terminálu telnet na localhost, port 7557. Výstup z příkladu jsem spustil, přičemž jsem zařadil tři požadavky do fronty, ale ukončil spojení během zpracování třetího:

Connection from 127.0.0.1
Starting request 1
Queueing request 2
Queueing request 3
Request finished.
Starting request 2
Request finished.
Starting request 3
Request cancelled.
Connection closed.

alternativa epoll

Další úprava: Vypracoval jsem další příklad pomocí select.epoll sledovat události. Nemyslím si, že nabízí mnoho oproti původnímu příkladu, protože nevidím způsob, jak přijmout událost, když vzdálený konec zavěsí. Stále musíte monitorovat událost přijatá data a kontrolovat zprávy s nulovou délkou (opět bych rád, kdyby se ukázalo, že se v tomto tvrzení mýlím).

import select
import socket
import time

port = 7557

# Create the server.
serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.bind((socket.gethostname(), port))
serversocket.listen(1)
serverfd = serversocket.fileno()
print "Listening on", socket.gethostname(), "port", port

# Make the socket non-blocking.
serversocket.setblocking(0)

# Initialise the list of clients.
clients = {}

# Create an epoll object and register our interest in read events on the server
# socket.
ep = select.epoll()
ep.register(serverfd, select.EPOLLIN)

while True:
    # Check for events.
    events = ep.poll(0)
    for fd, event in events:
        # New connection to server.
        if fd == serverfd and event & select.EPOLLIN:
            # Accept the connection.
            connection, address = serversocket.accept()
            connection.setblocking(0)

            # We want input notifications.
            ep.register(connection.fileno(), select.EPOLLIN)

            # Store some information about this client.
            clients[connection.fileno()] = {
                'delay': 0.0,
                'input': "",
                'response': "",
                'connection': connection,
                'address': address,
            }

            # Done.
            print "Accepted connection from", address

        # A socket was closed on our end.
        elif event & select.EPOLLHUP:
            print "Closed connection to", clients[fd]['address']
            ep.unregister(fd)
            del clients[fd]

        # Error on a connection.
        elif event & select.EPOLLERR:
            print "Error on connection to", clients[fd]['address']
            ep.modify(fd, 0)
            clients[fd]['connection'].shutdown(socket.SHUT_RDWR)

        # Incoming data.
        elif event & select.EPOLLIN:
            print "Incoming data from", clients[fd]['address']
            data = clients[fd]['connection'].recv(1024)

            # Zero length = remote closure.
            if not data:
                print "Remote close on ", clients[fd]['address']
                ep.modify(fd, 0)
                clients[fd]['connection'].shutdown(socket.SHUT_RDWR)

            # Store the input.
            else:
                print data
                clients[fd]['input'] += data

        # Run when the client is ready to accept some output. The processing
        # loop registers for this event when the response is complete.
        elif event & select.EPOLLOUT:
            print "Sending output to", clients[fd]['address']

            # Write as much as we can.
            written = clients[fd]['connection'].send(clients[fd]['response'])

            # Delete what we have already written from the complete response.
            clients[fd]['response'] = clients[fd]['response'][written:]

            # When all the the response is written, shut the connection.
            if not clients[fd]['response']:
                ep.modify(fd, 0)
                clients[fd]['connection'].shutdown(socket.SHUT_RDWR)

    # Processing loop.
    for client in clients.keys():
        clients[client]['delay'] += 0.1

        # When the 'processing' has finished.
        if clients[client]['delay'] >= 15.0:
            # Reverse the input to form the response.
            clients[client]['response'] = clients[client]['input'][::-1]

            # Register for the ready-to-send event. The network loop uses this
            # as the signal to send the response.
            ep.modify(client, select.EPOLLOUT)

        # Processing delay.
        time.sleep(0.1)

Poznámka :Toto detekuje pouze správná vypnutí. Pokud vzdálený konec prostě přestane poslouchat bez odeslání správných zpráv, nebudete to vědět, dokud nezkusíte napsat a dostanete chybu. Kontrola je ponechána jako cvičení pro čtenáře. Pravděpodobně také budete chtít provést nějakou kontrolu chyb v celkové smyčce, aby se samotný server řádně vypnul, pokud se uvnitř něj něco porouchá.


Volba KEEPALIVE soketu umožňuje detekovat tento druh scénářů „přerušení připojení, aniž byste řekli druhému konci“.

Měli byste nastavit možnost SO_KEEPALIVE na úrovni SOL_SOCKET. V Linuxu můžete upravit časové limity na soket pomocí TCP_KEEPIDLE (sekundy před odesláním sond pro udržování života), TCP_KEEPCNT (neúspěšné sondy pro udržování života před prohlášením druhého konce za mrtvý) a TCP_KEEPINTVL (interval v sekundách mezi sondami pro udržování života).

V Pythonu:

import socket
...
s.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
s.setsockopt(socket.SOL_TCP, socket.TCP_KEEPIDLE, 1)
s.setsockopt(socket.SOL_TCP, socket.TCP_KEEPINTVL, 1)
s.setsockopt(socket.SOL_TCP, socket.TCP_KEEPCNT, 5)

netstat -tanop ukáže, že soket je v režimu udržování života:

tcp        0      0 127.0.0.1:6666          127.0.0.1:43746         ESTABLISHED 15242/python2.6     keepalive (0.76/0/0)

zatímco tcpdump zobrazí sondy pro udržení naživu:

01:07:08.143052 IP localhost.6666 > localhost.43746: . ack 1 win 2048 <nop,nop,timestamp 848683438 848683188>
01:07:08.143084 IP localhost.43746 > localhost.6666: . ack 1 win 2050 <nop,nop,timestamp 848683438 848682438>
01:07:09.143050 IP localhost.6666 > localhost.43746: . ack 1 win 2048 <nop,nop,timestamp 848683688 848683438>
01:07:09.143083 IP localhost.43746 > localhost.6666: . ack 1 win 2050 <nop,nop,timestamp 848683688 848682438>

Linux
  1. Pwd bez symbolických odkazů?

  2. Tmux Socket Api?

  3. Detekce Process Eating CPU bez:Top, Htop, Ps?

  1. Jak na to:Programování soketů v Pythonu

  2. Detekce v C při výstupu na terminál

  3. cp -r bez skrytých souborů

  1. Vytváření raw socketu v Pythonu bez práv roota

  2. Ansible:sudo bez hesla

  3. Jak okamžitě vypnout počítač (1 až 5 sekund) bez použití fyzického vypínače/zásuvky?