当前位置: 首页 > 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.xdnf.cn/news/1158571.html

相关文章:

  • Linux场景常见的几种安装方式
  • 在C++里如何避免栈内存溢出
  • C++ primer知识点总结
  • 深度学习图像分类数据集—八种贝类海鲜食物分类
  • 基于Chinese-LLaMA-Alpaca-3的多模态中医舌诊辅助诊断系统设计与实现
  • day24——Java高级技术深度解析:单元测试、反射、注解与动态代理
  • 零基础 “入坑” Java--- 十三、再谈类和接口
  • ABP VNext + Playwright E2E:前后端一体化自动化测试
  • 苍穹外卖|项目日记(完工总结)
  • 基于Transformer的智能对话系统:FastAPI后端与Streamlit前端实现
  • 【RK3576】【Android14】ADB工具说明与使用
  • 企业级安全威胁检测与响应(EDR/XDR)架构设计
  • xavier nx上编译fast-livo过程中出现的问题记录
  • C++现代编程之旅:从基础语法到高性能应用开发
  • 【GameMaker】GML v3 的现行提案
  • Numpy库,矩阵形状与维度操作
  • (5)从零开发 Chrome 插件:Vue3 Chrome 插件待办事项应用
  • Vue3.6 无虚拟DOM模式
  • An End-to-End Attention-Based Approach for Learning on Graphs NC 2025
  • 线程(一):基本概念
  • 让黑窗口变彩色:C++控制台颜色修改指南
  • week4
  • 内网后渗透攻击过程(实验环境)--3、横向攻击
  • MES系列 - MES是提升制造执行效率与透明度的关键系统
  • 【自动驾驶黑科技】基于Frenet坐标系的车道变换轨迹规划系统实现(附完整代码)
  • 多目标轨迹优化车道变换规划:自动驾驶轨迹规划新范式:基于Frenet坐标系的车道变换算法全解析
  • 枪战验证系统:通过战斗证明你是人类
  • 单片机启动流程和启动文件详解
  • [Linux]进程 / PID
  • [硬件电路-57]:根据电子元器件的受控程度,可以把电子元器件分为:不受控、半受控、完全受控三种大类