From idea to interface
You already know what a socket is in principle: the doorway between an application and the transport layer, identified by an (IP address, port) endpoint. What you have not seen yet is the doorknob, the actual set of function calls a program turns to open that door, push bytes through it, and read bytes back. That set of calls is the Berkeley socket API, born in Unix in the early 1980s and so durable that the same handful of functions, socket, bind, listen, accept, connect, send, recv, close, are what your browser, your game, and the server you will write all still use today.
The single most useful thing to carry into this rung is the old Unix slogan: everything is a file. When you open a file, the operating system hands you back a small integer, a file descriptor, and from then on you read and write the file by passing that number around. The socket API deliberately reuses that exact shape. Creating a socket gives you back a descriptor too, and you read and write the network through it with calls that look almost identical to file reads and writes. A network connection, to your program, feels like a file whose other end happens to be a process on a different continent.
An address with two parts, and a few special ones
To use a socket you have to say where it lives, and an endpoint is always two pieces glued together: an IP address and a port number. Recall the analogy you have been carrying: the IP address is the street address that gets you to the building, and the port is the apartment number that says which program inside is meant to receive the mail. Written out, an endpoint looks like 203.0.113.7 on TCP port 443, or in shorthand 203.0.113.7:443. Neither half alone is enough; the pair together is what uniquely names one conversation partner.
A few addresses and ports are worth memorizing because they show up everywhere. The well-known ports, 0 to 1023, are reserved by convention for standard services, so a client knows where to knock without asking: HTTP on TCP port 80, HTTPS on TCP port 443, DNS on port 53. When a client opens a connection it does not pick a famous number for its own end; the operating system lends it a temporary ephemeral port from the high range (often 49152 to 65535) for the life of that one conversation, then takes it back. That asymmetry, fixed port on the server, throwaway port on the client, is exactly why thousands of clients can all talk to one web server on port 443 at once.
One special address deserves its own mention: 127.0.0.1, the loopback address, which always means this machine, right here. Packets sent to it never touch a cable or a switch; the kernel turns them straight around inside the host. This is how a database and the app that uses it can talk on the same laptop, and it is the address you will point your very first client at when you test the server you write next, no Internet required.
The server's four moves, the client's one
A TCP connection has two unequal sides. The server is the one waiting in a known place; the client is the one that goes and knocks. Setting up the server takes four distinct moves, and it helps to picture each as a physical act. First socket creates the descriptor, an unconnected doorway. Then bind nails that doorway to a specific (IP, port) so the kernel knows which arriving mail belongs to it. Then listen flips the socket into passive mode and opens a waiting room, a queue, for incoming connection requests. Finally accept blocks until a client actually arrives, then returns a brand-new socket for that one client.
That last point is the one beginners trip over, so slow down for it. The socket you listen on is not the socket you talk on. The listening socket is the front desk; every successful accept hands you a fresh, separate socket dedicated to one conversation, while the front desk goes right back to waiting for the next visitor. This is the API making concrete the four-tuple idea from the transport rung: each accepted socket is keyed by the full (source IP, source port, destination IP, destination port), which is how one listening port can fan out into thousands of independent live connections.
The client's life is much simpler. It calls socket to get a descriptor, then connect, naming the server's (IP, port). Behind that single connect call, the kernel quietly runs the three-way handshake, the SYN, SYN-ACK, ACK exchange you traced earlier, the can-you-hear-me, yes-can-you, yes of opening a TCP conversation. When connect returns successfully, the pipe is up and both sides can read and write. The client never binds explicitly; the kernel gives it an ephemeral port automatically.
SERVER CLIENT
------------------------------ ------------------------------
fd = socket() cfd = socket()
bind(fd, "0.0.0.0", 443)
listen(fd, backlog = 128)
connect(cfd, "203.0.113.7", 443)
<----------- SYN ---------
---------- SYN-ACK ------->
<----------- ACK ---------
conn = accept(fd) -> NEW socket
send(cfd, request_bytes)
recv(conn, buf) <---- data ---- recv(cfd, reply_bytes)
send(conn, reply) ---- data ---->
close(conn) close(cfd)
UDP has no listen/accept/connect dance:
sfd = socket(UDP); bind(sfd, port)
recvfrom(sfd, buf, &who) / sendto(sfd, msg, who)Two flavours of socket, and what TCP does NOT give you
There are two everyday flavours of socket, matching the two transport protocols. A TCP socket is connection-oriented: you do the handshake once, then read and write a stream, and the listen/accept/connect dance above applies. A UDP socket is connectionless: there is no handshake and no per-client accepted socket. You bind one socket to a port and then use recvfrom and sendto, each call carrying or reporting the address of the other end, because every datagram stands alone. UDP is not a broken or lesser TCP; it is a deliberate choice you make when you would rather lose a packet than wait for a retransmission, which the next two guides explore.
Now the most important warning in this whole guide, because it surprises nearly everyone. TCP gives you a byte stream, not a stream of messages. The far side may call send three times with the words HELLO, then WORLD, then BYE, and your recv calls might hand them to you as HELLOWOR, then LDBYE, or as one lump HELLOWORLDBYE, or one byte at a time. TCP faithfully delivers every byte in order, but it keeps no record of where one application message ended and the next began. Those boundaries existed only in the sender's head.
Blocking, and the leap to many connections
By default, socket calls are blocking: when you call recv and no data has arrived, your program simply stops, parked there, until some bytes show up or the connection closes. Likewise accept parks until a client knocks, and a send can park if the kernel's outgoing buffer is full. For a tiny tool that talks to one peer, blocking is a gift: the code reads top to bottom like a recipe and you never think about timing. The catch appears the moment you want to serve more than one client at a time, because a thread blocked in recv on client A is deaf to client B.
There are two honest ways out, and both later guides cover them. One is to hand each connection its own thread or process, so one blocked reader does not freeze the others; simple, but threads cost memory and the scheme strains at thousands of connections. The other is to make the sockets non-blocking, where recv returns immediately with would-block instead of waiting, and then use I/O multiplexing, a single call (select, poll, or epoll on Linux) that watches a whole set of sockets at once and tells you which ones are actually ready to read or write. One thread can then juggle tens of thousands of connections by only ever touching the ones with work to do.
One more reality check before you start coding, because it bites silently. Even a single send may not push all your bytes at once: it can accept just part of your buffer and return a smaller count, and recv can likewise return fewer bytes than you asked for. These partial reads and writes are not errors; they are the normal behaviour of a live byte stream under pressure. Correct socket code always loops, checking how many bytes actually moved and calling again for the rest. That loop, together with framing, is most of what separates a toy that works on localhost from a program that survives the real Internet.