欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

突破_编程_C++_网络_编程(TCPIP 四层模型(传输层))-2 TCP 协议

最编程 2024-04-09 18:43:56
...

2.1 TCP 的特点与工作原理

TCP(Transmission Control Protocol,传输控制协议)提供了面向连接的、可靠的、字节流的服务。

(1)TCP的特点

面向连接:TCP 是一个面向连接的协议,这意味着在数据发送之前,通信双方必须先建立连接。这种连接是全双工的,即数据可以在两个方向上同时传输。
可靠性:TCP 通过一系列机制确保数据的可靠传输。它使用序列号对发送的数据包进行编号,以便接收方能够按正确的顺序重组数据。此外,TCP 还使用确认和重传机制来处理丢失或损坏的数据包。
流量控制:TCP 通过滑动窗口机制实现流量控制,防止发送方发送数据过快而导致接收方缓冲区溢出。
拥塞控制:TCP 还具有拥塞控制功能,能够根据网络状况动态调整发送速率,避免网络拥塞。
字节流服务:TCP 将应用层的数据视为无结构的字节流,它负责将数据分割成合适大小的数据包进行传输,并在接收端将数据包重新组合成原始的字节流。

(2)TCP的工作原理

建立连接(三次握手):

  • SYN:客户端向服务器发送一个 SYN 包,并等待服务器确认。
  • SYN-ACK:服务器收到 SYN 包后,向客户端发送一个SYN-ACK包作为应答,此包同时包含服务器的初始序列号。
  • ACK:客户端收到 SYN-ACK 包后,向服务器发送一个ACK包作为应答,此包包含客户端的确认序列号。至此,TCP 连接建立完成。
  • 数据传输:在连接建立后,双方可以开始发送和接收数据。发送方将数据分割成适当大小的数据包,并为每个数据包添加 TCP 头部信息(包括序列号、确认号等),然后通过网络层发送出去。接收方收到数据包后,根据序列号将数据重新组合成原始的字节流,并发送确认包给发送方。

关闭连接(四次挥手):

  • FIN:当一方想要关闭连接时,它会发送一个 FIN 包给对方。
  • ACK:对方收到 FIN 包后,发送一个 ACK 包作为应答。
  • FIN:一段时间后,对方也发送一个 FIN 包来表示自己也想关闭连接。
  • ACK:收到对方的 FIN 包后,发送一个 ACK 包作为应答。至此,TCP 连接关闭完成。

在整个过程中,TCP 通过序列号、确认号、重传机制等确保数据的可靠传输。同时,它还根据网络状况动态调整发送速率,实现流量控制和拥塞控制。

2.2 TCP 流量控制与拥塞控制

TCP 协议不仅提供了面向连接的、可靠的数据传输服务,还通过流量控制和拥塞控制机制来确保网络的高效和稳定运行。下面我将详细讲解TCP的流量控制与拥塞控制。

(1)TCP 流量控制

TCP 流量控制的主要目的是防止发送方发送数据过快而导致接收方缓冲区溢出。TCP 通过一种称为“滑动窗口”的机制来实现流量控制。

滑动窗口实际上表示的是接收方的接收能力,即当前还有多少空间可以接收新的数据。发送方会根据这个窗口大小来决定发送多少数据。每次发送数据时,发送方都会在TCP头部中携带一个窗口大小字段,告诉接收方自己的当前窗口大小。接收方在收到数据后,会根据自己的缓冲区情况更新这个窗口大小,并发送给发送方。发送方根据接收到的窗口大小来调整自己的发送速率,确保不会发送过多数据导致接收方缓冲区溢出。

这种流量控制机制使得TCP能够根据网络的实际状况动态调整发送速率,实现网络资源的有效利用。

(2)TCP拥塞控制

TCP 拥塞控制的主要目的是防止过多的数据注入网络,避免网络出现拥塞现象。TCP 通过一系列算法来实现拥塞控制,包括慢开始、拥塞避免、快重传和快恢复等。

  • 慢开始:一开始向网络中注入的报文段较少,主要是为了避免突然发送大量数据导致网络拥塞。发送方维护一个叫做拥塞窗口(cwnd)的状态变量,其值取决于网络的拥塞程度。在慢开始阶段,拥塞窗口的大小会逐渐增大,直到达到一个门限值(ssthresh)。
  • 拥塞避免:当拥塞窗口大小超过门限值时,TCP 进入拥塞避免阶段。在这个阶段,拥塞窗口的增长速度会减慢,以避免网络拥塞的发生。
  • 快重传:当接收方收到一个失序的报文段时,它会立即发送一个重复确认报文给发送方,告诉发送方有一个报文段丢失了。发送方在连续收到多个重复确认报文后,会立即重传丢失的报文段,而不需要等待超时定时器超时。这种机制可以快速地恢复丢失的数据包,提高传输效率。
  • 快恢复:与快重传配合使用的算法。当发送方收到多个重复确认报文时,它会调整拥塞窗口的大小和门限值,并进入快恢复阶段。在这个阶段,发送方会尝试快速恢复网络的拥塞状况,避免进一步的拥塞发生。

通过这些拥塞控制算法,TCP 能够动态地调整发送速率,以适应网络的变化,避免网络拥塞的发生,从而确保数据的高效和可靠传输。

2.3 示例

下面是一个简单的 、TCP 客户端和服务器的示例。这个示例将创建一个简单的 TCP 服务器,它接受客户端的连接,并接收从客户端发送的字符串消息。然后,服务器会将消息发送回客户端。

首先,创建一个 TCP 服务器:

#include <iostream>  
#include <cstring>  
#include <sys/socket.h>  
#include <arpa/inet.h>  
#include <unistd.h>  
  
const int PORT = 8080;  
const int BACKLOG = 10;  
const int BUFFER_SIZE = 1024;  
  
int main() {  
    int server_fd, client_fd;  
    struct sockaddr_in server_addr, client_addr;  
    socklen_t client_len = sizeof(client_addr);  
    char buffer[BUFFER_SIZE];  
  
    // 创建socket  
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {  
        perror("socket failed");  
        exit(EXIT_FAILURE);  
    }  
  
    // 设置服务器地址信息  
    memset(&server_addr, 0, sizeof(server_addr));  
    server_addr.sin_family = AF_INET;  
    server_addr.sin_addr.s_addr = INADDR_ANY;  
    server_addr.sin_port = htons(PORT);  
  
    // 绑定socket到地址  
    if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {  
        perror("bind failed");  
        exit(EXIT_FAILURE);  
    }  
  
    // 监听连接  
    if (listen(server_fd, BACKLOG) < 0) {  
        perror("listen");  
        exit(EXIT_FAILURE);  
    }  
  
    // 接受客户端连接  
    if ((client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len)) < 0) {  
        perror("accept");  
        exit(EXIT_FAILURE);  
    }  
  
    // 读取客户端发送的消息  
    memset(buffer, 0, BUFFER_SIZE);  
    int valread = read(client_fd, buffer, BUFFER_SIZE);  
    std::cout << "Message received from client: " << buffer << std::endl;  
  
    // 发送消息回客户端  
    send(client_fd, buffer, strlen(buffer), 0);  
  
    // 关闭连接  
    close(client_fd);  
    close(server_fd);  
  
    return 0;  
}

接下来,创建一个 TCP 客户端:

#include <iostream>  
#include <cstring>  
#include <sys/socket.h>  
#include <arpa/inet.h>  
#include <unistd.h>  
  
const char *SERVER_IP = "127.0.0.1";  
const int PORT = 8080;  
const int BUFFER_SIZE = 1024;  
  
int main() {  
    int sock = 0;  
    struct sockaddr_in serv_addr;  
    char buffer[BUFFER_SIZE] = "Hello from client";  
  
    // 创建socket  
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {  
        std::cerr << "Socket creation error" << std::endl;  
        return -1;  
    }  
  
    // 设置服务器地址信息  
    memset(&serv_addr, '0', sizeof(serv_addr));  
    serv_addr.sin_family = AF_INET;  
    serv_addr.sin_port = htons(PORT);  
  
    // 将IP地址从点分十进制转换为网络字节序  
    if (inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr) <= 0) {  
        std::cerr << "Invalid address/Address not supported" << std::endl;  
        return -1;  
    }  
  
    // 连接到服务器  
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {  
        std::cerr << "Connection failed" << std::endl;  
        return -1;  
    }  
  
    // 发送消息到服务器  
    send(sock, buffer, strlen(buffer), 0);  
  
    // 接收来自服务器的消息  
    memset(buffer, 0, BUFFER_SIZE);  
    int valread = read(sock, buffer, BUFFER_SIZE);
	if (valread < 0) {
	std::cerr << "Read failed" << std::endl;
	return -1;
	}
	std::cout << "Message received from server: " << buffer << std::endl;

	// 关闭socket  
	close(sock);  
	 
	return 0;
	}