JOVANA
Explore Library Glossary Getting Started Three Levels Fields How it works Mission
Join the mission
All guides

Ports and Sockets: From Host to Process

The network layer gets a packet to the right machine. But a machine runs dozens of programs at once. This guide is about the last short hop: how the transport layer hands each arriving byte to exactly the right conversation, using port numbers and sockets.

The address gets you to the building, not the person

By now you can trace a packet across the world. The Internet Protocol reads the destination IP address and, hop by hop, delivers an IP datagram to the right machine. But here is the gap nobody mentioned yet: a single laptop is running a browser, a chat app, a music stream, and three background updaters all at the same time. The IP address gets the packet to the front door of the building. It says nothing about which apartment it is for.

Recall the apartment-number analogy from the early rungs. The street address (the IP address) names a location on the network; the apartment number names who, inside that building, the mail is for. The port number is that apartment number. The job of the transport layer is to take a generic stream of bytes that arrived at the machine and hand it to the one specific program that is waiting for it, and to do the reverse on the way out.

This is also where the famous end-to-end principle lives. The network in the middle (routers, switches, fiber) is kept deliberately dumb: it just forwards datagrams and makes no promises. All the smart, end-specific work, like figuring out which app gets the data and whether anything was lost, is pushed out to the two end hosts. The end-to-end principle is why the Internet could grow so fast: the core never had to be upgraded to understand any new application.

Multiplexing: many conversations, one wire

At any instant your machine may have many conversations open at once. The trick that lets them share a single network connection is multiplexing and demultiplexing. On the way out, the transport layer takes data from several programs, stamps each chunk with a source port and a destination port, and gathers them all into the stream of datagrams handed to IP. That gathering-up is multiplexing.

On the way in, the machine receives one undifferentiated firehose of datagrams. Demultiplexing is sorting that firehose back out: reading the port numbers in each segment and dropping each one into the correct program's bucket. The port number is the field that makes this possible. It is a 16-bit number, so there are 2^16 = 65536 possible ports per protocol, per IP address. That is the whole budget of distinct "apartments" one host can offer.

The socket: the program's handle on the network

A port number is just a label in a header. What a program actually holds in its hands is a socket. A socket is the doorway between an application and the transport layer: the program writes bytes into its end and reads bytes back out, and the operating system takes care of wrapping those bytes in transport headers and shipping them. To the program it feels almost like a file you can read and write, except the other side of it is a machine somewhere else on Earth.

Here is the subtle part, and it is worth slowing down for. A UDP socket is identified by just two things: the destination IP and the destination port. Every datagram aimed at that pair lands in the same socket, no matter who sent it. A TCP socket is identified by all four: source IP, source port, destination IP, destination port, the so-called four-tuple. That is why a busy web server can hold thousands of simultaneous connections all on TCP port 443: each client's unique source IP and source port make a distinct four-tuple, so each gets its own socket even though the server's side of every connection shares one port.

Server side (pseudocode)            Client side
--------------------------          ---------------------------
s = socket()                        c = socket()
bind(s, port = 443)                 connect(c, host="203.0.113.7",
listen(s)                                        port = 443)
conn = accept(s)   <----[ SYN ]----  send(c, request_bytes)
read(conn, ...)    ----[ data ]----> recv(c, reply_bytes)

The accepted 'conn' is a NEW socket, distinct per client,
keyed by the four-tuple (srcIP,srcPort,dstIP,dstPort).
A minimal TCP server/client skeleton. listen() opens the door on a port; each accept() returns a fresh socket dedicated to one client.

Two doorways, two promises: UDP and TCP

The transport layer offers your program a choice of doorway, and the choice is a real design decision, not a quality ranking. UDP is the thin one: it adds little more than the source and destination ports and a checksum to spot corruption, then hands your message to IP and forgets about it. It is connectionless and best-effort, meaning packets may arrive out of order, duplicated, or not at all, and UDP will not lift a finger to fix that. The next guide in this rung digs into exactly why that minimalism is sometimes the right tool.

TCP is the thick one. It first sets up a connection with a three-way handshake, then gives your program a reliable byte stream: bytes come out the far end in order, with nothing lost and nothing duplicated, as if you had a clean pipe straight to the other process. To pull that off over the same lossy IP that UDP rides on, TCP layers on reliable data transfer, flow control, and congestion control. Those mechanisms are the subject of the rest of this rung, so we will only name them here.

Tracing one arriving packet, end to end

Let us put it all together by following one packet the instant it lands on a server at IP 203.0.113.7. Watch how each layer peels off its own envelope and reads its own field, the encapsulation idea from the foundations rung running in reverse.

  1. The link layer hands up the frame's payload, and the network layer sees an IP datagram whose destination is 203.0.113.7. That is us, so it strips the IP header and passes the payload up to the transport layer.
  2. The transport layer reads the protocol field: this is TCP. It now looks at the four-tuple, source 198.51.100.5 port 51000, destination 203.0.113.7 port 443, to demultiplex.
  3. It searches its table of open sockets for that exact four-tuple and finds the one connection belonging to this client. (If no socket matched, TCP would refuse with a reset; for UDP it would send back an ICMP port-unreachable.)
  4. The byte payload is appended in order to that socket's receive buffer, and the waiting program's read() call wakes up with fresh data. The packet has reached not just the right host, but the right process.

Notice what the port and the socket did together. The port number was the small label that told the transport layer where to sort the bytes; the socket was the live object, owned by one program, that actually received them. That hand-off, from a location on the network down to a single running process, is the entire reason the transport layer exists. Everything else in this rung, reliability and flow control and the rest, is built on top of this one clean idea.