0

I am trying to send an image from a client to the server, using python sockets. The problem is the entire image gets sent only occasionally, and fails most of the times.

Here is a summary of what I’m doing: First I load an image from a file on the client’s computer, convert it to a numpy array and then to a bytes object, which I then send to the server. I add a “done” at the end of the bytes object to let the server know that the image transmission is complete.

However, although the “done” at the end of the bytes object gets sent every time, the entire image itself (which should be sent before the “done”) doesen’t. It works perfectly once in a while but not always.

Here is full reproducible code:

server.py

import numpy as np
from _thread import start_new_thread

IP = "0.0.0.0"  # Address to bind to
PORT = 5555  # Arbitrary non-privileged port
DEFAULT_BYTES = 2048
total_connections_so_far = 0

# function to send some data to a client
def send(data, conn):
    try:
        pickled_data = pickle.dumps(data)
        conn.sendall(pickled_data)
    except Exception as e:
        print(e)


# recieve some data
def recieve_data(conn, size=DEFAULT_BYTES):
    data = conn.recv(size)
    return data


# deal with sending and recieving data from and to a user
def threaded_client(conn, addr):
    # send this user some start up info
    send(str.encode("You are now connected to the server!", "utf-8"), conn)

    while True:
        try:
            data = recieve_data(conn)

            # user has disconnected
            if not data:
                break

            data = pickle.loads(data)  # data comes in as pickle encoded bytes
            reply = {}  # initiate a reply, to send to the user

            # user has updated something
            if data.get("image"):
                dtype = data["image"]["dtype"]
                size = data["image"]["size"]
                shape = data["image"]["shape"]
                full_image = b""
                print("Total bytes supposed to be recieved:", size)

                image_bytes = b""  # image will come in as bytes
                # the last 4 bytes will be "done" - to inform that the image transmission is complete
                while image_bytes[-4:] != b"done":
                    image_bytes = recieve_data(conn,)
                    full_image += image_bytes
                    # print(len(full_image), image_bytes[-4:])

                full_image = full_image[
                    :-4
                ]  # get rid of the last 4 bytes, which are gonna be "done"
                print("Total bytes recieved: ", len(full_image))

                # convert buffer to numpy array
                full_image = np.frombuffer(full_image, dtype=dtype)
                # reshape into 2d
                full_image = full_image.reshape(*shape)

                # surf = pygame.surfarray.make_surface(full_image)
                # pygame.image.save(surf, "test.png")

                reply["recieved"] = len(
                    full_image
                )  # send the number of bytes recieved to the client

            send(reply, conn)

        except Exception as e:
            print(e)
            break

    # the user has disconnected
    print(f"Disconnected: {addr}")

    # close the connection
    conn.close()


with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    # bind the socket to the host address and port
    s.bind((IP, PORT))
    print("Server started at: ", s.getsockname())

    # listen for connections
    s.listen()
    print("Server has started. waiting for connections...")

    while True:
        # accept any connectionn
        conn, addr = s.accept()
        print("Connected by", addr)
        total_connections_so_far += 1  # increment the totoal connections
        start_new_thread(threaded_client, (conn, addr,))

client.py ( you may have to send the image to the server a couple of times to encounter the error )

import socket, pickle, pygame, os


class Network:
    def __init__(self):
        # initialise the client
        self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server = ""  # the ip address of the server
        self.port = 5555  # port of the server
        self.addr = (
            self.server,
            self.port,
        )  # complete address, to which we can now connect to

    # function to connect to the server
    def connect(self):
        try:
            self.client.connect(self.addr)
            print("Connected!")
            # the server sends some text in the beginning
            data = self.recv()
            print(data.decode("utf-8"))
        except Exception as e:
            print("Could not connect to server!")
            print(e)

    # send some data to the server
    def send(self, data, pickle_data=True):
        try:
            if pickle_data:
                data = pickle.dumps(data)
            self.client.sendall(data)

        except Exception as e:
            print(e)
            return None

    # recieve some data from the server
    def recv(self, buffer_size=2048, load_pickle=True):
        try:
            data = self.client.recv(buffer_size)
            if not data:
                return None
            return pickle.loads(data) if load_pickle else data
        except Exception as e:
            print(e)
            return None


def main():
    n = Network()
    n.connect()
    base_path = os.path.dirname(__file__)
    while True:
        filepath = input("Paste in the absolute path to the image file: ")
        filepath = os.path.join(base_path, filepath)

        try:
            image = pygame.image.load(filepath)  # load the image using pygame
        except Exception as e:
            print(e)
            continue
        # get a numpy array of pixels from the image
        pixel_array = pygame.surfarray.array2d(image)

        send_image(pixel_array, n)

        print(n.recv())  # print out the number of bytes recieved by the server


def send_image(arr, n):
    image_bytes = arr.tobytes()  # convert it to bytes
    image_bytes += b"done"
    size = len(image_bytes)  # the size of the image + the "done" at the end
    print(image_bytes)
    # send the server the info about the image
    n.send({"image": {"dtype": arr.dtype, "shape": arr.shape, "size": size}})

    # send the image bytes
    n.send(image_bytes, pickle_data=False)


main()

I want to be able to send 100% of the image all the time, Please help me out!

Thanks!

xxSirFartAlotxx
  • 394
  • 1
  • 11
  • why do waste time to convert to numpy array and bytes objects - you could read file in bytes mode using standrad `open()` and send it directly - image may use less data because it is compressed as JPG or PNG. – furas Dec 18 '21 at 17:52
  • `socket` is primitive object and sending `done` is not good idea. Better first send data size as ie. 4 bytes and later data. And other side first has to get 4 bytes to get length and later it can use this length to check if it gets all data. `socket` doesn't have to send all data in one `recv` and you should use loop to read until you get `not data`. Besides you use `buffer_size=2048` so you read only `2048` but data can be longer. – furas Dec 18 '21 at 17:54
  • I can't reproduce your problem. But I see other mistakes. You display size `"Total bytes supposed to be recieved"` with `done` but `"Total bytes recieved"` without `done` so they display different values. You ask for absolute path but later you run `os.path.join(base_path, filepath)` to create absolute path using `base_path`. server send back `'recieved' with `image width` (because you `reshaped` data) but in comment you have `# send the number of bytes recieved to the client` – furas Dec 18 '21 at 22:14
  • @furas Thanks for the suggestion, I will try it out later. As for the “other mistakes”, I apologize for the misleading comments and prints, I was just trying to create some quick reproducible code – xxSirFartAlotxx Dec 19 '21 at 02:29

0 Answers0