Twisted is explicit. If you kill a script that uses your protocol, it might die before data transmission finishes, unless you handle it yourself.
Transports provide the loseConnection method that flushes the data to
the wire and then disconnects. All we have to do is wait for it to finish.

Predictably, it finishes asyncronously. Our Protocol subclass can be notified though, by
implementing the connectionLost method. The plan: provide a disconnect method that calls transport.loseConnection and returns a Deferred that we errback on connectionLost.

class MyProtocol(Protocol):

    def __init__(self):
        self.disconnecting = None

    def connectionLost(self, reason):
        if self.disconnecting is not None:
            self.disconnecting.errback(reason)

    def disconnect(self):
        if self.disconnecting is None:
            self.disconnecting = Deferred()
            self.transport.loseConnection()
        return self.disconnecting

We can then ensure the client code will "wait" until the deferred errbacks
before continuing

@inlineCallbacks
def use_my_protocol():
    try:
        client = ClientCreator(reactor, MyProtocol)
        protocol = yield client.connectTCP("127.0.0.1", 8080)
        protocol.transport.write(r"foo") # here we would do some work
        yield protocol.disconnect()
    finally:
        returnValue(True)

In case we want to handle a signal and gracefully kill our program,
after all pending data is transmitted, we can register
a callback with the reactor using addSystemEventTrigger. For example

    reactor.addSystemEventTrigger("before", "shutdown", protocol.disconnect)
    reactor.callWhenRunning(my_main_function)
    reactor.run()