BorutMatjasic
BorutMatjasic

Reputation: 55

Python struct as networking data packets (uknown byte sequence)

I am working on a server engine in Python, for my game made in GameMaker Studio 2. I'm currently having some issues with making and sending a packet.

I've successfully managed to establish a connection and send the first packet, but I can't find a solution for sending data in a sequence of which if the first byte in the packed struct is equal to a value, then unpack other data into a given sequence.

Example:

types = 'hhh' #(message_id, x, y) example
message_id = 0
x = 200
y = 200
buffer = pack(types, 0,x, y)

On the server side:

 data = conn.recv(BUFFER_SIZE)
    mid = unpack('h', data)[0]
    if not data: break

    if mid == 0:
        sequnce = 'hhh'
        x = unpack(sequnce, data)[1]
        y = unpack(sequnce, data)[2]

Upvotes: 1

Views: 659

Answers (2)

Gil Hamilton
Gil Hamilton

Reputation: 12347

It looks like your subsequent decoding is going to vary based on the message ID?

If so, you will likely want to use unpack_from which allows you to pull only the first member from the data (as written now, your initial unpack call will generate an exception because the buffer you're handing it is not the right size). You can then have code that varies the unpacking format string based on the message ID. That code could look something like this:

from struct import pack, unpack, unpack_from

while True:
    data = conn.recv(BUFFER_SIZE)
    # End of file, bail out of loop
    if not data: break

    mid = unpack_from('!h', data)[0]

    if mid == 0:
        # Message ID 0
        types = '!hhh'
        _, x, y = unpack(types, data)
        # Process message type 0
        ...
    elif mid == 1:
        types = '!hIIq'
        _, v, w, z = unpack(types, data)
        # Process message type 1
        ...
    elif mid == 2:
        ...

Note that we're unpacking the message ID again in each case along with the ID-specific parameters. You could avoid that if you like by using the optional offset argument to unpack_from:

x, y = unpack_from('!hh', data, offset=2)

One other note of explanation: If you are sending messages between two different machines, you should consider the "endianness" (byte ordering). Not all machines are "little-endian" like x86. Accordingly it's conventional to send integers and other structured numerics in a certain defined byte order - traditionally that has been "network byte order" (which is big-endian) but either is okay as long as you're consistent. You can easily do that by prepending each format string with '!' or '<' as shown above (you'll need to do that for every format string on both sides).

Finally, the above code probably works fine for a simple "toy" application but as your program increases in scope and complexity, you should be aware that there is no guarantee that your single recv call actually receives all the bytes that were sent and no other bytes (such as bytes from a subsequently sent buffer). In other words, it's often necessary to add a buffering layer, or otherwise ensure that you have received and are operating on exactly the number of bytes you intended.

Upvotes: 3

kosist
kosist

Reputation: 3057

Could you unpack whole data to list, and then check its elements in the loop? What is the reason to unpack it 3 times? I guess, you could unpack it once, and then work with that list - check its length first, if not empty -> check first element -> if equal to special one, continue on list parsing. Did you try like that?

Upvotes: 0

Related Questions