一、连接建立
当两台计算机建立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; }