Sean Francis N. Ballais
Sean Francis N. Ballais

Reputation: 2488

In Python socket programming, why does recv()-ing data directly from the sockets only gives you the first message?

I'm playing around with socket programming in Python 2 and trying out select() for the server script. When I have the following code for the server:

print('Server started in port {}.'.format(self.port))
server_socket = socket.socket()
server_socket.bind((self.address, self.port))
server_socket.listen(5)

client_sockets = [server_socket]
while True:
    for s in client_sockets:
        if s is server_socket:
            client_socket, address = server_socket.accept()
            client_sockets.append(client_socket)

            print('Connection received.')
        else:
            data = s.recv(200)
            if data:
                print('Received: {}'.format(data.decode().strip()))
            else:
                client_sockets.remove(s)
                s.close()

The server only receives the first message from the client. However, the second and later messages will only be received when the client is restarted. This baffles me (of which I attribute to my inadequate knowledge in networking). The data seems to be buffered. Why does this happen?

I did try this:

client_sockets = [server_socket]
while True:
    readable, writable, errored = select.select(client_sockets, [], [])
    for s in readable:
        if s is server_socket:
...

And finally, the server can now receive the second and later messages from the client.


Here is the code for the client:

class BasicClient(object):

    def __init__(self, name, address, port):
        self.name = name
        self.address = address
        self.port = int(port)
        self.socket = socket.socket()

    def connect(self):
        self.socket.connect((self.address, self.port))
        self.socket.send(self.name)

    def send_message(self, message):
        self.socket.send(message.encode())


args = sys.argv
if len(args) != 4:
    print "Please supply a name, server address, and port."
    sys.exit()

client = BasicClient(args[1], args[2], args[3])
client.connect()
while True:
    message = raw_input('Message: ')

    # We pad the message by 200 since we only expect messages to be
    # 200 characters long.
    num_space_pads = min(200 - len(message), 200)
    message = message.ljust(num_space_pads, ' ')

    client.send_message(message)

Upvotes: 0

Views: 391

Answers (1)

Hannu
Hannu

Reputation: 12205

There are many problems here.

The key problem is that socket.accept() is a blocking call. You accept your client connection, then read from it, but then your for loop processes back to the server socket and your code is stuck in accept. Only when you reconnect to the socket does the server code move forward, hence it appears you only receive one message.

The better way to do this is to use threads. In your main thread wait for connections (always waiting in accept). When a connection appears, accept it then spawn a thread and handle all client traffic there. The thread then blocks waiting only for the client socket and eventually terminates on connection closed. Much simpler and less prone to errors.

There is also the problem of the concept of "message", as TCP sockets do not know anything about your message structure. They just transmit a stream of data, whatever that is. When you send a "message" and read from a TCP socket, one of the following happens on the server side:

  1. There is no data and the read call blocks
  2. There is exactly one message and you receive that
  3. The server was slower than your client, there are several messages and you receive them all on one go
  4. The server jumped the gun and decided to read when only a partial message was available
  5. Combination of 3 and 4: You receive several full messages and one partial

You need to always cater for cases 3-5 (or use zeromq or something else that understands the concept of a message if you do not want to implement this yourself), not only 1 and 2.

When reading data from a TCP socket, it is always necessary to validate it. Have a "process" buffer and append what you read to that. Then start parsing from the beginning and process as many complete messages as you can find, delete these from the buffer and leave the remainder there. The assumption then is the partial message will eventually be completed.

Upvotes: 2

Related Questions