> 技术文档 > WireShark抓包分析TCP数据传输过程与内容详解_wireshark抓包的tcp数据怎么看

WireShark抓包分析TCP数据传输过程与内容详解_wireshark抓包的tcp数据怎么看



引言

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

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

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

Qt实现TCP客户端服务端

服务端代码

// Server.h#ifndef SERVER_H#define SERVER_H#include #include #include class Server : public QObject{ Q_OBJECTpublic: 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 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 #include class Client : public QObject{ Q_OBJECTpublic: 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 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 #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 #include #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]**