Joffo
Joffo

Reputation: 175

Why my client socket died after first send?

I am trying simple script client socket. My code is following:

import pickle
import socket

pole = ["mail","firewall","fria"]

c=False
while c==False:
   try:
     client = socket.socket ( socket.AF_INET, socket.SOCK_STREAM )
     client.connect ( ( 'localhost', 1234 ) )
     print("established")
     c=True
   except:
     print(".")

i = (len(pole)-1)
while i>=0:
   client.send(pickle.dumps(pole[i]))
   pole.remove(pole[i])
   i-=1

client.close()

On the other side is "forever" server. But server receive only one piece of data. Why no all of field? Client "while" loop should run 3times, so it should send all of them (["mail","firewall","fria"]). Here is server output:

enter image description here

And then client is ended. Why? Client should end after sending all data. Why is after one sending connection closed?

Thanks for help

Edited ---> server.py:

import pickle
import socket

updated_data = []        
server = socket.socket ( socket.AF_INET, socket.SOCK_STREAM )
server.bind ( ( '', 1234 ) )
server.listen ( 5 )

while 1:
   channel, details = server.accept()
   print ('Received connection:', details [ 0 ])
   updated_data.append(pickle.loads(channel.recv(1024)))
   print (updated_data)

channel.close()

Upvotes: 2

Views: 1418

Answers (3)

vaultah
vaultah

Reputation: 46603

I've just tested your code and it revealed that all the messages you send() from loop is being received by server as a single one and pickle loads only first of them. Basically, the received message is

b'\x80\x03X\x04\x00\x00\x00friaq\x00.\x80\x03X\x08\x00\x00\x00firewallq\x00.\x80\x03X\x04\x00\x00\x00mailq\x00.'

As you can see, all of the pole elements are in this string - that means that client sends all data before the connection closes and the server receives all data from the client.

The solution is to frame (to set up messages' boundaries) the messages. This problem is relatively widely known (Google search results for tcp message boundary and tcp message framing).

I find this blog post very helpful. It says:

There are two approaches commonly used for message framing: length prefixing and delimiters.

Length prefixing prepends each message with the length of that message. The format (and length) of the length prefix must be explicitly stated; "4-byte signed little-endian" (i.e., "int" in C#) is a common choice. To send a message, the sending side first converts the message to a byte array and then sends the length of the byte array followed by the byte array itself.

Receiving a length-prefixed message is harder, because of the possibility of partial receives. First, one must read the length of the message into a buffer until the buffer is full (e.g., if using "4-byte signed little-endian", this buffer is 4 bytes). Then one allocates a second buffer and reads the data into that buffer. When the second buffer is full, then a single message has arrived, and one goes back to reading the length of the next message.

Delimiters are more complex to get right. When sending, any delimiter characters in the data must be replaced, usually with an escaping function. The receiving code cannot predict the incoming message size, so it must append all received data onto the end of a receiving buffer, growing the buffer as necessary. When a delimiter is found, the receiving side can apply an unescaping function to the receiving buffer to get the message. If the messages will never contain delimiters, then one may skip the escaping/unescaping functions.

In your particular case I'd go with Python's list (will act as "auto-delimited container"), serialized with pickle and therefore I'll have to change your code a bit.

Client

import pickle
import socket

pole = ["mail","firewall","fria"]

c = False
while not c:
    try:
        client = socket.socket ( socket.AF_INET, socket.SOCK_STREAM )
        client.connect ( ( 'localhost', 1234 ) )
        print("established")
        c = True
    except:
        print(".")

# - i = (len(pole)-1)
# - import time
# - while i>=0:
# -     client.send(pickle.dumps(pole[i]))
# -     pole.remove(pole[i])
# -     i-=1

# +
client.send(pickle.dumps(pole))

client.close()

Server

import pickle
import socket

updated_data = []    
server = socket.socket ( socket.AF_INET, socket.SOCK_STREAM )
server.bind ( ( '', 1234 ) )
server.listen ( 5 )

while 1:
    channel, details = server.accept()
    print ('Received connection:', details[0])
    data = channel.recv(1024)
    if data:
        # Deserialize
        data = pickle.loads(data)
        # We're able to extend the list with another list:
        # [1, 2, 3].extend([4, 5, 6]) is [1, 2, 3, 4, 5, 6]
        updated_data.extend(data)
        print(updated_data)

channel.close()

Now the code works and the server prints

Received connection: 127.0.0.1
['mail', 'firewall', 'fria']

and updated_data grow as expected.

Upvotes: 4

user3286261
user3286261

Reputation: 501

I think all three dumps get sent the first time and only one is loaded.

I moved the server accept() outside of the while loop, and put a sleep(0.01) in the send loop of the client. updated_data grew as expected.

This is a one shot solution because the server only accepts one connection.

Upvotes: 2

Joffo
Joffo

Reputation: 175

I made only following changes to client:

i = (len(pole)-1)
while i>=0:
   c=False
   while c==False:
      try:
        client = socket.socket ( socket.AF_INET, socket.SOCK_STREAM )
        client.connect ( ( 'localhost', 1234 ) )
        print("established")
        c=True
      except:
        print(".")

   client.send(pickle.dumps(pole[i]))
   pole.remove(pole[i])
   i-=1

But in this solution, I need three times esteblish connection. Is there any way to establish only one, and send all data?

Upvotes: 0

Related Questions