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();}
功能说明
- 服务端功能:
- 监听指定端口
- 接受多个客户端连接
- 接收客户端消息并广播给所有客户端
- 处理客户端断开连接
- 客户端功能:
- 连接到指定服务器
- 发送消息到服务器
- 接收服务器消息
- 处理连接断开
编译运行
- 将服务端代码和服务端main文件一起编译
- 将客户端代码和客户端main文件一起编译
- 先运行服务端,再运行客户端
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: PSH
和 URG
的区别?
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]
通过分析 [PSH, ACK]
包,可以深入理解应用的通信模式和性能特征。
当主机A向主机B发送一个**[PSH, ACK]
数据包后,主机B通常会回复一个[ACK]
(纯确认包),这是TCP协议的标准行为**
TCP协议通过**确认机制(ACK)**保证可靠传输,具体逻辑如下:
[PSH, ACK]
的作用:PSH
:要求接收方(B)立即将数据推送(Push)给上层应用(如HTTP服务)。ACK
:携带对之前数据的确认号(Acknowledge Number),表示\"我已收到你之前的数据\"。
- B的响应逻辑:
- 当B收到**
[PSH, ACK]
**后,需要做两件事:- 确认接收:通过**
[ACK]
**包告知A\"数据已收到\"(确认号 = A发送的序列号 + 数据长度)。 - 处理数据:将数据立即提交给应用层(如Web服务器处理HTTP请求)。
- 确认接收:通过**
- 当B收到**
以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]
**