Search code examples
pythonpython-3.xsocketsnetwork-programmingtcp

python socket parallel sending and receiving


I'm working on a simple project that sends "server" cpu and ram usage with pstuil library to clients, but I got stuck. in a nutshell, my problem is I can't verify what data client's receive.
My Server code:

# Import Section
import socket
import threading
import psutil
import time

# TCP Socket Connection for server
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(("127.0.0.1", 6767))
server.listen()
client, address = server.accept()


# Functions
def cpu():
    while True:
        cpu_usage = str(psutil.cpu_percent(1))
        client.send(cpu_usage.encode("ascii"))


def ram():
    while True:
        time.sleep(1)
        ram_usage = str(psutil.virtual_memory()[2])
        client.send(ram_usage.encode("ascii"))


# Threads
t1 = threading.Thread(target=cpu).start()
t2 = threading.Thread(target=ram).start()

My Client Code:

# Import Section
import socket
import threading
import time

# TCP Socket Connection for server
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.connect(("127.0.0.1", 6767))


# Functions
def cpu():
    while True:
        server.send("cpu".encode("ascii"))
        cpu_usage = server.recv(1024).decode("ascii")
        print(f"cpu usage is {cpu_usage}")
        time.sleep(1)


def ram():
    while True:
        server.send("ram".encode("ascii"))
        ram_usage = server.recv(1024).decode("ascii")
        print(f"ram usage is {ram_usage}")
        time.sleep(1)


# Threads
t1 = threading.Thread(target=cpu).start()
t2 = threading.Thread(target=ram).start()

My expected Output:

cpu usage is cpu_usage
ram usage is ram_usage

example: 
cpu usage is 0.3
ram usage is 14.7

Real output:

randomly
cpu usage is ram_usage
ram usage is cpu_usage

or

cpu usage is cpu_usage
ram usage is ram_usage

example:
cpu_usage is 14.7
ram usage is 0.3
cpu usage is 0.3
ram usage is 14.7

it seems that they are not being sent in a line. one answer was to send all data by server but i want to send just the raw cpu or ram data and sort them in client code.


Solution

  • Thanks for providing the code, I was able to easily reproduce the issue on my VM.

    Short Answer: You send both RAM and CPU data packets to the same socket and when you call server.recv() you could either get RAM or CPU data packet, regardless of the function you used for calling recv().

    More Information: While TCP guarantees packet ordering, There's a possibility when using different threads for each functionality that they will get out of sync so much that you'll get to the point where you call cpu() twice in a row before calling ram() (or vise-versa) on the server or client side. You can even see this happen as the prints to screen are not consistent and sometimes print CPU or RAM twice in a row.

    There are a few possibilities to fix this. By using a different socket for each functionality you can make sure that there's never a mix up (see code below). Another option is to only run the measurement on the server side when the client requests it. I'm not sure if this is what you meant to do here on the client code:

    in cpu():

    cpu_server.send("cpu".encode("ascii"))
    

    in ram():

    ram_server.send("ram".encode("ascii"))
    

    I can see these packets go out in tcpdump, but I don't think they do anything as the server don't get them from the socket.

    Your code with socket per measurement (feel free to use/modify):

    server.py:

    # Import Section
    import socket
    import threading
    import psutil
    import time
    
    # Functions
    def cpu():
        # TCP Socket Connection for CPU server
        cpu_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        cpu_server.bind(("127.0.0.1", 6767))
        cpu_server.listen()
        cpu_client, cpu_address = cpu_server.accept()
        while True:
            cpu_usage = str(psutil.cpu_percent(1))
            cpu_client.send(cpu_usage.encode("ascii"))
    
    
    def ram():
        # TCP Socket Connection for RAM server
        ram_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        ram_server.bind(("127.0.0.1", 6768))
        ram_server.listen()
        ram_client, ram_address = ram_server.accept()
        while True:
            time.sleep(1)
            ram_usage = str(psutil.virtual_memory()[2])
            ram_client.send(ram_usage.encode("ascii"))
    
    # Threads
    t1 = threading.Thread(target=cpu).start()
    t2 = threading.Thread(target=ram).start()
    

    client.py:

    # Import Section
    import socket
    import threading
    import time
    
    # Functions
    def cpu():
        # TCP Socket Connection for CPU server
        cpu_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        cpu_server.connect(("127.0.0.1", 6767))
        while True:
            cpu_usage = cpu_server.recv(1024).decode("ascii")
            print(f"cpu usage is {cpu_usage}")
            time.sleep(1)
    
    def ram():
        # TCP Socket Connection for RAM server
        ram_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        ram_server.connect(("127.0.0.1", 6768))
        while True:
            ram_usage = ram_server.recv(1024).decode("ascii")
            print(f"ram usage is {ram_usage}")
            time.sleep(1)
    
    # Threads
    t1 = threading.Thread(target=cpu).start()
    t2 = threading.Thread(target=ram).start()
    

    Please note - I had to do the TCP connection inside the thread as the operation is blocking and response time is too slow. When I tried connecting from the main process I got an issue on the client as by the time it calls connect() on the second socket, the server side might not yet be listening on its second socket.