What is TCP/IP?
TCP/IP is the protocol suite that carries the internet. IP is responsible for addressing and routing packets between hosts; TCP sits on top and turns those unreliable packets into a reliable, ordered stream of bytes. Together they are the transport layer under HTTP, databases, email, WebSockets and almost everything else your server does.
You rarely write TCP code directly, but understanding it explains a lot: why connections are expensive, why keep-alive matters, why a database pool has a size limit, and why some traffic uses UDP instead.
The layer model
Networking is usually described in layers, each handling one job:
| Layer | Job | Examples |
|---|---|---|
| Application | Meaning | HTTP, WebSocket, DNS |
| Transport | Reliability and ports | TCP, UDP |
| Internet | Addressing and routing | IP, ICMP |
| Link | Local delivery | Ethernet, Wi-Fi |
Each layer uses the one below it. HTTP does not know whether it travels over Ethernet or Wi-Fi; it relies on TCP to deliver bytes, which relies on IP to move packets. This separation is why the internet can carry so many kinds of traffic.
IP addresses and ports
An IP address identifies a host, and a port identifies a service on that host. Together they form a socket address, such as 203.0.113.10:443.
- IPv4 addresses are 32-bit (
203.0.113.10); IPv6 addresses are 128-bit and written in hex. - Ports below 1024 are reserved for well-known services.
- Clients get an ephemeral port for the lifetime of a connection.
- A connection is uniquely identified by the full tuple: source address, source port, destination address and destination port.
That tuple is why a server can accept thousands of connections on port 443 at once: each client’s source port differs.
The three-way handshake
Before any data flows, TCP establishes a connection in three steps:
- The client sends a SYN with its initial sequence number.
- The server replies with SYN-ACK, acknowledging the client and sending its own sequence number.
- The client sends ACK, and the connection is established.
This costs one round trip before the first byte of application data, which is a real latency cost. TLS adds another round trip (or two), and DNS may add another before that. Connection reuse exists specifically to avoid paying these costs repeatedly.
Reliability and flow control
TCP provides reliability through a few mechanisms:
- Sequence numbers order the bytes, so the receiver can reassemble them correctly.
- Acknowledgements confirm receipt, and unacknowledged data is retransmitted.
- Checksums detect corruption.
- Flow control uses a receive window so a fast sender cannot overwhelm a slow receiver.
- Congestion control adapts the sending rate to the network, avoiding collapse.
The result is a stream of bytes that arrives complete and in order, at the cost of some latency and overhead.
UDP: the fast alternative
UDP is connectionless. It sends datagrams with no handshake, no ordering and no retransmission. That sounds worse, but for some workloads it is better:
- Latency matters more than perfection: live video, voice and games prefer a lost frame to a delayed one.
- The application handles reliability: QUIC, which powers HTTP/3, builds its own reliability on top of UDP.
- Small, stateless queries: DNS traditionally uses UDP because a single request and response fit in one datagram.
Choose TCP when correctness and ordering matter, and UDP when you need speed and can tolerate loss.
Sockets in Node.js
Node exposes TCP through node:net and UDP through node:dgram. A TCP server hands you a socket per connection.
// echo.js
import { createServer } from "node:net";
const server = createServer((socket) => {
socket.setKeepAlive(true, 10_000);
socket.setTimeout(30_000);
socket.on("timeout", () => socket.destroy());
socket.on("data", (data) => socket.write(data));
socket.on("error", (err) => console.error(err));
});
server.listen(9000);
Sockets are streams: you read data events and write with socket.write(). Because TCP is a byte stream with no message boundaries, application protocols must define their own framing, which is why HTTP has headers with content lengths and WebSockets have frames.
Connection lifecycle and tuning
- Keep-alive reuses connections, avoiding the handshake for every request.
- Pools cap the number of concurrent connections to a database or service.
- Timeouts close idle or stuck connections so resources are freed.
- TIME_WAIT is a normal state after closing, but too many short-lived connections can exhaust ports.
- Backpressure:
socket.write()returns false when the buffer is full, and you should wait fordrain.
Most production tuning is about reusing connections and limiting concurrency, not changing kernel parameters.
Best practices
- Reuse connections with keep-alive or a pool.
- Set timeouts and keep-alive on long-lived connections.
- Limit concurrency so you do not exhaust sockets or file descriptors.
- Handle errors and backpressure on every socket.
- Use TCP unless you have a specific reason for UDP.
- Understand that TCP is a byte stream and define your own framing.
- Monitor TIME_WAIT and connection counts as a signal of connection churn.
Common mistakes
- Opening a new connection for every request.
- Assuming TCP preserves message boundaries.
- Ignoring
socket.write()backpressure and buffering unbounded data. - Leaving long-lived connections without keep-alive or timeouts.
- Setting pool sizes far larger than the database can handle.
- Reaching for UDP when reliability actually matters.
Where to go next
TCP/IP is the layer everything else stands on. Build on it with the HTTP guide, understand the network basics in How the Internet Works, and move to persistent connections with WebSockets. Then inspect real connections on your machine with ss or netstat while your server handles traffic.