Startec Startec - 1 month ago 9
Python Question

How to only read data from a socket when neccesary

I am communicating with a piece of hardware that has only a rudimentary API that supports basic API calls.

Unfortunately some of the API calls illicit a response, and some do not. I want to read the response when there is one, or just do nothing when there is not.

Here is the code I have been using:

def send_message(self, message, host):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((host, self.PORT))
s.send(bytes(message, 'ascii'))
data = s.recv(1024)
print(data)


Unfortunately this binds the entire process when there is not data received. I only want to read data when there is an actual response. Is there a way to do this with the Python standard library?

Answer

Firstly, if you have control over the target API, I would suggest implementing a reply for every message. A reply ACK (acknowledge) for every message sent would make your API more robust, and sidestep this issue entirely.

If that's not an option, then I would create classes for the different types of messages:

class Message(object):
    def __init__(self, msg):
        self.msg = msg

class MessageWReply(Message):
    await_reply = True

class MessageNoReply(Message):
    await_reply = False

Create all of your message classes, then use them like this:

def send_message(self, message, host):
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.connect((host, self.PORT))
        s.send(bytes(message, 'ascii'))

        if message.await_reply:
            data = s.recv(1024)
            print(data)

Alternatively, if you don't want the overhead of creating all of the classes, you could use a dictionary to map the messages to the correct action:

messages = {
    'message with reply 1': True, # wait for reply
    'message with reply 2': True,
    'message with reply 2': True,
    'message without reply 1': False, # don't wait for reply
    'message without reply 2': False
}

Then do this:

def send_message(self, message, host):
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.connect((host, self.PORT))
        s.send(bytes(message, 'ascii'))

        if messages[message]:
            data = s.recv(1024)
            print(data)

The second method has cleaner initialization (less boilerplate code), but is a bit less clear. For someone not familiar with the code, it's not clear what the value stored at messages[message] actually means, while message.await_reply is crystal clear. Something like named tuples may be a good compromise - you can initialize everything in one data structure while still using named fields for clarity.