Sunday, March 8, 2020

Echo server using python and sockets

This post explains how to create a create a simple echo server using python and sockets.
In the first example we will create a simple echo server allowing one connection and a simple echo client, in the second example we will improve the first example allowing multiple client to be connected.

Version used in this tutorial:

Python: 3.7+

NOTE: In all scripts is present the hash bang for MacOS, you don't need it if you call the script using the python interpreter, i.e.: python my_test_server.py
Alternatively you can change the interpreter path in order to match the desidered one (i.e. /bin/python) and execute it directly (like: ./my_test_server.py  of course after giving the execute permission with "chmod +x my_test_server.py")

Simple echo server:

This simple server listens on a port and waits for one connection, when a client is connected it gets a chunk of data and resend it to the client, until data received is null (i.e. client presses the ENTER key)

#!/usr/bin/env python3

# Echo server 
import socket

HOST = ''    # all available interfaces
PORT = 5005  # any port > 1023
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))
s.listen(1)  # 1 means the number of accepted connections
conn, addr = s.accept()  # waits for a new connection
print('Client connected: ', addr)
while True:
    data = conn.recv(1024)
    if not data:
        break
    conn.sendall(data)
conn.close()

Simple echo client

The client connects the same server port and accepts user input until input data is null (i.e. ENTER key pressed). Any input data is converted to string and sent to the server as bytes, then waits to receive data from server, then prints echoed data to console.

#!/usr/bin/env python3

# Echo client
import socket

HOST = ''    # all available interfaces
PORT = 5005  # any port > 1023
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
print('Client connected.')
while True:
    user_input = str(input())  # convert input to string
    if not user_input:  # i.e. enter key pressed
        break
    s.sendall(user_input.encode())  # encode needed to transform string to bytes
    data = s.recv(1024)
    if not data:
        break
    print("Data received from server: ", data.decode('utf-8'))  # decode needed to convert data to string

Echo server allowing multiple connections

The first echo server can be improved allowing multiple connections. The accept() method blocks the current execution. We can send the accepted connection to a separate thread that will manage the task with the given client, while then the main socket can be ready again to accept a new client.
In order to stop the server gracefully, we will set the stop_main_thread variable and in order to unlock the accept() method we will send a dummy connection request.

#!/usr/bin/env python3

# Echo server 
import socket
import threading

HOST = ''    # all available interfaces
PORT = 5005  # any port > 1023

main_socket: socket.socket = None
stop_main_thread = False

def on_client_connected(conn: socket.socket, addr):
    print('Client connected: ', addr)
    while True:
        data = conn.recv(1024)
        if not data:
            break
        conn.sendall(data)
    conn.close();
    print('Client disconnected: ', addr)


def start_server():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind((HOST, PORT))
    s.listen(10)  # 10 means the number of accepted connections
    global main_socket
    global stop_main_thread
    main_socket = s
    print(f'Server started on port {PORT}, waiting for connections... (Press ENTER key to stop)')
    while True and not stop_main_thread:
        try: 
            conn, addr = s.accept()  # waits for a new connection
            threading.Thread(target=on_client_connected, args=(conn, addr)).start()
        except:
            pass  
    print('Server stopped.')    


def stop_server():
    global stop_main_thread
    stop_main_thread = True
    # makes a dummy connection just to unlock accept method and trigger the gracefully stop
    socket.socket(socket.AF_INET, socket.SOCK_STREAM).connect((HOST, PORT))


main_thread = threading.Thread(target=start_server)
main_thread.start()

while True:
    user_input = input()
    if not user_input:  # i.e. enter key pressed      
        break

print('Stopping server... ')
stop_server()
main_thread.join()
main_socket.close()

You can use the same client to connect to this server.
This is just a simple example and it can be improved a lot, i.e. my managing sent ad received data chunks in a better way.

For more information about sockets:
https://docs.python.org/3/howto/sockets.html


No comments:

Post a Comment

(c) Copyright 2020 - MyTroubleshooting.com