当前位置: 首页 > news >正文

WireShark抓包分析TCP数据传输过程与内容详解


引言

网上有很多关于 WireShark 抓包分析 TCP 三次握手和四次挥手的教程,这个个人认为属于八股的范畴,可能面试的时候比较有用,但是实际开发中应该不关心连接建立的过程

从现有的工作经验看,更关注连接建立后,上下的通信是否正常,也就是说,上位机发送的指令,下位机是否收到并且有相应的应答返回上来

本篇博客主要尝试对上下层通信建立后通信内容进行分析,通过 WireShark 抓包并且提取出发送和应答的内容

Qt实现TCP客户端服务端

服务端代码

// Server.h
#ifndef SERVER_H
#define SERVER_H#include <QObject>
#include <QTcpServer>
#include <QTcpSocket>class Server : public QObject
{Q_OBJECT
public:explicit Server(QObject *parent = nullptr);void startServer(quint16 port);signals:void newMessage(const QString &msg);private slots:void onNewConnection();void onReadyRead();void onDisconnected();private:QTcpServer *m_server;QList<QTcpSocket*> m_clients;
};#endif // SERVER_H
// Server.cpp
#include "Server.h"
#include <QDebug>Server::Server(QObject *parent) : QObject(parent)
{m_server = new QTcpServer(this);connect(m_server, &QTcpServer::newConnection, this, &Server::onNewConnection);
}void Server::startServer(quint16 port)
{if(!m_server->listen(QHostAddress::Any, port)) {emit newMessage(QString("服务器启动失败: %1").arg(m_server->errorString()));return;}emit newMessage(QString("服务器已启动,监听端口: %1").arg(port));
}void Server::onNewConnection()
{QTcpSocket *clientSocket = m_server->nextPendingConnection();connect(clientSocket, &QTcpSocket::readyRead, this, &Server::onReadyRead);connect(clientSocket, &QTcpSocket::disconnected, this, &Server::onDisconnected);m_clients.append(clientSocket);emit newMessage(QString("新的客户端连接: %1:%2").arg(clientSocket->peerAddress().toString()).arg(clientSocket->peerPort()));
}void Server::onReadyRead()
{QTcpSocket *clientSocket = qobject_cast<QTcpSocket*>(sender());if(!clientSocket) return;QByteArray data = clientSocket->readAll();emit newMessage(QString("收到数据: %1").arg(data.toHex()));// 广播给所有客户端for(QTcpSocket *client : qAsConst(m_clients)) {client->write(data);}
}void Server::onDisconnected()
{QTcpSocket *clientSocket = qobject_cast<QTcpSocket*>(sender());if(!clientSocket) return;m_clients.removeOne(clientSocket);emit newMessage(QString("客户端断开连接: %1:%2").arg(clientSocket->peerAddress().toString()).arg(clientSocket->peerPort()));clientSocket->deleteLater();
}

客户端代码

// Client.h
#ifndef CLIENT_H
#define CLIENT_H#include <QObject>
#include <QTcpSocket>class Client : public QObject
{Q_OBJECT
public:explicit Client(QObject *parent = nullptr);void connectToServer(const QString &host, quint16 port);void sendMessage(const QByteArray &message);signals:void newMessage(const QString &msg);void connected();void disconnected();private slots:void onConnected();void onReadyRead();void onDisconnected();private:QTcpSocket *m_socket;
};#endif // CLIENT_H
// Client.cpp
#include "Client.h"
#include <QDebug>Client::Client(QObject *parent) : QObject(parent)
{m_socket = new QTcpSocket(this);connect(m_socket, &QTcpSocket::connected, this, &Client::onConnected);connect(m_socket, &QTcpSocket::readyRead, this, &Client::onReadyRead);connect(m_socket, &QTcpSocket::disconnected, this, &Client::onDisconnected);
}void Client::connectToServer(const QString &host, quint16 port)
{m_socket->connectToHost(host, port);emit newMessage(QString("正在连接到服务器 %1:%2...").arg(host).arg(port));
}void Client::sendMessage(const QByteArray &message)
{if(m_socket->state() == QTcpSocket::ConnectedState) {m_socket->write(message);emit newMessage(QString("发送: %1").arg(message.toHex()));}
}void Client::onConnected()
{emit newMessage("已连接到服务器");emit connected();
}void Client::onReadyRead()
{QByteArray data = m_socket->readAll();emit newMessage(QString("收到: %1").arg(data.toHex()));
}void Client::onDisconnected()
{emit newMessage("与服务器断开连接");emit disconnected();
}

使用示例

服务端使用

// main_server.cpp
#include <QCoreApplication>
#include "Server.h"int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);Server server;QObject::connect(&server, &Server::newMessage, [](const QString &msg) {qDebug() << "[Server]" << msg;});server.startServer(12345); // 监听12345端口return a.exec();
}

客户端使用

// main_client.cpp
#include <QCoreApplication>
#include <QTimer>
#include "Client.h"int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);Client client;QObject::connect(&client, &Client::newMessage, [](const QString &msg) {qDebug() << "[Client]" << msg;});client.connectToServer("127.0.0.1", 12345);// 连接成功后发送消息QObject::connect(&client, &Client::connected, [&client]() {QTimer::singleShot(1000, [&client]() {client.sendMessage(QByteArray::fromHex("AB99CD66EE"));});});return a.exec();
}

功能说明

  1. 服务端功能
    • 监听指定端口
    • 接受多个客户端连接
    • 接收客户端消息并广播给所有客户端
    • 处理客户端断开连接
  2. 客户端功能
    • 连接到指定服务器
    • 发送消息到服务器
    • 接收服务器消息
    • 处理连接断开

编译运行

  1. 将服务端代码和服务端main文件一起编译
  2. 将客户端代码和客户端main文件一起编译
  3. 先运行服务端,再运行客户端
18:18:04: Starting E:\Code\QtTcpServer\build\Desktop_Qt_6_7_3_MSVC2019_64bit-Debug\QtTcpServer.exe...
[Server] "服务器已启动,监听端口: 12345"
[Server] "新的客户端连接: ::ffff:127.0.0.1:13908"
[Server] "收到数据: ab99cd66ee"
18:18:08: Starting E:\Code\QTcpClient\build\Desktop_Qt_6_7_3_MSVC2019_64bit-Debug\QTcpClient.exe...
[Client] "正在连接到服务器 127.0.0.1:12345..."
[Client] "已连接到服务器"
[Client] "发送: ab99cd66ee"
[Client] "收到: ab99cd66ee"

示例中是模拟实际中可能会发送的十六进制格式的控制指令(“AB99CD66EE”)

WireShark抓包分析

在这里插入图片描述

最前面三行是在进行三次握手,此处不展开,主要看后面四行

因为是在本地回环上测试,因此 SRC 和 DST 都是 127.0.0.1

Client的通信端口是13908,Server的通信端口是12345

Demo 实现的功能是客户端给服务端发什么,服务端都会无条件的转发回去

第四行13908→12345,在Data段可以看到客户端给服务端发送消息的具体内容,和代码中是一致的

在这里插入图片描述

第六行12345→13908,在Data段同样可以看到服务端回复给客户端相同的内容,与预期一致

在这里插入图片描述

抓包标志位详细分析

在 Wireshark 抓包分析中,[PSH, ACK] 是 TCP 协议标志位(Flags)的组合,表示数据包的两种关键行为。以下是详细解释:


1. 标志位含义

  • PSH (Push)
    • 发送方通知接收方立即将数据交给上层应用,而不是等待缓冲区填满。
    • 避免数据在 TCP 缓冲区中滞留,提高实时性(如交互式应用)。
  • ACK (Acknowledgment)
    • 表示该数据包包含对已接收数据的确认(确认号有效)。
    • 绝大多数 TCP 数据包都会带有 ACK 标志。

2. [PSH, ACK] 的典型场景

  • 实时数据传输

    例如:SSH 输入、HTTP 请求/响应、在线聊天消息等需要即时处理的场景。

    Client → Server: [PSH, ACK] "GET /index.html"
    Server → Client: [PSH, ACK] "HTTP/1.1 200 OK"
    
  • 避免缓冲延迟

    当发送方希望接收方立即处理数据时(如用户敲击键盘后发送字符)。


3. 技术细节

  • 数据流示例

    1. Client → Server [SYN]           # 建立连接
    2. Server → Client [SYN, ACK]
    3. Client → Server [ACK]
    4. Client → Server [PSH, ACK]      # 携带实际数据(如HTTP请求)
    5. Server → Client [PSH, ACK]      # 携带响应数据
    
  • Wireshark 中的显示

    • 在包列表中以 [PSH, ACK] 标记。

    • 在包详情中可查看具体标志位:

      Transmission Control Protocol (TCP)Flags: 0x018 (PSH, ACK)...000.. = Reserved: Not set....1... = PSH: Push.....1.. = ACK: Acknowledgment
      

4. 常见问题

Q1: 为什么有些数据包没有 PSH

  • 如果数据量较小或无需立即处理,TCP 可能合并多个小包(Nagle 算法),延迟发送 PSH

Q2: PSHURG 的区别?

  • PSH:要求正常数据立即上传给应用。
  • URG:标记紧急数据(如中断信号),需配合紧急指针使用(现代应用较少使用)。

Q3: 如何过滤 [PSH, ACK] 包?

在 Wireshark 过滤栏输入:

tcp.flags.push == 1 and tcp.flags.ack == 1

5. 实际意义

  • 网络调试:频繁出现 [PSH, ACK] 可能表明应用需要低延迟(如视频会议)。
  • 性能优化:若发现 PSH 过多,可检查是否禁用 Nagle 算法(TCP_NODELAY)。

总结

行为说明
PSH催促接收方立即处理数据,避免缓冲延迟
ACK确认已接收数据,保证可靠性
[PSH, ACK]同时携带数据和确认信息,常见于需要实时响应的应用(如HTTP、SSH、DNS)

通过分析 [PSH, ACK] 包,可以深入理解应用的通信模式和性能特征。

当主机A向主机B发送一个**[PSH, ACK]数据包后,主机B通常会回复一个[ACK](纯确认包),这是TCP协议的标准行为**

TCP协议通过**确认机制(ACK)**保证可靠传输,具体逻辑如下:

  • [PSH, ACK]的作用
    • PSH:要求接收方(B)立即将数据推送(Push)给上层应用(如HTTP服务)。
    • ACK:携带对之前数据的确认号(Acknowledge Number),表示"我已收到你之前的数据"。
  • B的响应逻辑
    • 当B收到**[PSH, ACK]**后,需要做两件事:
      1. 确认接收:通过**[ACK]**包告知A"数据已收到"(确认号 = A发送的序列号 + 数据长度)。
      2. 处理数据:将数据立即提交给应用层(如Web服务器处理HTTP请求)。

以HTTP请求为例:

A → B: [PSH, ACK] Seq=100, Ack=200, Len=50  # 携带HTTP请求数据
B → A: [ACK]      Seq=200, Ack=150           # 确认收到50字节(100+50=150)
B → A: [PSH, ACK] Seq=200, Ack=150, Len=80  # 携带HTTP响应数据
A → B: [ACK]      Seq=150, Ack=280           # 确认收到80字节(200+80=280)

如果B需要立即回复数据(如HTTP响应),可能会将**[ACK][PSH, ACK]**合并,直接回复:

A → B: [PSH, ACK] Seq=100, Ack=200, Len=50  # HTTP请求
B → A: [PSH, ACK] Seq=200, Ack=150, Len=80  # 同时确认请求+发送响应

此时不会单独出现纯**[ACK]**包

  • 默认情况下,TCP会尝试延迟发送[ACK](通常200ms),等待是否有数据需要一起发送(减少包数量)
  • 如果B没有数据要回复,最终仍会单独发送**[ACK]**
http://www.dtcms.com/a/289084.html

相关文章:

  • 多目标轨迹优化车道变换规划:自动驾驶轨迹规划新范式:基于Frenet坐标系的车道变换算法全解析
  • Node.js Express keep-alive 超时时间设置
  • spring boot2升级boot3
  • Linux简单了解历史
  • 大数据之路:阿里巴巴大数据实践——离线数据开发
  • RTC外设详解
  • Unity 新旧输入系统对比
  • XSS内容总结
  • 包装类型+泛型+List+ArrayList
  • [CVPR]DVFL-Net:用于时空动作识别的轻量级蒸馏视频调焦网络
  • 连接语言大模型(LLM)服务进行对话
  • vben-admin 导入并使用基础版的vxe-table
  • 【LeetCode 热题 100】236. 二叉树的最近公共祖先——DFS
  • oracle 11g drop user 失败,报错ORA-00600
  • jxORM--编程指南
  • EXPLAIN:你的SQL性能优化透视镜
  • 【Docker-Day 7】揭秘 Dockerfile 启动指令:CMD、ENTRYPOINT、ENV、ARG 与 EXPOSE 详解
  • 软件测试-Bug
  • 最简单的 Android TV 项目示例
  • 【RK3576】【Android14】显示屏MIPI开发调试
  • USB 2.0 vs USB 3.0:全面技术对比与选择指南
  • HuggingFace基础知识和环境安装
  • 如何在 QGIS 中定义/更改坐标系?
  • 吴恩达《AI for everyone》第二周课程笔记
  • Redis 概率型数据结构实战指南
  • 浅谈 Vue 的双向数据绑定
  • 10-day07文本分类
  • 借助AI学习开源代码git0.7之四update-cache
  • 常用框架知识
  • 基于单片机的温湿度报警系统设计与实现