Fundamentals 35 min read

Understanding TCP: Fundamentals, Handshake, Data Transfer, and Optimization

This article provides a comprehensive overview of the Transmission Control Protocol (TCP), covering its connection‑oriented design, reliability mechanisms, three‑way handshake, four‑step termination, packet structure, flow and congestion control, and practical C++ socket examples for establishing, sending, receiving, and closing connections.

Deepin Linux
Deepin Linux
Deepin Linux
Understanding TCP: Fundamentals, Handshake, Data Transfer, and Optimization

TCP Protocol Basics

TCP (Transmission Control Protocol) is a connection‑oriented, reliable, byte‑stream transport‑layer protocol defined in IETF RFC 793. It operates over lower‑level, possibly unreliable services and can run on various network types, from wired links to packet‑switched or circuit‑switched networks.

1.1 Characteristics of TCP

Connection‑oriented: applications must establish a TCP connection before transmitting data.

Reliability: data is delivered without errors, loss, duplication, and in order; lost or corrupted segments are retransmitted.

Ordered delivery: each segment is numbered, allowing the receiver to reassemble the original byte stream.

Flow control: the sender adjusts its transmission rate based on feedback from the receiver.

Congestion control: the sender adapts its rate according to network congestion conditions.

What is a connection‑oriented protocol? It requires a handshake before data exchange, ensuring both parties agree on the communication parameters.

Common connection‑oriented protocols include TCP and TLS, while UDP is an example of a connection‑less protocol.

Analogy: a TCP connection is like making a doctor’s appointment (handshake) before seeing the specialist, whereas UDP is like walking into a general clinic without an appointment.

1.2 Reliable Communication

Reliability is achieved through mechanisms such as sequence numbers, acknowledgments, timeout retransmission, sliding windows, error detection/correction, flow control, and congestion control.

1.3 TCP Data Transfer Process

The process consists of three phases:

Connection establishment (three‑way handshake): SYN → SYN‑ACK → ACK.

Data transfer: Segments are sent, acknowledged, and retransmitted if lost.

Connection termination (four‑step handshake): FIN → ACK → FIN → ACK.

2. TCP Working Mechanism

2.1 TCP Segment Format

A TCP segment (also called a TCP packet) contains the following fields:

Source and destination ports: identify the communicating processes.

Sequence number: the number of the first byte in the segment.

Acknowledgment number: the next expected byte.

Data offset / header length: size of the TCP header (minimum 20 bytes, maximum 60 bytes).

Reserved: future use, usually zero.

Control flags: URG, ACK, PSH, RST, SYN, FIN.

Window size: receiver’s buffer capacity for flow control.

Checksum: error‑checking for the entire segment.

Urgent pointer: indicates the end of urgent data when URG is set.

Options and padding: e.g., Maximum Segment Size (MSS).

Data: optional payload.

1、端口号:用来标识同一台计算机的不同的应用进程。
   1)源端口:标识报文的返回地址。
   2)目的端口:指明接收方的应用程序接口。

2.2 Three‑Way Handshake

The handshake ensures both sides agree on initial sequence numbers and that the connection is reliable.

Client sends SYN with initial sequence number.

Server replies with SYN‑ACK (its own sequence number and acknowledgment of client’s SYN).

Client sends ACK, completing the connection.

2.3 Data Transmission

Data is placed in the application’s write buffer, segmented by TCP, sent, and reassembled from the receiver’s read buffer. The sender includes the next expected sequence number; the receiver replies with acknowledgment and window size.

2.4 Four‑Step Connection Termination

The termination involves two FIN‑ACK exchanges to close each direction of the full‑duplex connection, followed by a TIME‑WAIT period to ensure delayed packets are discarded.

3. Using TCP in C++

3.1 Establishing a TCP Connection

#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

int main() {
    int clientSocket = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in serverAddress;
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_port = htons(8000);
    inet_pton(AF_INET, "127.0.0.1", &(serverAddress.sin_addr));
    if (connect(clientSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) < 0) {
        std::cerr << "Failed to connect to the server." << std::endl;
        return -1;
    }
    const char* message = "Hello, server!";
    if (send(clientSocket, message, strlen(message), 0) < 0) {
        std::cerr << "Failed to send data to the server." << std::endl;
        return -1;
    }
    char buffer[1024];
    ssize_t bytesRead = recv(clientSocket, buffer, sizeof(buffer) - 1, 0);
    if (bytesRead < 0) {
        std::cerr << "Failed to receive data from the server." << std::endl;
        return -1;
    }
    buffer[bytesRead] = '\0';
    std::cout << "Received: " << buffer << std::endl;
    close(clientSocket);
    return 0;
}

The code creates a socket, sets the server address, connects, sends a message, receives a reply, and finally closes the socket.

3.2 Sending Data

#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>

int main() {
    int clientSocket = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in serverAddress;
    serverAddress.sin_family = AF_INET;
    serverAddress.sin_port = htons(8000);
    inet_pton(AF_INET, "127.0.0.1", &(serverAddress.sin_addr));
    if (connect(clientSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) < 0) {
        std::cerr << "Failed to connect to the server." << std::endl;
        return -1;
    }
    const char* message = "Hello, server!";
    if (send(clientSocket, message, strlen(message), 0) < 0) {
        std::cerr << "Failed to send data to the server." << std::endl;
        return -1;
    }
    // ...
    close(clientSocket);
    return 0;
}

3.3 Receiving Data

#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>

int main() {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in serverAddr{};
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(8000);
    inet_pton(AF_INET, "127.0.0.1", &(serverAddr.sin_addr));
    connect(sockfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
    char buffer[1024];
    ssize_t bytesRead = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
    if (bytesRead > 0) {
        buffer[bytesRead] = '\0';
        std::cout << "Received data: " << buffer << std::endl;
    }
    close(sockfd);
    return 0;
}

3.4 Closing the Connection

#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>

int main() {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in serverAddr{};
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(8000);
    inet_pton(AF_INET, "127.0.0.1", &(serverAddr.sin_addr));
    connect(sockfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr));
    // ... perform communication ...
    close(sockfd);
    return 0;
}

4. TCP Issues and Optimizations

4.1 Problems

High latency environments suffer from waiting for acknowledgments, reducing throughput.

Flow and congestion control add computational complexity.

Heavy packet loss leads to long retransmission times.

4.2 Optimizations

Increase TCP window size to improve throughput.

Employ modern congestion‑control algorithms (e.g., CUBIC, BBR).

Enable Selective Acknowledgment (SACK) to avoid unnecessary retransmissions.

Use TCP Fast Open (TFO) to send data in the initial SYN.

Apply Nagle’s algorithm and delayed ACK to reduce small packet overhead.

Tune OS TCP/IP stack parameters (e.g., enable fast retransmit, adjust RTO).

Deploy accelerators or load balancers (CDN, hardware offload) for better performance.

5. Summary

TCP is a core transport protocol offering connection‑orientation, reliability, ordering, flow control, and congestion control. Proper use involves establishing a connection, transferring data, and gracefully terminating the session. Although TCP has inherent inefficiencies, techniques such as window scaling, selective ACK, Fast Open, and stack tuning can significantly improve its performance.

TCPreliabilitynetworkinghandshaketransport layer
Deepin Linux
Written by

Deepin Linux

Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.