mr.zhelez
mr.zhelez

Reputation: 11

How to split received data from socket in Python?

I'm trying to code my first client-server application in Python. My client sends different commands to the server side. For instance, one of them is for executing commands in command shell. It looks like:

client.send("execute".encode("utf-8"))
client.send(command.encode("utf-8"))

On the server side I'm trying to receive it this way:

data = client.recv(BUFF_SIZE).decode("utf-8").strip()
if data == "execute":
    command = client.recv(BUFF_SIZE).decode("utf-8").strip()
    ...

But I get "execute{command}" string in 'data' variable, and == "execute" condition is not fulfilled. I'm new to client-server applications and don't know, how to do it properly. What I do wrong?

Upvotes: 0

Views: 7232

Answers (2)

Uri Simchoni
Uri Simchoni

Reputation: 326

The thing to realize is that TCP is a byte stream. The guarantee that you get from TCP is that the bytes you send will arrive at the same order. If the byte stream represent a sequence of commands, you get no guarantee that the bytes will arrive in chunks that are aligned with stuff that you sent. You could be sending: "execute", "command" And receiving "e" "xecuteco" "mmand".

(yes, that's extremely unlikely, whereas receiving "executecommand" is extremely likely, because of the nagle algorithm, but I digress. The point is that in order to write robust code you are not to assume anything abut how TCP breaks your data into pieces)

Therefore the first thing you need to decide on is how the request byte stream is partitioned into requests, and how the response byte stream is partitioned into responses. Inside the request you need to decide on its internal structure.

Let's assume you decide that the request looks like: "verb param1 param2...paramN\n" That is:

  1. A request is a sequence of non-newline bytes, followed by a newline
  2. The request is comprised of an initial verb (non-space characters) followed by zero or more parameters

Since the protocol itself now has an additional layer over TCP, it's best to code this using an abstraction. Something akin to:

class Request(object):
    def __init__(self, verb, *args):
        self.verb = verb
        self.args = [str(x) for x in args]

class Client(object):
    def __init__(self, sock):
        self.sock = sock
        self.rxbuf = ''

    def send_request(self, req):
        req_str = req.verb
        if req.args:
            req_str += ' ' + ' '.join(req.args)
        req_str += '\n'
        self.sock.sendall(req_str.encode("utf-8"))

class Server(object):
    def __init__(self, sock):
        self.sock = sock
        self.rxbuf = ''
    def read_request(self):
        while True:
            s = self.rxbuf.split('\n', 1)
            if len(s) == 2:
                req_str = s[0]
                self.rxbuf = s[1]
                req_lst = req_str.split(' ')
                return Request(req_lst[0], *req_lst[1:])
            data = self.sock.recv(BUF_SIZE).decode("utf-8")
            self.rxbuf += data

Of course this has to be complemented by a decision of how the responses will look like and how to delineate the incoming byte stream into a sequence of responses. The main point I was trying to make with this code is that

  1. You read bytes
  2. You accumulate them
  3. Try to see if the stuff you've got so far is a full request
  4. if yes - analyze that and leave the rest for next time

That assumes the requests are reasonably small and you don't have to stream them, which is a more advanced topic.

Upvotes: 5

AllenMoh
AllenMoh

Reputation: 476

you could fix this by changing the if statement on the server code to:

if data.split("{")[0] == "execute"

explanation:

data = "execute{command}"
print data.split("{")[0]

prints

"execute"

Since data is a string, you can use the split method to split the string into a list using the "{" character.

so:

data.split("{")

would return

["execute", "commmand}"]

and since you want "execute" you take index 0 in the list

Upvotes: 0

Related Questions