【网络进阶】Posix API与网络协议栈(一)

1. TCP三次握手过程

TCP(传输控制协议)的三次握手过程是建立TCP连接的基础过程,以下是其步骤:

  1. SYN:客户端发送一个SYN(同步)包到服务器以建立连接。在这个数据包里,客户端会随机产生一个序列号(Seq=X)。这个步骤可以看作是客户端向服务器申请连接。

  2. SYN-ACK:服务器接收到SYN包后,会发送一个SYN-ACK(同步确认)包作为回应。在这个数据包里,服务器也会随机产生一个序列号(Seq=Y),并且确认客户端的SYN(Ack=X+1)。这个步骤可以看作是服务器接受了客户端的连接请求,并告知客户端已经准备好接收数据。

  3. ACK:最后,客户端收到服务器的SYN-ACK包后,还需要发送一个ACK(确认)包,确认服务器的SYN(Ack=Y+1)。这个步骤完成后,TCP连接就建立成功,可以开始传输数据了。

这个过程叫做“三次握手”,是因为在建立连接的过程中,会有三个数据包在客户端和服务器之间传输。这个过程可以确保TCP连接的可靠性,即在数据传输开始前,客户端和服务器都已经确认对方已经准备好建立连接和接收数据。

值得注意的是,在TCP的四次挥手(断开连接的过程)中,也使用了类似的机制来保证连接的可靠关闭。这些过程都是为了使TCP成为一种可靠的,面向连接的,传输控制协议。

以上就是TCP的三次握手过程,而以下是三次握手过程在代码中的一个简单实现:

#include<stdio.h>
#include<string.h>    
#include<sys/socket.h>    
#include<arpa/inet.h> 

int main(int argc , char *argv[])
{
    int socket_desc;
    struct sockaddr_in server;
    char *message , server_reply[2000];
    
    // 创建socket
    socket_desc = socket(AF_INET , SOCK_STREAM , 0);
    if (socket_desc == -1)
    {
        printf("Could not create socket");
    }
    
    server.sin_addr.s_addr = inet_addr("93.184.216.34"); // example.com的IP
    server.sin_family = AF_INET;
    server.sin_port = htons( 80 );

    // 第一次握手:发送SYN包,请求建立连接
    // 第二次握手:接收SYN-ACK包,表示对方已经准备好建立连接
    // 第三次握手:发送ACK包,表示自己已经准备好建立连接
    // connect函数就完成了这个过程
    if (connect(socket_desc , (struct sockaddr *)&server , sizeof(server)) < 0)
    {
        puts("connect error");
        return 1;
    }
    
    puts("Connected\n");
    
    // 发送数据
    message = "GET / HTTP/1.1\r\n\r\n";
    if( send(socket_desc , message , strlen(message) , 0) < 0)
    {
        puts("Send failed");
        return 1;
    }
    
    // 接收并打印服务器回应的数据
    if( recv(socket_desc, server_reply , 2000 , 0) < 0)
    {
        puts("recv failed");
    }
    puts("Reply received\n");
    puts(server_reply);
    
    return 0;
}

在这个例子中,我们创建了一个TCP客户端,该客户端连接到服务器并发送了一个HTTP GET请求。值得注意的是,C语言的socket库将TCP的三次握手过程封装在了connect()函数中,这在大多数语言中都是通用的。

2. TCP四次挥手过程

TCP的四次挥手过程用于终止一个已经建立的TCP连接。这个过程确保了双方都能终止连接,而且所有的数据包都已经被接收。

以下是TCP四次挥手的过程:

  1. FIN:当主动关闭连接的一方(可以是客户端,也可以是服务器)决定关闭连接时,它会发送一个FIN(结束)包到对方,表示数据发送完毕。这个时候,这一方进入FIN_WAIT_1状态。

  2. ACK:接收到FIN包的一方会发送一个ACK(确认)包作为回应,并进入CLOSE_WAIT状态。同时,发送FIN包的一方收到ACK后,会进入FIN_WAIT_2状态。

  3. FIN:当接收到FIN包的一方的数据也发送完毕后,它会发送一个FIN包到对方,告诉对方它的数据也发送完毕了。这个时候,这一方进入LAST_ACK状态。

  4. ACK:最后,发送第一个FIN包的一方收到了最后一个FIN包后,它会发送一个ACK包,并进入TIME_WAIT状态,等待一段时间确保对方收到ACK包后,再关闭连接。

以下是一个使用C语言创建TCP连接并终止的示例代码。注意,C语言的socket库已经在底层处理了TCP的四次挥手过程,我们只需要调用shutdown()函数,就能完成四次挥手并关闭连接。

#include<stdio.h>
#include<string.h>    
#include<sys/socket.h>    
#include<arpa/inet.h> 

int main(int argc , char *argv[])
{
    int socket_desc;
    struct sockaddr_in server;
    char *message , server_reply[2000];
    
    // 创建socket
    socket_desc = socket(AF_INET , SOCK_STREAM , 0);
    if (socket_desc == -1)
    {
        printf("Could not create socket");
    }
    
    server.sin_addr.s_addr = inet_addr("93.184.216.34"); // example.com的IP
    server.sin_family = AF_INET;
    server.sin_port = htons( 80 );

    // 连接到服务器
    if (connect(socket_desc , (struct sockaddr *)&server , sizeof(server)) < 0)
    {
        puts("connect error");
        return 1;
    }
    
    puts("Connected\n");
    
    // 发送数据
    message = "GET / HTTP/1.1\r\n\r\n";
    if( send(socket_desc , message , strlen(message) , 0) < 0)
    {
        puts("Send failed");
        return 1;
    }
    
    // 接收服务器回应的数据
    if( recv(socket_desc, server_reply , 2000 , 0) < 0)
    {
        puts("recv failed");
    }
    puts("Reply received\n");
    puts(server_reply);

    // 关闭连接
    if (shutdown(socket_desc, 2) < 0)  // 这一步就完成了四次挥手过程
    {
        puts("Shutdown failed");
    }
    puts("Disconnected");

    return 0;
}

这段代码中的shutdown()函数就是执行TCP四次挥手过程并断开连接的操作,参数2表示断开输入流和输出流。

请注意,这个示例代码仅仅用于说明,并没有包含错误处理和其他的复杂情况。在真实的编程实践中,我们需要更严谨的代码来处理各种可能的情况。

3. 为什么建立连接需要三次握手,而断开连接需要四次握手

TCP协议设计之初就是为了确保网络通信的可靠性,即数据能够准确无误地从发送方传送到接收方。为了达成这个目标,TCP协议在建立连接和断开连接时都使用了握手的机制。这里的"握手"其实就是信息的交换过程,用于确认双方的状态。

之所以建立连接只需要三次握手,而断开连接却需要四次挥手,是因为服务端在接收到FIN包时,可能还有数据没有发送完,所以需要多出一个步骤来确保所有的数据都能发送完毕。而在建立连接时,只需要交换一次序列号就能建立连接,所以只需要三次握手。

总的来说,无论是三次握手还是四次挥手,其根本目的都是为了确认双方的状态,从而保证TCP连接的可靠性。

4. TIME_WAIT状态持续时间及原因

在TCP四次挥手结束,即收到对方的最后一个ACK后,主动关闭连接的一方会进入TIME_WAIT状态。这个状态会持续一段时间,这段时间的长度通常被设置为2个最大段生存时间(MSL,Maximum Segment Lifetime)。

进入TIME_WAIT状态并持续一段时间的原因主要有两个:

  1. 保证最后一个ACK能够被对方正确接收:如果对方(被动关闭连接的一方)没有收到最后一个ACK,可能会重新发送FIN包。因此,TIME_WAIT状态可以使主动关闭连接的一方有足够的时间来处理这种情况。

  2. 让网络中还在传输的数据包有时间消失:即使TCP连接已经关闭,网络中可能还存在着这个连接的旧的数据包。TIME_WAIT状态可以让这些数据包在网络中消失,防止新的连接收到这些旧的数据包。

在C语言的网络编程中,我们无法直接控制TCP连接进入TIME_WAIT状态,因为这是由TCP协议自动处理的。但我们可以通过设置socket选项SO_LINGER来控制连接关闭的行为:

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

int main(int argc , char *argv[])
{
    int socket_desc;
    struct sockaddr_in server;
    char *message , server_reply[2000];
    struct linger sl;

    // 创建socket
    socket_desc = socket(AF_INET , SOCK_STREAM , 0);
    if (socket_desc == -1)
    {
        printf("Could not create socket");
    }
    
    // 设置SO_LINGER选项
    sl.l_onoff = 1;  // 开启SO_LINGER
    sl.l_linger = 0;  // 设置延迟关闭的时间为0秒
    setsockopt(socket_desc, SOL_SOCKET, SO_LINGER, &sl, sizeof(sl));
    
    server.sin_addr.s_addr = inet_addr("93.184.216.34"); // example.com的IP
    server.sin_family = AF_INET;
    server.sin_port = htons( 80 );

    // 连接到服务器
    if (connect(socket_desc , (struct sockaddr *)&server , sizeof(server)) < 0)
    {
        puts("connect error");
        return 1;
    }
    
    puts("Connected\n");
    
    // 发送数据
    message = "GET / HTTP/1.1\r\n\r\n";
    if( send(socket_desc , message , strlen(message) , 0) < 0)
    {
        puts("Send failed");
        return 1;
    }
    
    // 接收服务器回应的数据
    if( recv(socket_desc, server_reply , 2000 , 0) < 0)
    {
        puts("recv failed");
    }
    puts("Reply received\n");
    puts(server_reply);

    // 关闭连接
    close(socket_desc);  // 这一步会立即关闭连接,而不会进入TIME_WAIT状态

    return 0;
}

这段代码中的setsockopt()函数设置了SO_LINGER选项,使得连接在关闭时不会进入TIME_WAIT状态,而是立即关闭。这个选项可以用于控制连接关闭的行为,但并不会改变TIME_WAIT状态的存在意义和功能。在实际的网络编程中,我们应该根据具体的需求和情况来设置这个选项。

5. 超时重传和快速重传

TCP 协议为了保证网络通信的可靠性,提供了超时重传和快速重传两种重传机制。

1. 超时重传:超时重传是 TCP 协议中最基本的重传机制。在这种机制下,当发送一个数据包后,发送方会启动一个定时器,等待接收方的确认(ACK)。如果在定时器到期之前没有收到确认,发送方就会认为这个数据包可能且很可能丢失,于是就会重传这个数据包。

2. 快速重传:快速重传是 TCP 协议中的另一种重传机制,用于在超时之前就检测到可能的数据包丢失。在这种机制下,当发送方连续收到三个重复的 ACK 时,它就会立即重传可能丢失的数据包,而不需要等待超时。这是因为连续的重复 ACK 是一个信号,表示接收方已经收到了这个丢失数据包之后的数据包,因此这个数据包很可能已经丢失。

以下是一个使用 C 语言的 TCP 服务器的例子,这个服务器会接收客户端的数据,并且发送确认(ACK)。请注意,这个示例仅供理解超时重传和快速重传的概念,并不真实地实现这两种重传机制,因为这些机制是在 TCP 协议的底层实现的,我们通常无法直接在应用程序中控制这些机制。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

#define PORT 8080

int main()
{
    int sockfd;
    struct sockaddr_in server_addr, client_addr;
    char buffer[1024];
    socklen_t addr_size;

    // 创建 socket
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("socket creation 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(sockfd, (const struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // 开始监听连接
    if (listen(sockfd, 3) < 0) {
        perror("listen failed");
        exit(EXIT_FAILURE);
    }

    addr_size = sizeof(client_addr);
    int new_sock = accept(sockfd, (struct sockaddr *)&client_addr, &addr_size);

    // 循环接收数据并发送 ACK
    while (1) {
        memset(buffer, 0, sizeof(buffer));
        ssize_t len = recv(new_sock, buffer, sizeof(buffer) - 1, 0);
        if (len <= 0) {
            break;
        }

        printf("Received: %

s\n", buffer);

        // 发送 ACK
        char *msg = "ACK\n";
        send(new_sock, msg, strlen(msg), 0);
    }

    close(sockfd);

    return 0;
}

在这个代码中,我们并没有实现超时重传和快速重传,因为这些是由 TCP 协议自动处理的。但是,通过循环接收数据并发送确认(ACK),我们可以模拟 TCP 协议中的确认和重传机制。实际上,如果网络出现问题,比如数据包丢失,TCP 协议会自动使用超时重传或快速重传来尝试解决这个问题,以保证数据能够正确地从发送方传送到接收方。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ricky_0528

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值