Define writable method in asyncore client makes sending data very slow

1.3k Views Asked by At

I wrote a asynchorous client using python asyncore, and met some problems. I have solved this with the help of this:

Asyncore client in thread makes the whole program crash when sending data immediately

But now I meet some other problem.

My client program:

import asyncore, threading, socket
class Client(threading.Thread, asyncore.dispatcher):
    def __init__(self, host, port):
        threading.Thread.__init__(self)
        self.daemon = True
        self._thread_sockets = dict()
        asyncore.dispatcher.__init__(self, map=self._thread_sockets)
        self.host = host
        self.port = port
        self.output_buffer = []
        self.start()

    def send(self, msg):
        self.output_buffer.append(msg)
    def writable(self):
        return len("".join(self.output_buffer)) > 0
    def handle_write(self):
        all_data = "".join(self.output_buffer)
        bytes_sent = self.socket.send(all_data)
        remaining_data = all_data[bytes_sent:]
        self.output_buffer = [remaining_data]
    def handle_close(self):
        self.close()
    def handle_error(self):
        print("error")
    def handle_read(self):
        print(self.recv(10))
    def run(self):
        self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
        self.connect((self.host, self.port))
        asyncore.loop(map = self._thread_sockets)

mysocket = Client("127.0.0.1",8400)
while True:
    a=str(input("input"))
    mysocket.send("popo")

And my server program:

import socket
HOST="127.0.0.1"
PORT=8400
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print("socket created")
s.bind((HOST, PORT))
s.listen(1)
print("listen")
conn,addr = s.accept()
print("Accepted. Receiving")
while True:
    data = conn.recv(20)
    print("Received: ")
    print(data)
    data = input("Please input reply message:\n").encode("utf-8")
    conn.send(data)
    print("Data sended. Receiving")

My problem is sending data from client to server is very slow, about 20 to 30 seconds! But it could always send data successfully. And if I comment out the writable method in client, the sending process becomes very fast. Why does it behave like this? How to fix it if I want to use the writable method? Thanks!

I start the server with python3, and client with python 2. I use ubuntu 14.04.

1

There are 1 best solutions below

0
On

The asyncore loop calls writable() when it is ready to do something with the socket. If the method writable() tells that there is something to write then handle_write() is called. The default writable() always returns True, so in that case there is busy loop calling handle_write() and writable().

In the above implementation the method writable() is called immediately when the client loop is started. At that moment there is nothing in the buffer, so writable() tells that there is nothing to write.

The asyncore loop calls select(). Now the loop is in "standby" state. It can be wakened only when some data is received by the socket or by timeout event. After any of those events the loop again checks writable().

The server sends nothing to the client and the client waits for timeout. The default timeout is 30 seconds, so that is why it is needed to wait up to 30 seconds before something is sent. It is possible to reduce the timeout during starting asyncore.loop():

    asyncore.loop(map = self._thread_sockets, timeout = 0.5)

Another idea that may come here is to check if the buffer is empty in send() and if it is empty send it immediately. However, it is a bad idea. The send() is called in the main thread, but the socket is managed by the asyncore loop in another thread.

For the same reason it makes sense to protect usage of output_buffer for concurrent access from different threads. The lock object threading.Lock() can be used here:

def __init__(self, host, port):
    #...
    self.lock = threading.Lock()

def send(self, msg):
    self.lock.acquire()
    try:
        self.output_buffer.append(msg)
    finally:
        self.lock.release()

def writable(self):
    is_writable = False;
    self.lock.acquire()
    try:
        is_writable = len("".join(self.output_buffer)) > 0
    finally:
        self.lock.release()

    return is_writable

def handle_write(self):
    self.lock.acquire()
    try:
        all_data = "".join(self.output_buffer)
        bytes_sent = self.socket.send(all_data)
        remaining_data = all_data[bytes_sent:]
        self.output_buffer = [remaining_data]
    finally:
        self.lock.release()

There is no thread safe mechanism to waken asyncore from another thread. So, the only solution is to reduce loop timeout, although too small timeout increases CPU usage.