TCP and UDP Client–Server Programming
TCP
Sockets provide an abstraction between application and transport services, reducing complex protocol operations to interfaces used by communicating processes.
Originating in Unix's file-oriented model, sockets let connected peers write data for the other side to read and close resources when communication ends.
Run a TCP client and server step by step
Unlock the complete interactive lab, deeper explanations, and curated reference answers.
socket()
- Address families:
AF_INETfor IPv4 andAF_INET6for IPv6. - Socket types:
SOCK_STREAMfor a connection-oriented stream andSOCK_DGRAMfor datagrams.
bind()
Bind a socket to a local IP address and port. 127.0.0.1 is the IPv4 loopback address for local experiments. Port values range from 0 to 65535; 0–1023 is the well-known range. Binding port 0 normally asks the operating system to choose an available ephemeral port rather than identifying a fixed service.
listen()
Start listening and queue incoming connections.
accept()
Obtain a queued connection; a blocking accept waits if none is available. Python returns (connected_socket, peer_address). The listening socket remains separate from the socket used for actual communication.
read() / write()
Communication is bidirectional. Python examples use recv() and send() for reading and writing. send() can accept only part of the data; sendall() attempts all bytes or reports an error, without proving that the peer has processed them.
close()
Close the socket and release its resources. UDP has no TCP-style connection or teardown handshake.
A Local TCP Echo
Start the server first, then the client on the same machine. Use short ASCII messages and enter exit to stop. Port 8080 must be available.
Server
import socket
SERVER_HOST = "127.0.0.1"
SERVER_PORT = 8080
def main():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind((SERVER_HOST, SERVER_PORT))
sock.listen()
print("[Server] Server started.")
print("[Server] Waiting for connection...")
# Get client's socket and address
client, client_addr = sock.accept()
print("[Server] Client%s:%s Connected to server." % client_addr)
while True:
data = client.recv(100).decode("UTF8")
print("[Server] Received: %s" % data)
if data == "exit":
client.send("exit".encode("UTF8"))
break
else:
client.send(data.encode("UTF8"))
sock.close()
if __name__ == "__main__":
main()Client
import socket
SERVER_HOST = "127.0.0.1"
SERVER_PORT = 8080
def main():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((SERVER_HOST, SERVER_PORT))
while True:
msg = input("[Client] Enter data: ")
sock.send(msg.encode("UTF8"))
reply = sock.recv(100).decode("UTF8")
if reply == "exit":
break
else:
print("[Server] " + reply)
sock.close()
if __name__ == "__main__":
main()This compact example demonstrates API order, not a complete message protocol. TCP is a byte stream: one send need not correspond to one receive. recv(100) reads at most 100 bytes, not necessarily all 100. Content can be split or combined, including UTF-8 characters. Text messages need framing rules such as lengths or delimiters.
recv() returning b"" indicates end of the peer's sending stream, not a new empty message. Programs should also explicitly close connected and listening sockets, use appropriate timeouts, and handle exceptions. Those boundaries are not fully implemented in these small examples.
KNOWLEDGE QUIZ
UDP
UDP makes a best-effort delivery attempt without built-in retransmission of lost datagrams. TCP reliability comes from several mechanisms. Neither protocol is universally better; suitability depends on the application.
File transfer generally needs all data, favoring a reliable transport. Real-time calls may tolerate some loss to reduce delay and often use UDP-based designs. The application controls sending rate; UDP itself does not guarantee a constant rate. Applications can add reliability above UDP and still need congestion control and security.
A Local UDP Echo
Server
import socket
SERVER_HOST = "127.0.0.1"
SERVER_PORT = 8080
def main():
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((SERVER_HOST, SERVER_PORT))
print("[Server] Server started.")
print("[Server] Waiting for connection...")
while True:
data, client_addr = sock.recvfrom(100)
print("[Server] Client%s:%s: " % client_addr, end=":")
data = data.decode("UTF8")
print(data)
if data == "exit":
sock.sendto("exit".encode("UTF8"), client_addr)
break
else:
sock.sendto(data.encode("UTF8"), client_addr)
sock.close()
if __name__ == "__main__":
main()Client
import socket
SERVER_HOST = "127.0.0.1"
SERVER_PORT = 8080
def main():
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while True:
msg = input("[Client] Enter data: ")
sock.sendto(msg.encode("UTF8"), (SERVER_HOST, SERVER_PORT))
reply, addr = sock.recvfrom(100)
reply = reply.decode("UTF8")
if reply == "exit":
break
else:
print("[Server] " + reply)
sock.close()
if __name__ == "__main__":
main()recvfrom() returns data and the sender's address; sendto() needs a destination. Every server reply, including exit, goes to client_addr. The printed “Waiting for connection” here means waiting for a datagram, not a UDP handshake.
UDP preserves datagram boundaries, but a small receive buffer can truncate data. These examples receive at most 100 bytes and are intended for short messages. A lost request or reply can leave a blocking receive waiting indefinitely; real applications need timeouts and suitable recovery.
KNOWLEDGE QUIZ
Build a TCP uppercase service
Write one Python script that runs a small TCP service on your own machine and talks to it. Unlike the echo example, the server replies with the message in UPPERCASE plus its size in bytes, and messages end with a newline.
- Server: create a TCP socket, bind it to 127.0.0.1 with port 0 (let the OS pick a free port), listen(), and run the accept/recv/reply part in a thread.
- Client: connect to that port and send "hello, leaflet" followed by \n using sendall().
- Server: keep calling recv() until it has a complete line (do not assume one recv() returns the whole message, and stop if recv() returns b'').
- Server: reply with "<MESSAGE IN UPPERCASE> (<n> bytes)\n", where n is the UTF-8 byte length of the message.
- Client: read the full reply line, print it, and make sure every socket is closed and the script exits by itself.
Expected output: HELLO, LEAFLET (14 bytes). Then try "你好": the reply should report 6 bytes, not 2.