How to guarantee tcp data sent using python asyncio?
Question: I have a client that connect to server and send all messages of the list, and
each message sent, it is deleted from the list.

But when I force to close the server, client still continue sending and
deleting messages from the list. I would like that if connection is down,
client stop to send messages, or don't delete from list if has no guarantee
that server has received the message.

I check that when I sent more than four messages after connection is down,
show the error "socket.send() raised exception". But I don't know how to get
that error, I think it is asynchronous. Anyway, if I get that error, the error
don't happen if the list has less than five messages to sent after connection
down.

Ps: I wrote the server just for my tests, but the server I will have no
access. So, the client that need to do everything to have the guarantee of
data sent.

Thank you so much.

# client.py

    
    
    import asyncio
    
    class Client(asyncio.Protocol):
        TIMEOUT = 1.0
        event_list = []
        for i in range(10):
            event_list.append('msg' + str(i))
    
        def __init__(self):
            self.client_tcp_timeout = None
            print(self.event_list)
    
        def connection_made(self, transport):
            print('Connected to Server.')
            self.transport = transport
            self.client_tcp_timeout = loop.call_later(self.TIMEOUT, self.send_from_call_later)
    
        def data_received(self, data):
            self.data = format(data.decode())
            print('data received: {}'.format(data.decode()))
    
        def send_from_call_later(self):
            self.msg = self.event_list[0].encode()
            self.transport.write(self.msg)
            print('data sent: {}'.format(self.msg))
            print('Removing data: {}'.format(self.event_list[0]))
            del self.event_list[0]
            print(self.event_list)
            print('-----------------------------------------')
            if len(self.event_list) > 0:
                self.client_tcp_timeout = loop.call_later(self.TIMEOUT, self.send_from_call_later)
            else:
                print('All list was sent to the server.')
    
        def connection_lost(self, exc):
            print('Connection lost!!!!!!.')
    
    loop = asyncio.get_event_loop()
    
    coro = loop.create_connection(Client, 'localhost', 8000)
    client = loop.run_until_complete(coro)
    
    loop.run_forever()
    

# server.py

    
    
    import asyncio
    
    class Server(asyncio.Protocol):
        def connection_made(self, transport):
            peername = transport.get_extra_info('peername')
            print('connection from {}'.format(peername))
            self.transport = transport
    
        def data_received(self, data):
            print('data received: {}'.format(data.decode()))
            #self.transport.write(data)
    
    loop = asyncio.get_event_loop()
    coro = loop.create_server(Server, 'localhost', 8000)
    server = loop.run_until_complete(coro)
    
    print('serving on {}'.format(server.sockets[0].getsockname()))
    
    try:
        loop.run_forever()
    except KeyboardInterrupt:
        print("exit")
    finally:
        server.close()
        loop.close()
    

# server output (force (CTRL+C) to close server after received msg4):

    
    
    $ python3 server.py
    serving on ('127.0.0.1', 8000)
    connection from ('127.0.0.1', 56119)
    data received: msg0
    data received: msg1
    data received: msg2
    data received: msg3
    data received: msg4
    ^Cexit
    

# client output

    
    
    $ python3 client.py 
    ['msg0', 'msg1', 'msg2', 'msg3', 'msg4', 'msg5', 'msg6', 'msg7', 'msg8', 'msg9']
    Connected to Server.
    data sent: b'msg0'
    Removing data: msg0
    ['msg1', 'msg2', 'msg3', 'msg4', 'msg5', 'msg6', 'msg7', 'msg8', 'msg9']
    -----------------------------------------
    data sent: b'msg1'
    Removing data: msg1
    ['msg2', 'msg3', 'msg4', 'msg5', 'msg6', 'msg7', 'msg8', 'msg9']
    -----------------------------------------
    data sent: b'msg2'
    Removing data: msg2
    ['msg3', 'msg4', 'msg5', 'msg6', 'msg7', 'msg8', 'msg9']
    -----------------------------------------
    data sent: b'msg3'
    Removing data: msg3
    ['msg4', 'msg5', 'msg6', 'msg7', 'msg8', 'msg9']
    -----------------------------------------
    data sent: b'msg4'
    Removing data: msg4
    ['msg5', 'msg6', 'msg7', 'msg8', 'msg9']
    -----------------------------------------
    Connection lost!!!!!!.
    data sent: b'msg5'
    Removing data: msg5
    ['msg6', 'msg7', 'msg8', 'msg9']
    -----------------------------------------
    data sent: b'msg6'
    Removing data: msg6
    ['msg7', 'msg8', 'msg9']
    -----------------------------------------
    data sent: b'msg7'
    Removing data: msg7
    ['msg8', 'msg9']
    -----------------------------------------
    data sent: b'msg8'
    Removing data: msg8
    ['msg9']
    -----------------------------------------
    socket.send() raised exception.
    data sent: b'msg9'
    Removing data: msg9
    []
    -----------------------------------------
    All list was sent to the server.
    


Answer: A write to a TCP socket does not guarantee that the data get received. It only
sends the data to the OS kernel which then will try as hard as possible to
send the data to the other side. But, the write call will already return
success once the data are sent to the OS kernel. If the data then get received
by the peers OS kernel it will acknowledge them at the TCP level. But, that
only means that the data are received by the kernel and not that they are
processed by the application.

If you want to have guaranteed message delivery and deal with possible close
of the peer you have to implement some kind of acknowledgements inside your
protocol and only remove the data once you get an explicit acknowledgment from
your peer application after it successfully processed the data.

