§2.7.1Socket Programming with UDP

Application layer Kurose & Ross pp. 152–158 · ~14 min read

  • socket
  • port number
  • connectionless service
  • client process
  • server process

Where you are

  • Application layer you are here
  • Transport layer
  • Network layer
  • Link layer
  • Physical layer

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) 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) . 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) , 25 for SMTP (Simple Mail Transfer Protocol) , 53 for DNS (Domain Name System) .

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 is a house and its socket is the door. With UDP (User Datagram Protocol) 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) address lets routers carry it there. The destination socket’s port number 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) sockets:

  1. The client reads a line of characters from its keyboard and sends it to the server.
  2. The server receives the data and converts the characters to upper case.
  3. The server sends the modified data back to the client.
  4. The client receives it and displays the line on its screen.
Server(running on serverIP)ClientCreate socket, port=x:serverSocket = socket(AF_INET,SOCK_DGRAM)Create socket:clientSocket = socket(AF_INET,SOCK_DGRAM)Create datagram with serverIPand port=x;send datagram via clientSocketRead UDP segment fromserverSocketWrite reply to serverSocketspecifying client address,port numberRead datagram fromclientSocketClose clientSocketFigure 2.27 — and note the serverhas no Close box at all.

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()
LineWhat 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 = 12000The port the server will be listening on.
socket(AF_INET, SOCK_DGRAM)AF_INET says the underlying network is IPv4 (Internet Protocol version 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.

LineWhat 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.
The two programs, running side by side
The server must be running firststep 1 of 5
UDPServer.pyUDPClient.pysocket(AF_INET, SOCK_DGRAM)bind(('', 12000))recvfrom(2048) — waitsnothing on the network yet

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
  1. The server must be running firstIt 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.
  2. The client creates its socket — and names no portsocket(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.
  3. sendto() attaches the destination and pushes it outThe 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.
  4. recvfrom() returns the data AND the senderThe 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.
  5. The work, and the replydecode() 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.

  1. No connection is established. There is no handshake, no setup, no teardown. The first packet already carries data.
  2. The client never chooses its port, yet the reply reaches it. The operating system picked one and put it in the header.
  3. 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 . 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.

The two datagrams UDPClient.py and UDPServer.py actually send
No.TimeSourceDestinationProtocolLengthInfo
10.000000192.168.1.24198.51.100.7UDP6051873 → 12000 Len=18
20.091200198.51.100.7192.168.1.24UDP6012000 → 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!@.@........3
0020 64 07 ca a1 2e e0 00 1a a7 c3 68 65 6c 6c 6f 20 d.........hello
0030 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 answered
  1. 1.`clientSocket = socket(AF_INET, SOCK_DGRAM)` — what do the two arguments say?

  2. 2.The client never says which port it is using. So how does the server's reply reach it?

  3. 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. 4.Why does the client call `message.encode()` before sending, and `.decode()` after receiving?

  5. 5.The server has no `close()` and never leaves its `while True` loop. Is that a bug?

  6. 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_STREAM would 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.