Unit 1 · Chapter 1 · Book pp. 3–30
Introduction
This chapter answers the only question that matters before any of the API makes sense: what is the thing we are programming? By the end of it you will have read a complete TCP client and a complete TCP server, line by line, and you will know where in the stack your code actually sits.
What network programming actually is
Every program you have written so far talked to things inside one machine — a file, a keyboard, a screen, some memory. Network programming is writing programs that talk to a process on a different machine, across a network you do not control, using rules both sides agreed on in advance.
- Network
- An interconnection of devices, called nodes or hosts.
- Protocol
- The agreement — who speaks first, what the bytes mean, what counts as an error, how the conversation ends. HTTP, SMTP, FTP and Telnet are protocols.
- Network programming
- Writing the code that makes two nodes hold such a conversation. In this course, that code is written against the sockets API.
What people actually build with it
- Exchange mail — Gmail, the college mail server.
- Move files between systems — upload and download, FTP, cloud sync.
- Share peripherals — a network printer, a shared drive.
- Run a program on another computer — every web API you have called.
- Log in remotely — SSH, remote desktop, working from home.
The client/server model
Almost every network application splits into two unequal halves. This asymmetry is the single most important idea in the chapter, and it is the reason the API has two different sets of functions.
The server
Starts first. Initialises itself, announces where it can be reached, then goes to sleep waiting to be contacted. It does not know who will call or when. When a client arrives, it performs some service and — in the general case — goes back to waiting.
The client
Starts whenever a person or program wants something. It must already know where the server is. It initiates contact, asks for a service, uses the answer, and typically goes away.
Typical services a client asks for:
- Tell me the time of day.
- Print this file.
- Read or write a file on your system.
- Let me log in to your system.
- Run this command for me.
fork) and Chapter 6 (select).
§1.1 · p.3
Two processes, four layers, one wire
The client and the server appear to talk to each other directly. They do not. Data goes down the protocol stack on one machine, across the physical network, and back up the stack on the other. Only the top layer — your program — gets the illusion of a direct line.
A first program: the daytime client
This is a complete, working TCP client in 27 lines. It connects to a daytime server — a standard service on port 13 that replies with the current date and time as human-readable text and then closes the connection. Step through it; the diagram underneath shows the connection coming into existence as you go.
Why this client is not good enough
The program above works only with IPv4, because AF_INET,
struct sockaddr_in and inet_pton(AF_INET, …) are all
hard-coded. Making it work with IPv6 means changing the structure, the family
constant and the conversion call. The book calls the better answer
protocol independence — writing clients and servers that do not
name a protocol at all — and reaches it in Chapter 11 with
getaddrinfo.
§1.3 · p.10
Error handling: wrapper functions
Nearly every socket function can fail, and checking each one inflates every example by three lines. The book's solution is a wrapper function: the same call, capitalised, that checks the return value, prints a message and terminates on error.
int
Socket(int family, int type, int protocol)
{
int n;
if ( (n = socket(family, type, protocol)) < 0)
err_sys("socket error");
return (n);
}
So Socket(AF_INET, SOCK_STREAM, 0) means exactly
socket(AF_INET, SOCK_STREAM, 0) plus "and die if it fails". The
convention throughout the book — and throughout this site — is:
- lowercase —
socket(),bind() - The real system call. You must check its return value yourself.
- Capitalised —
Socket(),Bind() - The book's wrapper. It has already checked, and exits on failure.
Socket(...) you must not then also test its return value
for < 0 — that combination tells the examiner you have not
understood what the wrapper is for.
err_sys is used when the failure set errno — a system
call went wrong, so the message can include strerror(errno).
err_quit is used when nothing set errno, such as the
user supplying the wrong number of arguments.
§1.4 · p.11
A first server: the daytime server
Its counterpart, in 24 lines. Notice how different the shape is: the client is a straight line from top to bottom, while the server is a loop that never ends.
socket, bind, listen,
accept. A client calls socket, connect.
That is the entire shape of TCP network programming; everything in Chapters 4 and
5 is detail hung on those six calls.
| Call | Telephone equivalent |
|---|---|
| socket() | Having a telephone to use. |
| bind() | Telling people your number so they can call you. |
| listen() | Switching the ringer on, so you hear an incoming call. |
| connect() | Knowing someone's number and dialling it. |
| accept() | Picking up when the phone rings. |
The analogy has one honest limit worth knowing, because it is exactly the kind
of nuance that earns a mark: caller ID shows you the number
before you decide whether to answer, whereas accept hands
you the client's identity only after the connection already exists.
And if DNS is involved, it plays the part of the phone book —
getaddrinfo looks up a name to get a number,
getnameinfo does the reverse.
§4.6
The server calls socket() and so does the client. So what actually makes one of them a server?
Nothing about the socket itself — the call is identical. What makes it a
server is the sequence that follows: bind() gives it a
well-known address so it can be found, and listen() marks it
passive, meaning it will receive connections rather than initiate
them. A client instead calls connect(), which is the active
open.
Put the other way round: a socket is neither until you commit it. The kernel
assumes active until listen says otherwise.
The OSI model, and where sockets sit
The OSI (Open Systems Interconnection) model was described by ISO to give a standard vocabulary for describing any network. It has seven layers. Its practical value here is that it tells you exactly which layer your program is, and which layers you are entitled to ignore.
What each layer actually does
| Layer | Job | Data unit | Examples |
|---|---|---|---|
| 7 Application | Provides network services to user programs. Logical connection is process-to-process, end to end — two chat windows, for instance. | Messages | HTTP, SMTP, FTP, TELNET, SSH, SNMP, DNS |
| 6 Presentation | Data representation — encoding, compression, encryption. | — | (folded into the application in TCP/IP) |
| 5 Session | Dialogue control, checkpointing, recovery. | — | (folded into the application in TCP/IP) |
| 4 Transport | Sequence control, error detection, retransmission and flow control. Logical connection is end-to-end. | Segments (TCP) · user datagrams (UDP) | TCP, UDP, SCTP |
| 3 Network | Routing and addressing, host to host. IP itself is connectionless: no flow control, no error control, no congestion control. | Datagrams | IPv4, IPv6 · helped by ICMP, IGMP, DHCP, ARP |
| 2 Datalink | Moves a datagram across one link — a wired LAN with a switch, a wireless LAN, a WAN. | Frames | Ethernet, PPP, Wi-Fi |
| 1 Physical | Moves data as electromagnetic signals over a medium. Data must be converted into signals. | Bits | Copper, fibre, radio |
Why layer at all
- Simplifies learning — a large problem is broken into smaller, manageable chunks.
- Reduces complexity — each layer solves one thing.
- Provides compatibility — standardised interfaces allow plug-and-play and multi-vendor integration.
- Facilitates modularisation — a new technology can be swapped in at one layer without disturbing the architecture.
- Accelerates evolution — developers work on one layer while changes are prevented from leaking into another.
- Raw socket
- A socket that lets the application bypass the transport layer and speak to IP — or even to the datalink layer — directly. This is the gap drawn between TCP and UDP in Figure 1.14.
- TLI / XTI
- The Transport Layer Interface, an alternative API introduced in AT&T System V Unix, sitting between the OSI transport and session layers. XTI (X/Open Transport Interface) is its evolution. Sockets won; XTI is now mainly of historical interest.
Where the API came from
The sockets API originated in 4.2BSD, released in 1983, and
spread from there into essentially every operating system. That history is why the
API has some awkward corners — the sin_zero padding, the
bzero/bcopy family, the word "socket" itself — that make
no sense from first principles but plenty of sense as accumulated compatibility.
§1.8 · p.20
Unix standards
Exam questions on this section are recall questions. Learn the names, the years and what each one added.
- UNICS → UNIX
- The name began as UNiplexed Information Computing System, single-user and single-process. It later became UNIX — a multi-user, multiprocessing operating system.
- POSIX
- Portable Operating System Interface, an IEEE family of standards. Its primary purpose is to maintain compatibility between operating systems, particularly Unix-derived ones.
- Austin Common Standards Revision Group (CSRG)
- The body that now runs the standardisation work. Its output carries both the IEEE POSIX designation and The Open Group's Technical Standard designation.
| Standard | What it defined |
|---|---|
| IEEE Std 1003.1–1988 | Process primitives (fork, exec, signals, timers), the process environment (user IDs, process groups), files and directories (all the I/O functions), terminal I/O, system databases (password and group files), and the tar and cpio archive formats. |
| IEEE Std 1003.1–1990 | "Part 1: System Application Program Interface (API) [C Language]" — this is POSIX.1. |
| IEEE Std 1003.2–1992 | "Part 2: Shell and Utilities" — this is POSIX.2. |
| IEEE Std 1003.1b–1993 | Realtime extensions: file synchronisation, asynchronous I/O, semaphores, memory management (mmap and shared memory), execution scheduling, clocks and timers, message queues. |
| IEEE Std 1003.1, 1996 edition | Adds threads: thread synchronisation (mutexes and condition variables), thread scheduling, synchronisation scheduling. |
| IEEE Std 1003.1g |
The networking API standard, referred to as POSIX.1g. It
defines two APIs, which it calls Detailed Network Interfaces (DNIs):
1. DNI/Socket, based on the 4.4BSD sockets API. 2. DNI/XTI, based on the X/Open XPG4 specification. |
- The Open Group
- An international consortium of vendors and end-user customers from industry, government and academia. Its combination with IEEE's work is what produced The Single Unix Specification; most Unix systems today conform to some version of POSIX.1 and POSIX.2, and many comply with Single Unix Specification Version 3.
- IETF — Internet Engineering Task Force
- A large, open, international community of network designers, operators, vendors and researchers concerned with the evolution of the Internet architecture and the smooth operation of the Internet. It publishes RFCs (Request For Comments) — UDP is RFC 768, for example.
socket() is called and what it returns. IETF
standardises the protocols — what a TCP segment looks like on the wire.
64-bit architectures
Through the 1990s Unix systems used the ILP32 model: integers,
longs and pointers all 32 bits. The move to 64 bits chose LP64 —
long and pointer become 64 bits, but int stays 32.
| Type | ILP32 | LP64 |
|---|---|---|
| char | 8 | 8 |
| short | 16 | 16 |
| int | 32 | 32 |
| long | 32 | 64 |
| pointer | 32 | 64 |
htonl is a problem waiting to happen if you read its name
literally. The "l" means 32-bit value, not long — it is a
historical artefact from when long was 32 bits. Under LP64 a
long is 64 bits, but htonl still converts 32. The same
goes for "s": think 16-bit value, not short.
§1.11 · p.28
Why must a socket address structure be zeroed before you fill it in, when you are about to assign every field you care about anyway?
Because you are not assigning every field. struct sockaddr_in
contains sin_zero, an 8-byte padding member that exists only to
bring the structure up to 16 bytes, and on BSD-derived systems there is also
sin_len. You never assign those, so without a bzero
they hold whatever was on the stack.
The structure is handed to the kernel wholesale, so those bytes go too.
bzero is a one-line guarantee that nothing undefined crosses the
boundary.
The client's read() returns 0. What happened, and is it an error?
It is not an error. A return of 0 from read on a
socket means end-of-file: the peer has closed its end of the connection and
sent a FIN. For the daytime protocol this is meaningful — the close
is the end-of-message marker.
Errors are signalled by a return of -1, with the reason in
errno. That is why the client tests n < 0
separately after the loop.