The most surprising thing about network sockets is that they aren’t really "connections" in the way you probably think.
Let’s see this in action. Imagine you’re running a simple web server on your machine, listening on port 80.
# On your server, start a simple Python HTTP server
python3 -m http.server 80
Now, from another machine, you can curl it:
# From your client machine
curl http://your_server_ip:80
What’s happening under the hood? Your curl command initiated a process. The operating system on your client machine, at the request of curl, created a socket. This socket is an endpoint for communication. It has an IP address and a port number. When curl sent its request to http://your_server_ip:80, it was essentially telling the OS: "Send this data to the socket identified by your_server_ip:80."
On the server side, the Python process created its own socket, bound it to port 80, and told the OS, "Listen for incoming data on this socket." When the data from curl arrived at your_server_ip:80, the OS delivered it to the Python process’s listening socket. The Python process then read the data, processed it (in this case, by figuring out what file curl was asking for), and sent a response back. The response also traveled through sockets, from the Python process’s socket back to the socket that curl had created on the client machine.
The core concept is that a socket is an abstraction. It’s a file descriptor in Unix-like systems, an object in Windows. It represents one end of a two-way communication link between two programs running on the network. This link can be between processes on the same machine or on different machines. The operating system’s network stack handles the actual transmission of data packets, but the socket is your program’s interface to that process.
Now, about those two main types: TCP and UDP. Your curl example above uses TCP. TCP (Transmission Control Protocol) is like sending a registered letter. It’s reliable, ordered, and guarantees delivery. When you send data over TCP, the OS breaks it into packets, numbers them, and sends them. The receiving OS acknowledges receipt of each packet. If a packet is lost, it’s retransmitted. If packets arrive out of order, they’re reassembled correctly. This reliability comes with overhead: more data is exchanged for acknowledgments and sequencing, making it slower than UDP.
UDP (User Datagram Protocol), on the other hand, is like sending a postcard. It’s connectionless, faster, and has less overhead. You just send your data, and the OS fires it off. There are no guarantees of delivery, no ordering, and no acknowledgments. If a packet gets lost, it’s gone. This makes UDP ideal for applications where speed is critical and some data loss is acceptable, like streaming video, online gaming, or DNS lookups.
The underlying mechanism for both TCP and UDP involves the IP layer, which handles the routing of packets across networks. TCP and UDP add their own headers to the IP packet, specifying source and destination ports, sequence numbers (for TCP), and other control information. The operating system’s network stack then uses this information to deliver the data to the correct application process via its socket.
When you’re debugging network applications, understanding sockets means looking at things like netstat or ss to see which processes are listening on which ports, and which connections are established. It’s about inspecting the endpoints.
The most common misconception is that a "TCP connection" is a persistent, unbroken pipe. In reality, TCP establishes a connection through a three-way handshake, but the data flows in discrete segments, each potentially traversing different paths and experiencing delays or loss independently, only to be reassembled and ordered by the TCP layer at the destination.
The next concept to explore is how these socket abstractions are used in higher-level protocols like HTTP and how they handle different communication patterns like client-server and peer-to-peer.