├── README.md ├── client.py └── server.py /README.md: -------------------------------------------------------------------------------- 1 | # Simple Socket Chat 2 | 3 | Simple chat project, written in Python using sockets for college network class. 4 | 5 | ## Requirements 6 | 7 | In order to run the project, you'll need to have Python 3.7.6 or greater, in case that you already have it you can go to next section: **Running the project**. 8 | If you don't have it you can use [pyenv](https://github.com/pyenv/pyenv) with [pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv) 9 | for download python and create your python environment for running the project. 10 | 11 | I'm going to leave below a tutorial of how to configure your environment for running the project using `pyenv` with `pyenv-virtualenv`. 12 | 13 | ### Installing Python with `pyenv` 14 | 15 | ```bash 16 | $ pyenv install 3.7.6 17 | ``` 18 | 19 | ### Creating virtual environment 20 | 21 | ```bash 22 | $ pyenv virtualenv 3.7.6 socketchat 23 | ``` 24 | 25 | ### Using the virtual environment 26 | 27 | ```bash 28 | $ pyenv activate socketchat 29 | ``` 30 | 31 | ## Running the project 32 | 33 | For running the project, you will need to have an server to receive and send messages to 34 | clients that are connected, and at least 2 clients to see messages being broadcasted. 35 | 36 | - Creating the server: 37 | 38 | If you are using Python 3.7.6 without `pyenv`, run the following command below to start 39 | running server: 40 | ```bash 41 | $ python3 server.py 42 | ``` 43 | 44 | If you are using `pyenv`, just run the following command to up the server: 45 | ```bash 46 | (socketchat) $ python server.py 47 | ``` 48 | 49 | - Creating clients: 50 | 51 | Now we are going to need two clients, in order to see each other message on terminal. 52 | 53 | If you are using Python 3.7.6 without `pyenv`, run the following command in two different 54 | terminals/bashes in order to create our clients and exchange messagens between them. 55 | ```bash 56 | $ python3 client.py 57 | ``` 58 | 59 | Otherwise if you are using `pyenv`, simply run the following code in different terminals/bashes: 60 | ```bash 61 | (socketchat) $ python client.py 62 | ``` 63 | 64 | > If you are a client and want to quit from chat, simply write `quit` and you will be 65 | > disconnected from the chat. -------------------------------------------------------------------------------- /client.py: -------------------------------------------------------------------------------- 1 | import socket, threading 2 | 3 | def handle_messages(connection: socket.socket): 4 | ''' 5 | Receive messages sent by the server and display them to user 6 | ''' 7 | 8 | while True: 9 | try: 10 | msg = connection.recv(1024) 11 | 12 | # If there is no message, there is a chance that connection has closed 13 | # so the connection will be closed and an error will be displayed. 14 | # If not, it will try to decode message in order to show to user. 15 | if msg: 16 | print(msg.decode()) 17 | else: 18 | connection.close() 19 | break 20 | 21 | except Exception as e: 22 | print(f'Error handling message from server: {e}') 23 | connection.close() 24 | break 25 | 26 | def client() -> None: 27 | ''' 28 | Main process that start client connection to the server 29 | and handle it's input messages 30 | ''' 31 | 32 | SERVER_ADDRESS = '127.0.0.1' 33 | SERVER_PORT = 12000 34 | 35 | try: 36 | # Instantiate socket and start connection with server 37 | socket_instance = socket.socket() 38 | socket_instance.connect((SERVER_ADDRESS, SERVER_PORT)) 39 | # Create a thread in order to handle messages sent by server 40 | threading.Thread(target=handle_messages, args=[socket_instance]).start() 41 | 42 | print('Connected to chat!') 43 | 44 | # Read user's input until it quit from chat and close connection 45 | while True: 46 | msg = input() 47 | 48 | if msg == 'quit': 49 | break 50 | 51 | # Parse message to utf-8 52 | socket_instance.send(msg.encode()) 53 | 54 | # Close connection with the server 55 | socket_instance.close() 56 | 57 | except Exception as e: 58 | print(f'Error connecting to server socket {e}') 59 | socket_instance.close() 60 | 61 | 62 | if __name__ == "__main__": 63 | client() 64 | -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | import socket, threading 2 | 3 | # Global variable that mantain client's connections 4 | connections = [] 5 | 6 | def handle_user_connection(connection: socket.socket, address: str) -> None: 7 | ''' 8 | Get user connection in order to keep receiving their messages and 9 | sent to others users/connections. 10 | ''' 11 | while True: 12 | try: 13 | # Get client message 14 | msg = connection.recv(1024) 15 | 16 | # If no message is received, there is a chance that connection has ended 17 | # so in this case, we need to close connection and remove it from connections list. 18 | if msg: 19 | # Log message sent by user 20 | print(f'{address[0]}:{address[1]} - {msg.decode()}') 21 | 22 | # Build message format and broadcast to users connected on server 23 | msg_to_send = f'From {address[0]}:{address[1]} - {msg.decode()}' 24 | broadcast(msg_to_send, connection) 25 | 26 | # Close connection if no message was sent 27 | else: 28 | remove_connection(connection) 29 | break 30 | 31 | except Exception as e: 32 | print(f'Error to handle user connection: {e}') 33 | remove_connection(connection) 34 | break 35 | 36 | 37 | def broadcast(message: str, connection: socket.socket) -> None: 38 | ''' 39 | Broadcast message to all users connected to the server 40 | ''' 41 | 42 | # Iterate on connections in order to send message to all client's connected 43 | for client_conn in connections: 44 | # Check if isn't the connection of who's send 45 | if client_conn != connection: 46 | try: 47 | # Sending message to client connection 48 | client_conn.send(message.encode()) 49 | 50 | # if it fails, there is a chance of socket has died 51 | except Exception as e: 52 | print('Error broadcasting message: {e}') 53 | remove_connection(client_conn) 54 | 55 | 56 | def remove_connection(conn: socket.socket) -> None: 57 | ''' 58 | Remove specified connection from connections list 59 | ''' 60 | 61 | # Check if connection exists on connections list 62 | if conn in connections: 63 | # Close socket connection and remove connection from connections list 64 | conn.close() 65 | connections.remove(conn) 66 | 67 | 68 | def server() -> None: 69 | ''' 70 | Main process that receive client's connections and start a new thread 71 | to handle their messages 72 | ''' 73 | 74 | LISTENING_PORT = 12000 75 | 76 | try: 77 | # Create server and specifying that it can only handle 4 connections by time! 78 | socket_instance = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 79 | socket_instance.bind(('', LISTENING_PORT)) 80 | socket_instance.listen(4) 81 | 82 | print('Server running!') 83 | 84 | while True: 85 | 86 | # Accept client connection 87 | socket_connection, address = socket_instance.accept() 88 | # Add client connection to connections list 89 | connections.append(socket_connection) 90 | # Start a new thread to handle client connection and receive it's messages 91 | # in order to send to others connections 92 | threading.Thread(target=handle_user_connection, args=[socket_connection, address]).start() 93 | 94 | except Exception as e: 95 | print(f'An error has occurred when instancing socket: {e}') 96 | finally: 97 | # In case of any problem we clean all connections and close the server connection 98 | if len(connections) > 0: 99 | for conn in connections: 100 | remove_connection(conn) 101 | 102 | socket_instance.close() 103 | 104 | 105 | if __name__ == "__main__": 106 | server() --------------------------------------------------------------------------------