Building a Simple Chat Application
Open any group chat on your phone right now and type "Hi" and hit send. Within a second, that word shows up on your friend's phone in Chennai, your cousin's tablet in Mumbai, and your own screen — all at once, even though none of those devices are physically connected to each other by a wire. Nobody's phone knows where anybody else's phone is sitting. There is no direct line between your device and theirs. Yet the message arrives, in order, without getting mixed up with anyone else's messages. That should feel a little strange once you stop and think about it. This chapter takes that everyday "magic" apart piece by piece and rebuilds it — you will learn exactly what has to be true for two computers to hold a conversation, and by the end you will read (and understand, line by line) real working code for a chat program.
Why can't two computers just "talk"?
Imagine you want to pass a paper note to a classmate sitting on the other side of the school, in a different building, whom you have never met. You cannot just say the words out loud — the message needs a destination address, and someone in between needs to carry it there. Two computers trying to exchange a chat message have exactly this problem, except worse: there could be millions of computers on the internet, most of them switched off or busy, and your friend's phone might be connected through a mobile tower in one city while yours is on a Wi-Fi router in another. For your one word "Hi" to reach the right device, three separate problems must be solved:
- Addressing — which exact device, out of billions, should receive this?
- Reachability — how does data actually get carried across networks it doesn't own?
- Agreement — both devices must understand the message the same way; if I send text and you expect a picture, we have failed even if the data arrived perfectly.
A chat application is really just a small, careful set of answers to these three problems. Let's solve them one at a time, the same way you would design the app yourself.
The relay idea: why almost no chat app connects you directly to your friend
Here is a real Indian example that solves the same kind of problem chat apps face. Mumbai's dabbawalas deliver over a lakh home-cooked lunchboxes to office workers every single day. A dabbawala who picks up a box in a suburb does not personally carry it across the city to a specific office tower — that would need thousands of individual couriers crossing paths constantly. Instead, every box is carried to a central sorting point, coded and sorted there, and handed off to a different dabbawala who covers the delivery zone. The sender and the final receiver never need to know each other's exact location; they only need to know the address of the sorting point and trust the relay system to do the rest.
Chat applications work the same way. Instead of your phone connecting directly to your friend's phone, both of you connect to a computer that is always switched on, always reachable, and has a fixed, known address — the server. Your phone (a client) sends the message to the server; the server looks at who it is for and forwards it on to the right client(s). This is called the client-server model, and it is the architecture behind almost every chat, email, and messaging system you use.
There is a good technical reason direct phone-to-phone connections usually will not work, beyond convenience: most phones and home Wi-Fi routers sit behind what is called network address translation, which hides your device's true address from the rest of the internet and only lets you initiate outgoing connections, not receive incoming ones out of nowhere. A server, by contrast, is deliberately set up with a fixed, public address that anyone is allowed to connect to. It also solves a problem the dabbawala system does not have to: if your friend's phone is switched off, the server can hold the message and deliver it the moment they reconnect — this is called store-and-forward delivery.
Addresses: IP address and port number
A postal address alone is not always enough — a large office building has one street address but many separate desks inside it. Computers have the same layering. The IP address identifies one particular device on a network (something like 203.0.113.5 for a server on the internet, or 127.0.0.1, a special address every computer uses to mean "myself," useful for testing a client and server on the same machine). But a single device usually runs many programs that all want to use the network at once — a browser, a music app, and your chat client could all be running together. If a message just said "deliver to 203.0.113.5," which program on that machine should receive it?
The second part of the address is the port number, a number attached to a specific program's connection point on that device. Port numbers are stored using 16 bits of information. Since 16 bits can represent 216 different values, and 216 = 65,536, valid port numbers run from 0 to 65,535. Numbers below 1024 are reserved for well-known services set by convention (for example, web servers traditionally listen on port 80 or 443), so when we build our own chat program, we pick an unused number above that range — this chapter uses port 5000. The full address a client needs to reach a specific chat server program is therefore always a pair: (IP address, port number) — for example, (203.0.113.5, 5000). This pair is called a socket address, and in code, it is literally what you pass in to open a connection.
Sockets: the actual connection endpoint
A socket is the programming object that represents one end of a network connection — think of it as a telephone handset. Creating a socket does not connect you to anyone by itself, just like picking up a handset does not connect you to a friend; you still have to dial their number. A server socket does something extra: it listens at a fixed socket address, patiently waiting for someone to connect to it, the way a school reception desk waits for visitors and does not go looking for them. A client socket does the opposite: it actively dials out to a server's known socket address to start a conversation.
Writing the actual program: a working one-to-one chat
Let's build the smallest version of this that actually works, using Python's built-in socket module (Python comes with networking support already built in — nothing extra to install). This program has two files: one for the server, one for the client. Read them fully before running either.
server.py — this runs first and waits:
import socket
HOST = "127.0.0.1"
PORT = 5000
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind((HOST, PORT))
server_socket.listen(1)
print("Chat server started. Waiting for a friend to connect...")
connection, address = server_socket.accept()
print("Connected by", address)
while True:
data = connection.recv(1024)
if not data:
break
text = data.decode("utf-8")
print("Friend says:", text)
reply = "Server got: " + text
connection.send(reply.encode("utf-8"))
connection.close()
client.py — this runs second and speaks:
import socket
HOST = "127.0.0.1"
PORT = 5000
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect((HOST, PORT))
client_socket.send("Hi Server!".encode("utf-8"))
reply = client_socket.recv(1024)
print("Server replied:", reply.decode("utf-8"))
client_socket.close()
Now trace exactly what happens when both run, in order:
server_socket.bind((HOST, PORT))claims socket address(127.0.0.1, 5000)for this program, so no other program on the machine can also claim it.server_socket.listen(1)puts the socket into waiting mode. The lineprint("Chat server started...")runs, thenaccept()blocks — the program pauses here until someone connects.- When
client.pyruns,client_socket.connect((HOST, PORT))dials the server's exact socket address. The server'saccept()call unblocks, returning a new socket calledconnectiondedicated to this one conversation, plusaddress, the client's own socket address (something like('127.0.0.1', 54821)— the exact port varies each run, chosen automatically). The server printsConnected by ('127.0.0.1', 54821). - The client calls
client_socket.send("Hi Server!".encode("utf-8")). This converts the text into raw bytes and hands them to the network. - The server's
connection.recv(1024)— which means "read up to 1024 bytes" — returns those bytes.data.decode("utf-8")turns them back into the string"Hi Server!". The server printsFriend says: Hi Server!, buildsreply = "Server got: Hi Server!", and sends it back. - The client's
client_socket.recv(1024)receives those reply bytes and printsServer replied: Server got: Hi Server!. - The client calls
client_socket.close(), ending its side of the conversation. - Back on the server, the
while Trueloop callsconnection.recv(1024)again. Since the client closed its socket, this call now returns an empty byte stringb"", which is treated as false byif not data:, so the loop breaks and the server also closes its connection.
Every single line has now been accounted for, and the output matches exactly what the code says it will do — nothing about this is guesswork. Notice this program only handles one conversation and then stops; a real chat app needs to keep listening for new connections and juggle several at once, which we will get to shortly.
The hidden problem: TCP does not know where your message ends
Here is a detail that trips up almost every beginner, and it is worth naming directly as a misconception: it is not true that one send() call always matches up neatly with one recv() call on the other end. The connection type used above, SOCK_STREAM (which runs on the TCP protocol), guarantees that bytes arrive in the correct order and without loss, but it treats all the data as one continuous stream — it does not preserve where one message stopped and the next one started. If your chat program quickly sends two separate messages, say "HELLO" and then "BYE", without anything marking the boundary between them, the receiving computer's single recv() call might return them stuck together as "HELLOBYE". The receiving program would have no way to know whether that meant one message ("HELLOBYE"), or two ("HELLO" and "BYE"), or even three.
This is a real design problem, and every chat protocol must solve it by adding a way to mark message boundaries — this is called framing. Two common techniques:
- Delimiter framing: place a special character, such as a newline
\n, after every complete message, and have the receiver keep reading bytes until it sees that character. This is simple but breaks if the message itself could ever contain that character. - Length-prefix framing: before sending the message text, send a fixed-size field stating exactly how many characters (or bytes) are coming next, so the receiver knows precisely when to stop reading.
Let's work through length-prefix framing numerically, because it is the more robust of the two and used in most real protocols. Suppose we agree that every message is preceded by its length written as exactly 5 digits. Message 1 is "HELLO", which has 5 characters, so its length field is 00005. Message 2 is "BYE", which has 3 characters, so its length field is 00003. Sent back to back with no gap, the raw bytes on the wire look like this:
00005HELLO00003BYE
The receiver's job is now a simple, mechanical parsing loop: read exactly 5 characters and interpret them as a number — 00005 parses as the integer 5. Then read exactly that many characters as the message — the next 5 characters are HELLO, so message 1 is recovered correctly. Repeat: read the next 5 characters, 00003, parses as 3; read the next 3 characters, BYE — message 2 recovered correctly. No character was ever misread, because the length told the receiver exactly how far to look, regardless of how the operating system chose to chop the data into chunks along the way. (Real protocols usually store this length as a small binary number rather than 5 text digits, to save space, but the arithmetic idea — read a length, then read exactly that many bytes — is identical.)
Scaling up: one server, many clients
The server program written earlier accepts exactly one connection and then quits — useless for a group chat. A real chat server needs to do two more things: accept many client connections at once (not just one), and when a message arrives from any one client, forward — or broadcast — it to every other connected client. The core relay logic looks like this, assuming the server already keeps a list of every client socket it is currently talking to:
clients = [] # every currently connected client socket lives here
def broadcast(message, sender_socket):
for client_socket in clients:
if client_socket != sender_socket:
client_socket.send(message)
Every time any client's message arrives, the server calls broadcast, which loops through its list of connected clients and forwards a copy of the message to each one — except back to whoever sent it, since they already have their own message on screen. If 4 students are in a group chat and one of them sends a message, the server does not send 1 message — it sends 3 copies out, one to each of the other members. This is the arithmetic behind why a group chat server's outgoing traffic grows with the number of members: for a group of n people, each message received generates n − 1 messages sent back out. A class group of 40 students means a single "good morning" from one student turns into 39 separate deliveries from the server — this is exactly why chat servers, not phones, do the heavy lifting.
Handling many clients "at once" also needs one more idea that this chapter only names, since implementing it needs tools beyond Grade 8 scope: the server cannot sit and wait on accept() or recv() for one client while ignoring everyone else, so real servers either use separate threads per client or a single loop that checks all sockets for new data using tools such as select. What matters for you right now is the concept: one always-on server, a list of connected sockets, and a broadcast loop — everything else is an engineering detail on top of that idea.
Putting it all together: what a message actually goes through
Trace one message all the way from your finger tapping "send" to it appearing on a friend's screen, using everything covered above. First, your chat app already holds an open socket connected to the server's socket address, (IP, port) — this connection was set up once when you opened the app, not freshly for every message. Second, your app takes the text you typed and frames it — wrapping it with a length prefix or delimiter so the receiver can tell where it ends. Third, it calls send(), handing the framed bytes to the operating system, which carries them across however many networks separate your phone from the server (your home Wi-Fi, your internet provider's network, and the server's data centre network). Fourth, the server's recv() picks up the bytes, un-frames them back into the original text, looks up which clients belong to the group, and calls something like broadcast() to hand a copy to every other member's open socket. Fifth, each of those clients' own recv() calls pick up the bytes, un-frame them, and the app displays the text on screen. Every step in that chain is something you have now seen in actual code or worked arithmetic — there is no remaining "magic" left in "how does my message reach five friends at once."
Summary
A chat application is not one clever trick but several small, precise agreements stacked on top of each other. Devices are located using a socket address — an IP address naming the device plus a 16-bit port number (0 to 65,535) naming the specific program on it. Most chat systems route every message through an always-on server rather than connecting phones directly, both because home devices are usually unreachable from outside and because a server can hold messages for someone who is temporarily offline. A socket is the programming endpoint used to either listen for connections (server) or dial out to one (client), and the send()/recv() calls shown in working Python code move raw bytes between them. Because the underlying TCP connection delivers a continuous stream of bytes with no built-in message boundaries, every real protocol must add framing — marking, by a delimiter or by a length prefix, exactly where one message ends and the next begins. Scaling from a two-person chat to a group chat only adds one new idea: the server keeps a list of connected sockets and broadcasts each incoming message to every other member, which is why a message to a group of n people costs the server n − 1 outgoing sends, not one.
Check your understanding
- A port number is stored using 16 bits. Show the arithmetic for why valid port numbers run from 0 to 65,535, and explain in one sentence why a chat server should avoid picking a port number below 1024.
- In the one-to-one Python program in this chapter, after the client calls
client_socket.close(), what value doesconnection.recv(1024)return on the server, and why does that specific value cause the server'swhileloop to end? - A sender transmits two messages,
"NO"and"WAIT", back to back, using 5-digit length-prefix framing (so lengths are written as exactly 5 digits). Write out the exact byte stream the receiver would see, and then show the two-step reading process the receiver uses to recover both original messages correctly. - A school group chat has 25 members. One student sends a single message. Using the broadcast idea from this chapter, how many separate outgoing sends does the server perform, and why is that number not 25?
- Explain, in your own words, why "the server relays every message" is a more accurate description of how a chat app works than "your phone connects directly to your friend's phone" — name at least one concrete reason a direct phone-to-phone connection usually is not possible.
Practice Exercises
Now it is time to practice! Complete these challenges to solidify your understanding:
- Exercise 1: Write a short program that demonstrates the core concept from this chapter. Test it with at least 3 different inputs.
- Exercise 2: Find a real-world example where building a simple chat application is used in an Indian company (like TCS, Infosys, Flipkart, or ISRO). Write a paragraph explaining the connection.
- Exercise 3: Create a mind-map connecting building a simple chat application to at least 3 other topics you have studied.