• Wireshark抓包:理解TCP三次握手和四次挥手过程


    TCP是一种面向连接、端到端可靠的协议,它被设计用于在互联网上传输数据和确保成功传递数据和消息。本节来介绍一下TCP中的三次握手和四次挥手。

    1 TCP头部格式

    TCP头部占据TCP段的前20个字节,其中包含端到端TCP套接字的参数和状态。如下图所示:

    在这里插入图片描述

    下面来逐个解释一下这些字段:

    • 源端口(Source port):16位,用于标识源端口号(发送方的TCP端口)
    • 目标端口(Destination port):16位,用于标识目标端口号(接收方的TCP端口)
    • 序列号(Sequence number):32位,指示在TCP会话期间发送了多少数据。当建立新的TCP连接时,初始序列号是一个随机值。
    • 确认号(``Acknowledgment Number`):32位,由接收方用于请求下一个TCP段。如果设置了ACK控制位,该字段包含段发送方期望接收的下一个序列号的值。一旦建立了连接,这个字段总是被发送。
    • 数据偏移(Data offset):4位,显示头部中32位字的数量,也称为头部长度
    • 保留数据(Reserved data):6位,保留字段,始终设置为零
    • 控制位标志(Control bit Flags):TCP使用9位控制标志来管理特定情况下的数据流,例如建立连接、发送数据和终止连接
      • URG: 与后面的紧急指针字段相关,当设置了此位时,数据应被视为优先于其他数据。
      • ACK: 与ACK相关,确认字段用于指示已成功接收到的数据量,如果设置了此字段,说明发送方期望接收方继续发送下一个TCP段
      • PSH: 推送功能,表示发送方希望接收方立即传输数据,而不必等到整个TCP段的数据都准备好再传输
      • RST: 重置连接,仅在存在无法恢复的错误时使用
      • SYN: 同步序列号,此标志用于设置初始序列号
      • FIN: 完成位用于结束TCP连接,因为TCP是全双工连接,所以双方都必须使用FIN位来结束连接
    • 窗口(Window):16位,指定接收方愿意接收多少字节
    • 校验和(Checksum):16位,用于对头部和数据进行错误检查
    • 紧急指针(Urgent Pointer):如果设置了URG控制标志,该值表示与序列号的偏移,指示最后一个紧急数据字节
    • 选项(Options):可选,长度可为0~320位之间的任意大小

    2 wireshark抓包分析

    程序流程:服务端监听本地环回地址127.0.0.1的12345端口,客户端则连接这个端口,连接上后服务端发送一个Hello, World!给客户端。

    先来了解一下SEQACK的概念:

    2.1 SEQ和ACK

    客户端和服务器之间建立TCP连接时会进行三次握手。先来理解一下SEQACK的概念:

    1. 序列号(SEQ): 表示发送方发送的数据的起始位置。每发送一个新的数据段,序列号就会递增。
    2. 确认号(ACK): 表示接收方期望下次收到的数据的序列号。当接收方收到数据后,它会发送一个带有确认号的ACK,告诉发送方它已成功接收到了特定序列号之前的所有数据。
    3. 下一个期望的ACK: 当接收方收到一段数据时,ACK表示已成功接收的数据的下一个期望的序列号。因此,下一个期望的ACK号通常是上一个ACK号加上接收到的数据的长度。

    2.2 三次握手

    1、客户端发送SYN给服务端

    在这里插入图片描述

    • 在wireshark中SEQ使用相对0的值,为的是方便分析,所以这里是0

    从上图中可以看到SYN标识被设置:

    在这里插入图片描述

    2.服务端回复带有SEQACKSYN-ACK数据包

    在这里插入图片描述

    如下图所示:

    在这里插入图片描述

    3.客户端向服务器发送一个带有ACK号的数据包,确认服务器的序列号

    在这里插入图片描述

    如下图所示:

    在这里插入图片描述

    此时双方的SEQ已同步,以上就是三次握手的内容。下面客户端和服务器可以独立地发送和接收数据。

    4.服务器向客户端发送“Hello, World!”

    在这里插入图片描述

    5.客户端向服务器发送一个ACK号,确认服务器的消息

    上一个ACK号为1,长度为13,因此ACK号将为13+1=14。

    在这里插入图片描述

    2.3 四次挥手

    接着上面的抓包来看,在程序中,服务端发送完“Hello, World!”后就关闭了客户端的socket。

    1.服务端发送FIN给客户端

    在这里插入图片描述

    如下图所示:

    在这里插入图片描述

    2.客户端向服务器发送一个ACK号,确认服务器的FIN请求

    在这里插入图片描述

    如下图所示:

    在这里插入图片描述

    3.TCP是一种全双工连接,因此,客户端也向服务器发送一条消息以关闭连接

    • 前面的图中最后一行是红色的RST是我不小心直接关闭了程序,下面的图为重新抓的包,注意看最后两条即可

    在这里插入图片描述

    如下图所示:

    在这里插入图片描述

    4.服务器向客户端发送一个ACK号,确认客户端的FIN请求

    在这里插入图片描述

    如下图所示:

    在这里插入图片描述

    3 程序

    本文的代码使用Windows环境下的网络编程库,所以需要在链接库中增加ws2_32

    服务端

    // Server.c
    
    #include 
    #include 
    #include 
    
    int main() {
        WSADATA wsaData;
        SOCKET listenSocket, clientSocket;
        struct sockaddr_in serverAddr, clientAddr;
        int addrLen = sizeof(clientAddr);
        char buffer[1024] = "Hello, World!";
    
        // Initialize Winsock
        if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
            fprintf(stderr, "WSAStartup failed\n");
            return 1;
        }
    
        // Create a socket
        if ((listenSocket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
            fprintf(stderr, "Socket creation failed\n");
            WSACleanup();
            return 1;
        }
    
        // Set up server address information
        memset(&serverAddr, 0, sizeof(serverAddr));
        serverAddr.sin_family = AF_INET;
        serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
        serverAddr.sin_port = htons(12345);
    
        // Bind the socket
        if (bind(listenSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
            fprintf(stderr, "Bind failed\n");
            closesocket(listenSocket);
            WSACleanup();
            return 1;
        }
    
        // Listen for incoming connections
        if (listen(listenSocket, SOMAXCONN) == SOCKET_ERROR) {
            fprintf(stderr, "Listen failed\n");
            closesocket(listenSocket);
            WSACleanup();
            return 1;
        }
    
        printf("Server listening on 127.0.0.1:12345\n");
    
        // Accept a connection from a client
        if ((clientSocket = accept(listenSocket, (struct sockaddr*)&clientAddr, &addrLen)) == INVALID_SOCKET) {
            fprintf(stderr, "Accept failed\n");
            closesocket(listenSocket);
            WSACleanup();
            return 1;
        }
    
        // Send data to the client
        send(clientSocket, buffer, strlen(buffer), 0);
    
        printf("Data sent to the client\n");
    
        // Clean up
        closesocket(clientSocket);
        closesocket(listenSocket);
        WSACleanup();
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    客户端

    // Client.c
    
    #include 
    #include 
    #include 
    
    int main() {
        WSADATA wsaData;
        SOCKET clientSocket;
        struct sockaddr_in serverAddr;
        char buffer[1024];
    
        // Initialize Winsock
        if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
            fprintf(stderr, "WSAStartup failed\n");
            return 1;
        }
    
        // Create a socket
        if ((clientSocket = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
            fprintf(stderr, "Socket creation failed\n");
            WSACleanup();
            return 1;
        }
    
        // Set up server address information
        memset(&serverAddr, 0, sizeof(serverAddr));
        serverAddr.sin_family = AF_INET;
        serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
        serverAddr.sin_port = htons(12345);
    
        // Connect to the server
        if (connect(clientSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {
            fprintf(stderr, "Connection failed\n");
            closesocket(clientSocket);
            WSACleanup();
            return 1;
        }
    
        // Receive data from the server
        int bytesRead = recv(clientSocket, buffer, sizeof(buffer), 0);
        if (bytesRead > 0) {
            buffer[bytesRead] = '\0'; // Null-terminate the received data
            printf("Received data from server: %s\n", buffer);
        } else {
            fprintf(stderr, "Error receiving data\n");
        }
        while(1)
        {
            if(recv(clientSocket, buffer, sizeof(buffer), 0) == 0)
            {
                break;
            }
        }
        // Clean up
        closesocket(clientSocket);
        WSACleanup();
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
  • 相关阅读:
    C/C++游戏开发(easyx框架)回合制——魔塔
    康拓123发卡软件支持PN532读卡器
    【Vue】ElementUI实现登录注册+axios全局配置+CORS跨域
    语法复习之C语言与指针
    amlogic 机顶盒关闭DLNA 后,手机还能搜到盒子
    pipeline拉取代码-maven打包-sonar代码质量检测
    【逐步剖C++】-第二章-C++类和对象(下)
    vue+elementui个人健康信息网站php
    ChatGPT支持下的PyTorch机器学习与深度学习技术应用
    【区块链 | 预言机】价格预言机的使用总结(二):UniswapV2篇
  • 原文地址:https://blog.csdn.net/tilblackout/article/details/134431658