您的位置:

TCP如何保证可靠传输

一、连接建立

当两台计算机建立TCP连接时,需要进行三次握手。首先客户端向服务端发送连接请求报文,如果服务端收到了请求报文,会向客户端发送一个确认报文,表示可以建立连接。客户端收到确认之后还会向服务器发送一个确认报文,表示连接已经建立。

// TCP连接建立的示例代码

// 客户端发送连接请求报文
struct tcp_header request;
request.destination_port = SERVER_PORT;
request.source_port = CLIENT_PORT;
request.sync = 1; // 同步位设置为1

send_tcp_packet(request);

// 服务端发送确认报文
struct tcp_header response;
response.source_port = SERVER_PORT;
response.destination_port = CLIENT_PORT;
response.ack = 1; // 确认位设置为1
response.acknowledgment_number = request.sequence_number + 1;

send_tcp_packet(response);

// 客户端发送确认报文
struct tcp_header confirm;
confirm.source_port = CLIENT_PORT;
confirm.destination_port = SERVER_PORT;
confirm.ack = 1; // 确认位设置为1
confirm.acknowledgment_number = response.sequence_number + 1;

send_tcp_packet(confirm);

二、数据传输

在TCP连接建立之后,数据传输分为两个部分:发送方将数据拆分成多个报文段(segment),每个报文段都有一个序号和确认号,表示该报文段所包含的数据在整个数据流中的位置,接收方接收到报文段后需要按序号重新组装成完整的数据。

当发送方发送了一个报文段之后,会等待接收方发送确认消息,表示已经成功接收到数据。如果没有收到确认消息,说明数据包丢失,发送方会重新发送该数据包,接收方收到重发的数据包后会判断该数据包是否已经收到过,如果收到过就可以丢弃,否则就可以重新组装成完整的数据。

// TCP数据传输的示例代码

// 发送方代码
struct tcp_header segment;
segment.source_port = CLIENT_PORT;
segment.destination_port = SERVER_PORT;
segment.sequence_number = send_base; // 发送基准序号
segment.acknowledgment_number = receive_base; // 确认基准序号

while (not_all_sent) { // 发送所有数据
    if (next_byte_to_send < send_base + window_size) { // 可以发送新的数据
        segment.payload = get_next_data();
        send_tcp_packet(segment);
        next_byte_to_send ++;

        if (send_base == next_byte_to_send) { // 如果发送窗口满了,等待接收窗口的确认消息
            wait_for_ack();
        }
    } else { // 发送窗口已满,等待接收窗口的确认消息
        wait_for_ack();
    }
}

// 接收方代码
struct tcp_header received_segment;
received_segment = wait_for_segment();

if (received_segment.sequence_number == receive_base) { // 接收到了下一个期望的序号
    append_data(received_segment.payload);
    receive_base = received_segment.sequence_number + received_segment.payload.length();
    send_ack(receive_base); // 发送确认消息
} else if (received_segment.sequence_number > receive_base) { // 接收到了缺失的数据
    send_ack(receive_base); // 发送确认消息
} else { // 接收到了已经接收过的数据
    send_ack(receive_base); // 发送确认消息
}

三、流量控制

TCP连接中的每一方都有一个缓存区,可以存储已经接收到的数据或者等待发送的数据。为了防止发送方发送过多数据导致接收方无法及时处理,TCP使用了滑动窗口的技术进行流量控制。发送方不可以发送超出窗口大小的数据,接收方需要在处理完窗口内的数据之后向发送方发送确认消息,告知发送方可以发送更多的数据。

// TCP流量控制的示例代码

// 发送方代码
struct tcp_header segment;
segment.source_port = CLIENT_PORT;
segment.destination_port = SERVER_PORT;
segment.sequence_number = send_base; // 发送基准序号
segment.acknowledgment_number = receive_base; // 确认基准序号

while (not_all_sent) {
    if (next_byte_to_send < send_base + min(window_size, receive_window)) { // 发送窗口大小不能超过接收窗口大小
        segment.payload = get_next_data();
        send_tcp_packet(segment);
        next_byte_to_send ++;

        if (send_base == next_byte_to_send) { // 如果发送窗口满了,等待接收窗口的确认消息
            wait_for_ack();
        }
    } else { // 发送窗口已满,等待接收窗口的确认消息
        wait_for_ack();
    }
}

// 接收方代码
struct tcp_header received_segment;
received_segment = wait_for_segment();

if (received_segment.sequence_number >= receive_base && received_segment.sequence_number < receive_base + receive_window) { // 接收窗口内的数据
    append_data(received_segment.payload);
    receive_base = received_segment.sequence_number + received_segment.payload.length();
    receive_window = get_receive_window_size();
    send_ack(receive_base); // 发送确认消息
} else { // 接收窗口外的数据
    send_ack(receive_base); // 发送确认消息
}

四、拥塞控制

TCP使用了四种拥塞控制算法,分别是慢开始(slow start)、拥塞避免(congestion avoidance)、快速重传(fast retransmit)、快速恢复(fast recovery)。

慢开始是指在连接建立时,发送方先以指数级别增加发送窗口的大小,直到达到某个阈值,然后再以线性级别增长发送窗口。如果在发送数据的过程中出现了乱序或者丢失的数据包,就需要采用快速重传和快速恢复的技术,快速重传就是在连续收到三个重复的确认消息之后立即重传数据包,快速恢复是在重传数据包之后将发送窗口减半然后进行拥塞避免。

// TCP拥塞控制的示例代码

// 发送方代码
struct tcp_header segment;
segment.source_port = CLIENT_PORT;
segment.destination_port = SERVER_PORT;
segment.sequence_number = send_base; // 发送基准序号
segment.acknowledgment_number = receive_base; // 确认基准序号
int cwnd = 1; // 初始发送窗口大小为1

while (not_all_sent) {
    if (next_byte_to_send < send_base + min(cwnd, receive_window)) { // 发送窗口大小不能超过接收窗口大小和拥塞窗口大小
        segment.payload = get_next_data();
        send_tcp_packet(segment);
        next_byte_to_send ++;

        if (send_base == next_byte_to_send) { // 如果发送窗口满了,等待接收窗口的确认消息
            wait_for_ack();
        }
    } else { // 发送窗口已满,等待接收窗口的确认消息
        wait_for_ack();
    }

    if (received_dup_ack >= 3) { // 收到三个重复确认消息,触发快速重传和快速恢复
        resend_segment();
        cwnd /= 2;
        receive_window = get_receive_window_size();
        cwnd = cwnd + 1;
    } else if (next_byte_to_send - send_base == cwnd) { // 发送窗口满了,进入拥塞避免状态
        cwnd ++;
    }
}

// 接收方代码
struct tcp_header received_segment;
received_segment = wait_for_segment();

if (received_segment.sequence_number >= receive_base && received_segment.sequence_number < receive_base + receive_window) { // 接收窗口内的数据
    append_data(received_segment.payload);
    receive_base = received_segment.sequence_number + received_segment.payload.length();
    receive_window = get_receive_window_size();
    send_ack(receive_base); // 发送确认消息
} else { // 接收窗口外的数据
    send_ack(receive_base); // 发送确认消息
}

if (received_segment.acknowledgment_number > send_base) { // 收到新的确认消息,更新发送窗口大小
    cwnd = min(received_segment.acknowledgment_number - send_base, cwnd * 2);
    send_base = received_segment.acknowledgment_number;
}