Two short Python programs put the whole chapter on the wire: a socket, a destination address attached to every datagram, and two packets that are the entire conversation.
Words you will meet
- Socket — the door of a program, through which its messages leave and enter.
- Port number — the number that says which program on a host a message is for.
- SOCK_DGRAM — the argument that makes a socket a UDP (User Datagram Protocol) User Datagram Protocol A simple transport protocol with no reliability, no flow control and no congestion control. introduced in ch. 1 socket.
sendto()— send bytes, with the destination address attached to this call.recvfrom()— receive bytes, and the address they came from.- Ephemeral port — a port the operating system picks for a client, unasked.
Why this matters
Everything in this chapter so far has been described. This is the first section where you can run it.
The programs are deliberately tiny — the book calls them intentionally minimal, without even the error handling good code would have. That is the point. Nine lines of client are enough to show where the application layer stops and the operating system takes over. That is the boundary section 2.1.2 drew, made concrete.
Two kinds of network application
Before any code, one distinction. There are two kinds of network application, and they differ in who gets to decide the rules.
Open applications have their operation specified in a protocol standard, such as an RFC (Request For Comments) Request For Comments The name of an IETF standards document. There are currently nearly 9000 of them. introduced in ch. 1 . The rules are known to everybody. If one developer writes the client and another writes the server, and both follow the RFC carefully, the two programs interoperate. That is why a Chrome browser can talk to an Apache web server, and a BitTorrent client to a BitTorrent tracker, with neither team having met.
Proprietary applications use an application-layer protocol that has not been published. One developer or team writes both sides and has complete control over the code. Nobody else can write something that interoperates with it.
One rule about ports, in two directions
If you implement a protocol defined by an RFC, use the well-known port number associated with it — 80 for HTTP (HyperText Transfer Protocol) HyperText Transfer Protocol The application-layer protocol that requests and transfers Web documents. introduced in ch. 1 , 25 for SMTP (Simple Mail Transfer Protocol) Simple Mail Transfer Protocol The application-layer protocol that transfers e-mail messages. introduced in ch. 1 , 53 for DNS (Domain Name System) Domain Name System Translates a human-friendly name such as www.ietf.org into a network address. introduced in ch. 1 .
If you are developing a proprietary application, be careful to avoid those numbers. The programs below use 12000, chosen precisely because nothing well known claims it.
The book uses Python 3 for these examples. The code could have been Java, C or C++; Python was chosen because it exposes the key socket concepts with fewer lines, and each line can be explained without difficulty.
How a UDP socket is used
Recall the analogy from section 2.1.2: a process process Simple A program that is currently running inside an end system. Precise A program that is running within an end system. Processes on the same end system communicate using interprocess communication governed by the operating system; processes on different end systems communicate by exchanging messages across the computer network. introduced in ch. 2 — open in glossary is a house and its socket socket Simple The door of a program, through which its messages leave and enter the network. Precise The software interface between the application layer and the transport layer within a host, through which a process sends messages into, and receives messages from, the network. It is the concrete thing a program holds; the socket interface of §1.1 is the service it offers. It is also called the Application Programming Interface between the application and the network. The developer controls everything on the application side of the socket and almost nothing on the transport side. introduced in ch. 2 — open in glossary is the door. With UDP (User Datagram Protocol) User Datagram Protocol A simple transport protocol with no reliability, no flow control and no congestion control. introduced in ch. 1 there is one extra step before pushing anything out of that door.
The UDP-specific rule
Before the sending process can push a packet out the socket door, it must first attach a destination address to the packet.
That address has two parts, exactly as section 2.1.2 said. The destination host’s IP (Internet Protocol) Internet Protocol The network-layer protocol that defines the datagram format and addressing every Internet device must use. introduced in ch. 1 address lets routers carry it there. The destination socket’s port number port number Simple A number that says which program on a host a message is meant for. Precise An identifier assigned to a socket which, together with the host’s IP address, specifies the receiving process in the destination host. Popular applications have assigned well-known port numbers: a Web server is port 80, an SMTP mail server port 25, a DNS server port 53. The full list is published by IANA. introduced in ch. 2 — open in glossary tells the receiving host which process to give it to.
The sender’s source address — its own IP address and port number — is attached too. But notice who does it:
attaching the source address to the packet is typically not done by the UDP application code; instead it is automatically done by the underlying operating system.
That sentence is easy to read past. Keep it: it explains something you will see in the capture below that appears nowhere in the source code.
The application
The same tiny application demonstrates both UDP and TCP (Transmission Control Protocol) Transmission Control Protocol The Internet transport protocol that delivers data reliably and in order, with flow control and congestion control. introduced in ch. 1 sockets:
- The client reads a line of characters from its keyboard and sends it to the server.
- The server receives the data and converts the characters to upper case.
- The server sends the modified data back to the client.
- The client receives it and displays the line on its screen.
UDPClient.py
The whole client
from socket import *
serverName = 'hostname'
serverPort = 12000
clientSocket = socket(AF_INET, SOCK_DGRAM)
message = input('Input lowercase sentence:')
clientSocket.sendto(message.encode(), (serverName, serverPort))
modifiedMessage, serverAddress = clientSocket.recvfrom(2048)
print(modifiedMessage.decode())
clientSocket.close()| Line | What it does |
|---|---|
from socket import * | The socket module is the basis of all network communication in Python. This line is what lets the program create sockets at all. |
serverName = 'hostname' | Either the server’s IP address, such as "128.138.32.126", or its hostname, such as "cis.poly.edu". If a hostname is used, a DNS lookup is performed automatically — section 2.4 happening quietly inside one assignment. |
serverPort = 12000 | The port the server will be listening on. |
socket(AF_INET, SOCK_DGRAM) | AF_INET says the underlying network is IPv4 (Internet Protocol version 4) Internet Protocol version 4 The Internet Protocol with 32-bit addresses and a variable-length header.
introduced in ch. 4 . SOCK_DGRAM says this is a UDP socket rather than a TCP one. Note what is not here: the client’s own port number. The operating system chooses it. |
input(...) | Prompts the user and puts the typed line into message. |
sendto(message.encode(), (serverName, serverPort)) | encode() converts the string to bytes, because bytes are what sockets carry. sendto() attaches the destination address to the message and pushes it into the socket. |
recvfrom(2048) | Blocks until a datagram arrives. The data goes into modifiedMessage and the packet’s source address into serverAddress. The buffer size 2048 works for most purposes. |
print(modifiedMessage.decode()) | Back from bytes to a string, and onto the screen. |
clientSocket.close() | Closes the socket. The process then terminates. |
In plain words — a line the client does not need
recvfrom() gives back the server’s address as well as the data, and
UDPClient does not actually need it: it knew the server’s address from the
outset.
The line is there because that is simply what recvfrom() returns. Keep it in
mind for the server, where the same return value is the only reason a reply can
be sent at all.
UDPServer.py
The whole server
from socket import *
serverPort = 12000
serverSocket = socket(AF_INET, SOCK_DGRAM)
serverSocket.bind(('', serverPort))
print("The server is ready to receive")
while True:
message, clientAddress = serverSocket.recvfrom(2048)
modifiedMessage = message.decode().upper()
serverSocket.sendto(modifiedMessage.encode(), clientAddress)The beginning is similar to the client: the same module, the same port number,
the same SOCK_DGRAM socket type. Three lines differ, and each one matters.
| Line | What is different, and why |
|---|---|
serverSocket.bind(('', serverPort)) | Binds — assigns — port 12000 to the server’s socket. Here the application developer explicitly chooses the port, where the client left it to the operating system. From now on, any packet sent to port 12000 at this host is directed to this socket. |
while True: | The loop lets the server receive and process packets from clients indefinitely. Without it the server would answer one datagram and exit. |
message, clientAddress = recvfrom(2048) | Same call as in the client, but here the second value is used. clientAddress holds the client’s IP address and port number. It is a return address, exactly like the one on ordinary postal mail — and it is how the server knows where to send its reply. |
message.decode().upper() | The heart of the application: convert to a string, and capitalise it. |
sendto(modifiedMessage.encode(), clientAddress) | Attaches the client’s address to the capitalised message and pushes it into the socket. Then the loop returns to recvfrom(), waiting for another datagram from any client on any host. |
It creates a UDP socket and binds port 12000 to it. From now on, any datagram sent to port 12000 at this host is directed to this socket. Then it blocks in recvfrom(), waiting.
Step forward. The left column is UDPServer.py, the right is UDPClient.py, and the middle is what exists on the network at that moment.
Read all steps as text
- The server must be running first — It creates a UDP socket and binds port 12000 to it. From now on, any datagram sent to port 12000 at this host is directed to this socket. Then it blocks in recvfrom(), waiting.
- The client creates its socket — and names no port — socket(AF_INET, SOCK_DGRAM) again. Notice what is missing: the client never says which port it will use. The operating system picks one from the ephemeral range, and the code never learns what it was.
- sendto() attaches the destination and pushes it out — The client reads a line from the keyboard, encodes it to bytes, and calls sendto() with the destination address — hostname and port together. With UDP the destination must be attached to EVERY datagram, because no connection remembers it.
- recvfrom() returns the data AND the sender — The server wakes with two things: the message, and clientAddress — the client’s IP address and port. That second value is the whole reason a reply is possible, and it came from the header the operating system filled in.
- The work, and the reply — decode() to a string, .upper() to shout it, encode() back to bytes, and sendto() using clientAddress as the destination. Then the server loops back to recvfrom() and waits for the next datagram — from anybody.
Three things that are absent
Look at what these programs never do.
- No connection is established. There is no handshake, no setup, no teardown. The first packet already carries data.
- The client never chooses its port, yet the reply reaches it. The operating system picked one and put it in the header.
- The server never closes anything. There is no per-client connection to close, and it must stay ready for whoever sends next.
All three follow from one fact: UDP provides a connectionless service connectionless service Simple Data is sent with no greeting first, and with no promise that it will arrive. Precise The service provided by UDP. There is no handshaking before the two processes start to communicate, and no guarantee that a message will ever reach the receiving process. Messages that do arrive may arrive out of order. introduced in ch. 2 — open in glossary . Section 2.7.2 writes the same application over TCP, and all three change.
What actually went on the wire
The programs above produce exactly two packets. Here they are.
| No. | Time | Source | Destination | Protocol | Length | Info |
|---|---|---|---|---|---|---|
| 1 | 0.000000 | 192.168.1.24 | 198.51.100.7 | UDP | 60 | 51873 → 12000 Len=18 |
| 2 | 0.091200 | 198.51.100.7 | 192.168.1.24 | UDP | 60 | 12000 → 51873 Len=18 |
Packet 1 — One datagram out. No connection was established first — this is the first packet either side has sent, and it already carries application data. Compare the three-way handshake in the chapter 1 capture.
Protocol tree — click a field
The actual bytes
0000 aa bb cc 00 00 01 aa bb cc 00 00 11 08 00 45 00 ..............E.0010 00 2e 4f 21 40 00 40 11 ff a2 c0 a8 01 18 c6 33 ..O!@.@........30020 64 07 ca a1 2e e0 00 1a a7 c3 68 65 6c 6c 6f 20 d.........hello0030 66 72 6f 6d 20 62 61 6e 67 6b 6f 6b from bangkok
A real capture of the book’s example, generated with correct checksums. Click a field to highlight its bytes. The whole conversation is two packets.
In plain words — read the source port field
Select the source port of the first datagram. That number appears nowhere in
UDPClient.py. The program says only socket(AF_INET, SOCK_DGRAM).
The operating system chose it, wrote it into the header, and the server read it
back out of recvfrom() as clientAddress. It is the reason the second packet
knows where to go. It is also the clearest possible illustration of where your
control stops — the boundary section 2.1.2 drew with the
socket diagram.
Note also the protocol field in the IP header: 17, not 6. That single byte is how the receiving host knows to hand the payload to UDP rather than TCP.
Running them yourself
Run UDPServer.py on one host and UDPClient.py on another, with the server’s
hostname or IP address filled in. Start the server first — it must be running as
a process before the client sends anything, because there is no connection
attempt that would fail loudly.
Then type a sentence and press return.
To develop your own application, start by modifying these. Instead of converting
to upper case, the server could count how many times the letter s appears and
return that number. Or the client could keep sending further sentences after
receiving each reply.
Where this breaks: if the server is not running, the client simply blocks in recvfrom() forever. Nothing tells it that nobody was listening — which is UDP’s unreliability arriving in the least dramatic way possible. A TCP client in the same situation fails immediately, because the connection attempt is refused.
Check yourself
Check yourself
0 of 6 answered1.`clientSocket = socket(AF_INET, SOCK_DGRAM)` — what do the two arguments say?
2.The client never says which port it is using. So how does the server's reply reach it?
3.predictOpen the capture and select the source port field of the first datagram. What do you learn that the source code does not tell you?
4.Why does the client call `message.encode()` before sending, and `.decode()` after receiving?
5.The server has no `close()` and never leaves its `while True` loop. Is that a bug?
6.The whole exchange in the capture is two packets. What would the same exchange over TCP have needed first?
What to remember
socket(AF_INET, SOCK_DGRAM)— IPv4, and UDP.SOCK_STREAMwould make it TCP, and that one word is the whole choice.- With UDP the sending process must attach a destination address to every datagram: destination IP address and destination port. The operating system attaches the source address, not your code.
sendto(bytes, address)sends.recvfrom(size)returns the data and the sender’s address — and that second value is what makes a reply possible.