TCP Chat in Python

You are currently viewing TCP Chat in Python

Introduction

Python is a great programming language for computer networking. It allows us to create solid applications very fast and easily. In this tutorial, we are going to implement a fully-functioning TCP chat. We will have one server that hosts the chat and multiple clients that connect to it and communicate with each other. At the end, you can also add custom features like chat rooms, commands, roles etc., if you want to.

Client-Server Architecture

For our application, we will use the client-server architecture. This means that we will have multiple clients (the users) and one central server that hosts everything and provides the data for these clients.

Therefore, we will need to write two Python scripts. One will be for starting the server and one will be for the client. We will have to run the server first, so that there is a chat, which the clients can connect to. The clients themselves, are not going to directly communicate to each other but via the central server.

Implementing The Server

Now let’s start by implementing the server first. For this we will need to import two libraries, namely socket and threading. The first one will be used for the network connection and the second one is necessary for performing various tasks at the same time.

import socket
import threading

The next task is to define our connection data and to initialize our socket. We will need an IP-address for the host and a free port number for our server. In this example, we will use the localhost address (127.0.0.1) and the port 55555. The port is actually irrelevant but you have to make sure that the port you are using is free and not reserved. If you are running this script on an actual server, specify the IP-address of the server as the host. Check out this list of reserved port numbers for more information.

# Connection Data
host = '127.0.0.1'
port = 55555

# Starting Server
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((host, port))
server.listen()

# Lists For Clients and Their Nicknames
clients = []
nicknames = []

When we define our socket, we need to pass two parameters. These define the type of socket we want to use. The first one (AF_INET) indicates that we are using an internet socket rather than an unix socket. The second parameter stands for the protocol we want to use. SOCK_STREAM indicates that we are using TCP and not UDP.

After defining the socket, we bind it to our host and the specified port by passing a tuple that contains both values. We then put our server into listening mode, so that it waits for clients to connect. At the end we create two empty lists, which we will use to store the connected clients and their nicknames later on.

# Sending Messages To All Connected Clients
def broadcast(message):
    for client in clients:
        client.send(message)

Here we define a little function that is going to help us broadcasting messages and makes the code more readable. What it does is just sending a message to each client that is connected and therefore in the clients list. We will use this method in the other methods.

Now we will start with the implementation of the first major function. This function will be responsible for handling messages from the clients.

# Handling Messages From Clients
def handle(client):
    while True:
        try:
            # Broadcasting Messages
            message = client.recv(1024)
            broadcast(message)
        except:
            # Removing And Closing Clients
            index = clients.index(client)
            clients.remove(client)
            client.close()
            nickname = nicknames[index]
            broadcast('{} left!'.format(nickname).encode('ascii'))
            nicknames.remove(nickname)
            break

As you can see, this function is running in a while-loop. It won’t stop unless there is an exception because of something that went wrong. The function accepts a client as a parameter. Everytime a client connects to our server we run this function for it and it starts an endless loop.

What it then does is receiving the message from the client (if he sends any) and broadcasting it to all connected clients. So when one client sends a message, everyone else can see this message. Now if for some reason there is an error with the connection to this client, we remove it and its nickname, close the connection and broadcast that this client has left the chat. After that we break the loop and this thread comes to an end. Quite simple. We are almost done with the server but we need one final function.

# Receiving / Listening Function
def receive():
    while True:
        # Accept Connection
        client, address = server.accept()
        print("Connected with {}".format(str(address)))

        # Request And Store Nickname
        client.send('NICK'.encode('ascii'))
        nickname = client.recv(1024).decode('ascii')
        nicknames.append(nickname)
        clients.append(client)

        # Print And Broadcast Nickname
        print("Nickname is {}".format(nickname))
        broadcast("{} joined!".format(nickname).encode('ascii'))
        client.send('Connected to server!'.encode('ascii'))

        # Start Handling Thread For Client
        thread = threading.Thread(target=handle, args=(client,))
        thread.start()

When we are ready to run our server, we will execute this receive function. It also starts an endless while-loop which constantly accepts new connections from clients. Once a client is connected it sends the string ‘NICK’ to it, which will tell the client that its nickname is requested. After that it waits for a response (which hopefully contains the nickname) and appends the client with the respective nickname to the lists. After that, we print and broadcast this information. Finally, we start a new thread that runs the previously implemented handling function for this particular client. Now we can just run this function and our server is done.

Notice that we are always encoding and decoding the messages here. The reason for this is that we can only send bytes and not strings. So we always need to encode messages (for example using ASCII), when we send them and decode them, when we receive them.

receive()

Implementing The Client

A server is pretty useless without clients that connect to it. So now we are going to implement our client. For this, we will again need to import the same libraries. Notice that this is now a second separate script.

import socket
import threading

The first steps of the client are to choose a nickname and to connect to our server. We will need to know the exact address and the port at which our server is running.

# Choosing Nickname
nickname = input("Choose your nickname: ")

# Connecting To Server
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 55555))

As you can see, we are using a different function here. Instead of binding the data and listening, we are connecting to an existing server.

Now, a client needs to have two threads that are running at the same time. The first one will constantly receive data from the server and the second one will send our own messages to the server. So we will need two functions here. Let’s start with the receiving part.

# Listening to Server and Sending Nickname
def receive():
    while True:
        try:
            # Receive Message From Server
            # If 'NICK' Send Nickname
            message = client.recv(1024).decode('ascii')
            if message == 'NICK':
                client.send(nickname.encode('ascii'))
            else:
                print(message)
        except:
            # Close Connection When Error
            print("An error occured!")
            client.close()
            break

Again we have an endless while-loop here. It constantly tries to receive messages and to print them onto the screen. If the message is ‘NICK’ however, it doesn’t print it but it sends its nickname to the server. In case there is some error, we close the connection and break the loop. Now we just need a function for sending messages and we are almost done.

# Sending Messages To Server
def write():
    while True:
        message = '{}: {}'.format(nickname, input(''))
        client.send(message.encode('ascii'))

The writing function is quite a short one. It also runs in an endless loop which is always waiting for an input from the user. Once it gets some, it combines it with the nickname and sends it to the server. That’s it. The last thing we need to do is to start two threads that run these two functions.

# Starting Threads For Listening And Writing
receive_thread = threading.Thread(target=receive)
receive_thread.start()

write_thread = threading.Thread(target=write)
write_thread.start()

And now we are done. We have a fully-functioning server and working clients that can connect to it and communicate with each other. 

Running The Chat

Let’s go for a test run. Just keep in mind that we always need to start the server first because otherwise the clients can’t connect to a non-existing host. Here you can see an example of a chat with two clients (you can also connect with 20 if you want):

Server Log

Connected with ('127.0.0.1', 4970)
Nickname is Neural
Connected with ('127.0.0.1', 4979)
Nickname is Nine

Client A Log

Choose your nickname: Neural
Neural joined!Connected to server!
Nine joined!
Hello
Neural: Hello
Nine: What's up?
Not much! How about you?
Neural: Not much! How about you?
Nine: Same! Gotta Go! Bye!
Nine left!

Client B Log

Choose your nickname: Nine
Nine joined!
Connected to server!
Neural: Hello
What's up?
Nine: What's up?
Neural: Not much! How about you?
Same! Gotta Go! Bye!
Nine: Same! Gotta Go! Bye!

As you can see, it works very well! You can now modify and customize your chat however you want. Maybe you want to have multiple chat rooms or maybe you want to have different roles like admin and moderator. Be creative!

I hope you enjoyed this tutorial and you learned something. If you are interested in a GUI version of this chat, please leave a comment. It works quite differently, since it works more with events than with endless loops. Also, if you want to tell me something or ask questions, feel free to ask in the comments! Check out my instagram page or the other parts of this website, if you are interested in more! Stay tuned!

Follow NeuralNine on Instagram: Click Here

This Post Has 39 Comments

  1. tejaswan

    an error came

    Traceback (most recent call last):
    File “server.py”, line 11, in
    server.listen()
    File “/usr/lib/python2.7/socket.py”, line 228, in meth
    return getattr(self._sock,name)(*args)
    TypeError: listen() takes exactly one argument (0 given)

    1. tejaswan

      yes its working
      i have used python2 instead of python3

    2. Aditya

      You can write the no of connections in it like 10 or 100. That’s it.

    3. Lewis

      I put 1 in there.

    4. op john

      Your should put listen(5 or how much client you want to connect)

  2. Preetam

    Can you help me tejaswan, i am getting the same error.

    1. rene8128

      If u have the same error as tejaswan, then u should download python3 python3 programs are not suitable for python2

    2. Avinash Gupta

      try keeping some value inside the listen function…

  3. Amit Sahu

    Hey!
    Just asking out of curiosity, how to go about a client – client version of this?

    1. Danyal

      do you mean peer to peer, multiple clients?

    2. root user

      you are right but in perspective the client is also a server bcoz he is accepting or sending the messages, it is same for all.

  4. Elliot

    I would like to know how to do a a visual studio ui version of this

  5. Sylvanus

    When does the handle function on the server side get called? I read over and over through the code, and I saw that the broadcast function got called in the receive function and the receive function get called at the end of the script, but the handle function was only defined and not called, is there a reason for this?

  6. rj varma

    File “c:\Users\91994\Desktop\RJ networks\python\TCP_server.py”, line 8
    server.bind((host,port))
    ^
    SyntaxError: invalid syntax

    I just want to clear my doubt your video was awesome…..but, I don’t know why I am getting this error in visual studio

  7. Akshit

    hey, how can I use a public IP in these scripts so that I can run it over the internet?

  8. Costantin

    HI, i tried to run this chatroom. the server runs correctly, but when i open the client, insert the nickname and connect to server, the server automatically get closed and the client starts a loop. how can i solve?

  9. Alex

    It would be awesome to learn how to make this not only on local but allow people from any router to access and type through it.

  10. pravenesh

    how to transfer the file in chat room between clientt

  11. adriyaman

    the code is working but i cant type messages.

  12. Ovidio Molina

    HI!
    Very useful your video!
    I’m new programming in Python and take a challenge of programming a multi player game. I implemented your code in my pc and works very good for more than one client. I got a Mac too, and implemented the client on it and I had to do some changes in server code. I had to use host=’0.0.0.0′ to get comunicaton with the mac. Althoug i succeded in comunications, they behave different on the mac. The incoming messages to the mac appears to be enqueued. You have to press enter to get them on the display. Any hint about this ..

  13. sol

    had the same problem change python to python3

  14. tundra

    Maybe just put a 1 in server.listen(1)

  15. Ansh Goel

    That’s,working pretty good thanks Neuralnine!

  16. Hioshi

    How do i bring it online? on my wifi that my friends can chat there and more?

  17. Thammineni Pushyamitra

    Hey!,

    On my system the chats are not syncing . What’s the problem?

  18. Rudra Nalawade

    It is a great project.
    I have admin, senior and junior added to this project.

  19. FreidMule

    Thank you so much for sharing!!
    Is it possible to get it to run on Python 3?
    I do get the following error:
    14 “stream = p.open(format=FORMAT,
    15 channels=CHANNELS,
    16 rate=RATE,
    17 input=True,
    18 frames_per_buffer=CHUNK,”

    Exception has occurred: OSError
    [Errno -9999] Unanticipated host error
    File “G:\Documents\Programmering\Instant-Messaging-master\send_voice.py”, line 18, in
    frames_per_buffer=CHUNK,
    File “G:\Documents\Programmering\Instant-Messaging-master\Client.py”, line 14, in
    from send_voice import send_voice
    File “G:\Documents\Programmering\Instant-Messaging-master\IM_client.py”, line 11, in
    from Client import Client

  20. Nim09911

    how do i remove/stop/kill the thread

  21. syed

    host = ‘127.0.0.1’_# localhost
    ^
    SyntaxError: invalid syntax

    can anyone help me with this please

  22. Aditya Soni

    When using it on two devices server sends blank messages if message sent from client

  23. musko

    how would i use this online? like from computer to computer

  24. M Benchaa

    Thank you for this code. I need to use this code in order to send notification to thounsands of my clients without discuss with them..
    Just send them message when i wish..
    How can i modify the client code?
    It’s possible?
    Need a help please

  25. Benchaa

    Thank you
    I need Gui version of this project please

  26. jack

    Hi, thanks for the article, very helpful and descriptive, I have an issue where I can’t send a message from any client. If type a message and press enter it just goes to the next line and does not appear in the terminal of the other connected client.
    I have reviewed my code and it seems fine, any suggestions on what could be causing it are welcome.
    Many thanks
    Jackjack

  27. codeme

    can I use this code and connect to another server and port?

  28. neuralninefan

    Hi NeuralNine , you forgot to attach ” handle function ” of server’s piece of code in this blog post .

  29. Kartik

    Yes plz make a GUI version of this plz….

  30. marc

    working just great and learning as we go thanks

  31. Jeff

    I got this error when used it on a old version of python try “server.listen(0)”

Leave a Reply