Python TCP Networking Sockets client/server

Hello, I need help with a homework assignment because I have tried to figure it out but cannot.
The assignment:
Write a Python network TCP server/client script.
It should be 1 script that can accept the arguments SERVER and run the server-side script or CLIENT and run the client-side script
Upon receiving a connection, it should send back to the client its IP address (client IP). Then it should wait for commands from the client.
Valid commands are: “File”, “TIME”, “IP”, “OS” and “EXIT”.
To the TIME command, the server should return the current time.
To the IP command, it returns the client’s IP address.
To the “OS” command, the server returns the name of its operating system and version.
To the “File” command, the server sends a dummy file to the client.
If the client closes the connection or does not respond with a command in a reasonable time (20 seconds), the server must close the current connection and wait for another connection (see Setting a timeout on a socket).
To the EXIT command, your server should close all open sockets and exit.

My issues:

  1. After entering 1 or 2 commands my stream no longer replies. Maybe buffer issue?
  2. How, HOW to close the socket after 20 seconds??? This seems to be much harder than it should be!!

My client code:

#shell_client.py for python3
import socket
import time

s= socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(("192.168.135.128",4144)) #this is my local IP
filedata = bytearray()

while True:
	data = s.recv(1024) #max size of data to receive
	print(data.decode())
	cmd = input() #allow the user to input data and fill the cmd variable	
	s.send(cmd.encode('utf-8')) #encode cmd and send it to the server
	
	
	#if the cmd is file, receive some data, decode it and print the output
	if cmd == "File":
	    print("cmd = File") #for debugging purposes	
	    datafile1 = s.recv(1024)
	    filedata.extend(datafile1) #extend the byte array beacuse dealing with multiple packets
	    print (filedata.decode())
	
	#if the cmd is time, receive some data, decode it and print the output     
	elif cmd == "TIME":
	    print("cmd = time") #for debugging purposes
	    timedata = s.recv(1024)
	    print("The current time is:", timedata.decode())
	
	#if the cmd is os, receive some data, decode it and print the output    
	elif cmd == "OS":
            print("cmd = os") #for debugging purposes
            OSdata = s.recv(1024)
            print("The OS is:", OSdata.decode())
        
        #if the cmd is ip, receive some data, decode it and print the output    
	elif cmd == "IP":
            print("cmd = ip") #for debugging purposes
            #hostname = socket.gethostname()
            #local_ip = socket.gethostbyname(hostname)
            #print("Client IP is:", format(local_ip))
            IPdata = s.recv(1024)
            print("Client IP:", IPdata.decode())
        
        #if the cmd is exit, close the socket and exit    
	elif cmd == "EXIT":
            print("cmd = exit") #for debugging purposes
            s.close()
            break 

My SERVER code:

#shell_server.py for python3
import socket
import subprocess
import time
import platform

#try to create a socket s
try:
    
    s= socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    #s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(("192.168.135.128",444)) #bind to all ip in the range
    s.listen(1) #listen for 1 connection
    
    #while the socket is created, keep the socket open
    while True:
        connexion, client_address = s.accept()
        print ("connexion from :" +str(client_address))
        
        #while there is a connection, ask to receive a cmd and reply accordingly
        while True:
            connexion.send(bytes("\nEnter cmd : ", "utf-8"))
            cmd = connexion.recv(8192).decode()
            # p = subprocess.Popen(cmd.split(" "),shell=True
            p = subprocess.Popen(cmd,shell=True
                ,stdout=subprocess.PIPE,stderr = subprocess.PIPE)
            out , err = p.communicate()
            
            connexion.send(out)        
            
            #if cmd is file the open the file and display for client
            if cmd == "File": 
                #print("Received command: File") #for debuggin purposes
                p= subprocess.Popen(["cat", "file1.txt"],
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE)
                
                out,err = p.communicate()
                
                connexion.send(out)
            
            #if cmd is time send the current time to the client    
            elif cmd == "TIME":
                t = time.localtime()
                current_time = time.strftime("%H:%M:%S", t)
                print("cmd = time") #for debugging purposes
                connexion.send(current_time.encode())
            
            #if the cmd is os send the os name and version to the client    
            elif cmd == "OS":
                #print("cmd = OS") #for debugging purposes
                os = platform.platform()
                connexion.send(os.encode())
             
            #if the cmd is ip .... i guess i dont need this because i want to display client ip address    
            elif cmd == "IP":
                #print(client_address) #for debugging purposes
                connexion.send(str(client_address).encode())
                
             
            #if the cmd is exit then close the socket and exit (do not listen)    
            elif cmd == "EXIT":
                #print("cmd = exit") #for debugging purposes
                s.close()
                break


#finally close the connection                             
finally:
    s.close()

PLEASE any comments would be very much appreciated!!

Couple of points to start with.

  • Please test your code exactly as-is before you post it. Right now, you have mismatched port numbers in your server and client. Check that sort of thing :slight_smile:
  • The instructions say one script with both sets of code in it, but you’ve done two separate scripts. I’ll let you figure out how to merge them.
  • Rather than hard-coding an IP address, you can use 0.0.0.0 on the server to bind to all addresses, and 127.0.0.1 on the client to connect to localhost. Might be easier when asking for help, since it doesn’t depend on your precise address. Also, your server code has a comment about binding to “all IP in the range”, which is false at the moment - you probably want 0.0.0.0 for that.
  • No idea why your server runs the command (subprocess, shell=True), since you’re mainly handling commands yourself. Left-over code from previous tests?

As to your actual TCP doesn’t have a concept of messages; it’s just a stream of bytes. You’ll need some clear and definitive way to mark the end of one message and the beginning of another. Usually that would be an end-of-line marker "\r\n" (or if you prefer, just "\n", but most internet protocols use CRLF).

The instructions tell you “see Setting a timeout on a socket”, which is probably a link. I’d recommend following it. Timeouts are a well-known concept with sockets.

The stipulated protocol flow is a little unusual in that the client doesn’t send a QUIT command, it just disconnects. But if that’s how it is, you definitely need to check for client disconnection (TBH you should be coping with this regardless). When the client is gone, you’ll get a blank read from the socket. That’s your signal to close out cleanly and go back for another client.

Side point: this server is stuck handling one client at a time. I presume that your next challenge, once this is set up, is to implement multiplexing, probably with threads, since that’s the most similar to the code you have. In order to make your life easier when that happens, I strongly recommend making some functions in your server, maybe something like:

def handle_client(sock):
    """Respond to one client's commands"""
    # while True: ...

def listen():
    """Listen for incoming connections and call handle_client for each one"""
    # s = socket.socket(...)
    # while True: s.accept() ...

This is optional, but it’ll make the code easier to follow now, and way WAY easier to turn into something multithreaded later. (Moving to asyncio would be a bigger change from this code, so I hope they’re going for threads first.)

Sort those matters out, then see how it goes. You may find that your issues with the stream not replying have magically disappeared :slight_smile:

https://stromberg.dnsalias.org/~strombrg/TCP/