Python sockets, servers and multi threading

import socket
import threading
import time

HOST = '127.0.0.1'
PORT = 8000

REREAD_ON_QUERY = True
CONFIG_FILE = '200k.txt'

# Declare contents as a global variable so it can be accessed by the handle_connection function
contents = ""


def handle_connection(conn, addr):
    """
    Handle an incoming connection.
    """
    global contents  # Declare contents as a global variable so it can be modified in this function

    print(f'Received connection from {addr}')

    # Receive the incoming "String" in clear text
    data = conn.recv(1024)
    data = data.strip(b'\x00')  # Strip any \x00 characters from the end of the payload
    string = data.decode('utf-8')
    print(f'Received string: {string}')

    # Measure the execution time
    start_time = time.time()

    # Open the file and read its contents, if REREAD_ON_QUERY is True
    if REREAD_ON_QUERY:
        try:
            with open(CONFIG_FILE, 'r') as f:
                contents = f.read()
        except FileNotFoundError:
            print('Error: File Not Found')
        except PermissionError:
            print('Error: You have know permission to read the file.')

    # Check whether the "String" exists in the file
    if string in contents:
        response = 'STRING EXISTS\n'
    else:
        response = 'STRING NOT FOUND\n'

    # Send the response back to the client
    conn.sendall(response.encode('utf-8'))

    # Measure the execution time and log the results
    end_time = time.time()
    elapsed_time = end_time - start_time
    print(f'DEBUG: Search query: {string}')
    print(f'DEBUG: Requesting IP: {addr}')
    print(f'DEBUG: Execution time: {elapsed_time:.3f} ms')
    print(f'DEBUG: Timestamp: {time.ctime()}')


def main():
    # Bind to a specific port and listen for incoming connections
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        sock.bind((HOST, PORT))
    except Exception as e:
        print('you cant bind the port')
    print((f'starting up {HOST} on port {PORT}'))
    sock.listen()

    # Accept incoming connections
    while True:
        conn, addr = sock.accept()

        # Create a new thread to handle the connection
        t = threading.Thread(target=handle_connection, args=(conn, addr))
        t.start()
    sock.close()

if __name__ == '__main__':
    main()

I am trying to run my code on a server, but it is not behaving as expected. The server is unable to receive and send data as it should.

I have tried troubleshooting and debugging the code, but I am unable to find the root cause of the issue.

Here is a brief overview of my setup:

Server: [needs to send and recieve msgs depend on if query is found]

Code language:python

Could someone please help me fix this issue and get my server running smoothly? Any guidance or suggestions would be greatly appreciated. Thank you in advance for your help!"

It would help a lot if you could tell us exactly what isn’t working, but in the absence of that, I’ll do a basic code review and see if I can point you in some useful directions.

Your main loop looks pretty classic. Bind to a port, listen, accept, spawn a thread for each connection. You probably want to terminate the process if binding fails (currently you just print out an unhelpful message and then carry on with an unbound listening socket), but otherwise, that looks pretty much fine. (Having code after a while True with no break in it is unnecessary, btw; you’ll never reach that sock.close() call.)

Without seeing your client code, I can’t be sure of what this server will see. Be aware that TCP sockets (indicated by the SOCK_STREAM requirement) don’t have any sort of concept of “messages”, and will simply give you a stream of bytes. It’s generally best to build some sort of protocol into your code, such as “send a message followed by b"\r\n" and then wait for a reply terminated the same way”. In your case, you could possibly have the client shut down the transmit end of the socket to signal the end of the string, and have the server fully close the socket after sending the response, but that’s an unusual way to do things and I wouldn’t normally recommend it :slight_smile: Most likely, this would be the place to focus your attention.

I’d definitely recommend having the server thread close the socket when it’s done. Otherwise you leave them all hanging out in memory, waiting for the clients to close them.

For more useful advice, tell us what your code is doing that it shouldn’t be, or not doing that it should.

By the way: check point 7 of your instructions. It relates directly to my comments regarding protocol. Be sure that the server and client agree on exactly what the end-of-line signal is.

Both client and servers are using the same protocol

import socket
import threading
import time

HOST = '127.0.0.1'
PORT = 8000

REREAD_ON_QUERY = True
CONFIG_FILE = '200k.txt'

# Declare contents as a global variable so it can be accessed by the handle_connection function
contents = ""


def handle_connection(conn, addr):
    """
    Handle incoming requests from a client.
    """
    global contents  # Declare contents as a global variable so it can be modified in this function

    print(f'Received connection from {addr}')

    # Continuously handle requests from the same client
    while True:
        # Receive the incoming "String" in clear text
        try:
            data  = conn.recv(1024)
        except ConnectionResetError:
        	print('Error: Connection was reset by client.')
        if not data:  # If data is not received, the client has closed the connection
            break

        data = data.strip(b'\x00')  # Strip any \x00 characters from the end of the payload
        string = data.decode('utf-8')

        print(f'Received request from {addr}: {string}')

        # Measure the execution time
        start_time = time.time()

        # Open the file and read its contents, if REREAD_ON_QUERY is True
        if REREAD_ON_QUERY:
            try:
                with open(CONFIG_FILE, 'r') as f:
                    contents = f.read()
            except FileNotFoundError:
                print('Error: File Not Found')
            except PermissionError:
                print('Error: You have no permission to read the file.')

        # Check whether the "String" exists in the file
        if string in contents:
            response = 'STRING EXISTS\n'
        else:
            response = 'STRING NOT FOUND\n'

        # Send the response back to the client
        try:
            conn .sendall(response.encode('utf-8'))
        except ConnectionResetError:
            print('Error: Connection was reset by client.')
            print(f'Sent response to {addr}: {response.strip()}')

    # Measure the execution time and log the results
        end_time = time.time()
        elapsed_time = end_time - start_time
        print(f'DEBUG: Search query: {string}')
        print(f'DEBUG: Requesting IP: {addr}')
        print(f'DEBUG: Execution time: {elapsed_time:.3f} ms')
        print(f'DEBUG: Timestamp: {time.ctime()}')


def main():
    # Bind to a specific port and listen for incoming connections
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        sock.bind((HOST, PORT))
    except Exception as e:
        sock.close()
    print((f'starting up {HOST} on port {PORT}'))
    sock.listen()

    # Accept incoming connections
    while True:
        conn, addr = sock.accept()

        # Create a new thread to handle the connection
        t = threading.Thread(target=handle_connection, args=(conn, addr))
        t.start()

if __name__ == '__main__':
    main()

This is a modified version of the script. the question now is the code complete from the requirements i provided. Pls help me analyse,

The first question is: does it work? It’s most important to test it. But otherwise…

  • What is the contents variable, and why does it need to be global?
  • Be careful of mixing tabs and spaces. There are places where your indentation is technically legal but may cause you confusion.
  • You’re still closing the socket on failed-bind but not actually preventing the main loop from running. So now, at least, it won’t go into the main listen/accept loop, but it’s going to fail with a noisy and possibly-confusing error.
  • Since it’s possible for one conn.recv call to return any amount of data up to the limit you specify, you should probably have your own buffering, in case the message gets split across two reads. You may not notice this with small messages on localhost, but over the internet and/or with larger messages, and especially if there’s a bit of lag, you could easily get multiple messages grouping up.
  • What happens if you fail to reread the file? You print out an “Error” line, but just continue regardless, as if it worked.
  • Does time spent sending the response get included in your execution time? It’s not completely clear from the specs.

But the most important thing to do is to try it!