目录
代码下载地址:https://github.com/KeepTryingTo/openssl-client-server-WireShark
基于TCP协议的socket网络编程大家应该是比较熟悉的了,特别是大家在学习网络编程或者web,http服务相关项目时,这个是必须要知道的,关于基于epoll的IO多路复用大家也应该尝试去了解一下,为什么要使用epoll机制。今天这个文章内容对于很多人来说都是非常熟悉的,所以应该没有什么问题,主要在linux上基于tcpdump工具抓取客户端向服务端发送数据的过程以及到tcp最后挥手,主要有两种测试方式,一种是单纯基于TCP协议传输数据,另外一种是基于TCP和SSL/TLS协议加密传输数据的过程,通过抓包来看一下整个过程,并且还给出了怎么生成私钥以及证书。
代码编译
cd simple_cli_ser
make
cd bin
./server #启动服务端
./client #启动客户端
Linux上基于tcpdump抓包
tcpdump抓包命令
sudo tcpdump -i any -w tcp.pcap -v host 127.0.0.1 and port 8080 and tcp #本项目使用的这一条就可以了
# 同时显示抓包信息到控制台
sudo tcpdump -i any -w tcp.pcap -v host 127.0.0.1 and port 8080 and tcp
# 限制抓包数量(例如只抓100个包)
sudo tcpdump -i any -w tcp.pcap -c 100 host 127.0.0.1 and port 8080 and tcp
# 指定网络接口(如只监控回环接口)
sudo tcpdump -i lo -w tcp.pcap host 127.0.0.1 and port 8080 and tcp
tcpdump抓包参数解析
| 参数/表达式 | 说明 |
|---|---|
| sudo | 需要管理员权限才能捕获网络数据包 |
| tcpdump | 抓包工具命令 |
| -i any | 监听所有网络接口(包括lo回环接口) |
| -w tcp.pcap | 将抓包数据保存到 tcp.pcap 文件中 |
| host 127.0.0.1 | 过滤主机IP为127.0.0.1(本地回环) |
| port 8080 | 过滤端口为8080 |
| tcp | 只捕获TCP协议的数据包 |
| -v | 显示抓包信息到控制台 |
| -c 100 | 指定只抓100个包 |
| -i lo | 指定网络接口(只监控回环接口) |
抓包流程
- 开启第一个终端:首先启动服务器,比如./server
- 开启第二个终端:其次输入tcpdump抓包命令
- 开启第三个终端:最后启动客户端,比如./client
wireshark抓包分析
- SYN 表示建立连接,
- FIN 表示关闭连接,
- ACK 表示响应,
- PSH 表示有 DATA 数据传输,
- RST 表示连接重置。
单纯基于TCP协议传输数据过程

注意:
从TCP三次握手,然后进行数据传输以及ACK确认,到最后的TCP四次挥手合并为三次挥手(TCP延迟确认机制开启),这个整个过程就是一个典型的TCP连接到数据传输以及断开连接的过程。
大家看到的上面TCP握手过程的序列号是从0开始的,这只是因为wireshark工具使用相对序列号进行表示的,因此,在消息栏右击 → 协议首选项 → Transmission Control Protocol → 点击Relative sequence numbers ...就可以看到绝对的序列号了。 其实使用相对序列号也可以,并不妨碍我们观察 。


注:从抓包的内容来看,确实是首相客户端向服务端发送hello,server消息之后,其次服务端向客户端会送消息hello,server,由于是基于TCP数据传输,整个过程都是明文传输。
TCP协议 + SSL/TLS(安全套接层)协议
OpenSSL库
OpenSSL 是一个功能完备的、商业级的、开源的工具包,实现了 SSL(Secure Sockets Layer) 和 TLS(Transport Layer Security) 协议。它提供了一个强大的通用密码学库,用于保护网络通信的安全。
OpenSSL 项目主要包含三个核心组件:
1. libcrypto- 密码学库 这是 OpenSSL 的基础和灵魂。它是一个提供各种加密、解密、哈希、数字签名等底层操作的通用密码学库。
- 对称加密:AES, DES, 3DES, Blowfish, ChaCha20 等。
- 非对称加密(公钥加密):RSA, DSA, Diffie-Hellman, EC(椭圆曲线)等。
- 哈希函数:MD5, SHA-1, SHA-256, SHA-3 等。
- 数字证书:X.509 证书的编码、解码和管理。
- 随机数生成:提供加密安全的伪随机数生成器(CSPRNG)。
- 编码转换:支持 ASN.1, PEM, DER 等格式的编解码。
2. libssl- SSL/TLS 协议库 这个库建立在 libcrypto之上,实现了 SSL 和 TLS 协议。它提供了客户端和服务器端的 API,用于创建安全的加密通信通道。
- 处理复杂的握手过程(密钥交换、身份验证)。
- 建立连接后的应用数据加密传输。
- 提供高级别的 API(如
SSL_read,SSL_write),让开发者无需深入理解密码学细节也能轻松实现安全通信。
3. openssl- 命令行工具 这是一个极其强大的多功能命令行工具,可以单独使用,无需编程。
- 生成密钥和证书:
openssl genrsa,openssl req - 模拟客户端/服务器:
openssl s_client,openssl s_server - 数据加密解密:
openssl enc - 计算哈希值:
openssl dgst - 诊断和调试:检查证书、测试连接等。
编程接口(API)
OpenSSL 通常书写步骤:
- 初始化库:
SSL_library_init() - 创建上下文(CTX):
SSL_CTX_new(),用于存储全局设置和证书。 - 绑定 socket:将网络 socket 与 SSL 结构关联。
- 执行 SSL/TLS 握手:
SSL_connect()(客户端) 或SSL_accept()(服务器)。 - 安全通信:使用
SSL_read()和SSL_write()替代普通的read()和write()。 - 清理:关闭连接并释放资源。
自签证书和正式证书有什么区别呢
| 特性 | 自签证书 | 正式证书(CA签发) |
|---|---|---|
| 签发机构 | 自己签发 | 受信任的证书颁发机构(CA)如 Let's Encrypt、DigiCert |
| 信任链 | 无第三方背书,需手动信任 | 自动嵌入浏览器/操作系统的信任链 |
| 加密强度 | 与正式证书相同(取决于密钥算法和长度) | 与自签证书相同 |
| 浏览器警告 | 显示"不安全"警告 | 显示绿色锁标志(EV证书显示公司名称) |
| 有效期 | 可自定义(通常较长) | 通常较短(如90天~1年) |
| 适用场景 | 内部测试、开发环境、内网服务 | 公网网站、商业服务、API接口 |
私钥和证书(自签证书)生成命令
# 生成私钥(保存为 .pem 格式)
openssl genrsa -out server.pem 2048
# 生成自签名证书(保存为 .pem 格式)
openssl req -new -x509 -sha256 -key server.pem -out cert.pem -days 3650
# 查看证书信息(包含了签名算法,生成证书时填写的信息等)
openssl x509 -in cert.pem -noout -text
openssl genrsa -out server.pem 2048
| 参数 | 说明 |
|---|---|
|
| 生成RSA私钥 |
|
| 指定输出文件为 |
|
| 密钥长度(位),推荐2048或4096 |
openssl req -new -x509 -sha256 -key server.pem -out cert.pem -days 3650
| 参数 | 说明 |
|---|---|
|
| 证书请求和生成命令 |
|
| 创建新证书请求 |
|
| 生成自签名证书(而非证书请求) |
|
| 使用SHA-256哈希算法 |
|
| 指定使用的私钥文件 |
|
| 输出证书文件 |
|
| 证书有效期(10年) |
openssl x509 -in cert.pem -noout -text
| 参数 | 说明 |
|---|---|
|
| 处理X.509证书 |
|
| 指定输入证书文件 |
|
| 不输出证书本身(只输出解析信息) |
|
| 以可读格式显示完整证书信息 |
验证密钥与证书是否匹配
验证密钥与证书是否匹配
openssl x509 -noout -modulus -in cert.pem | openssl md5
输出:(stdin)= 8b92476c6177fff45071d3c55b67b54b
openssl rsa -noout -modulus -in server.pem | openssl md5
输出:(stdin)= 8b92476c6177fff45071d3c55b67b54b
如果要加密私钥的话
命令:openssl genrsa -aes256 -out server.pem 2048
提示:
Enter pass phrase for server.pem: [输入密码]
Verifying - Enter pass phrase for server.pem: [确认密码]
验证加密
命令:file server.pem # 会显示"PEM RSA private key"
命令:openssl rsa -in server.pem -check # 会提示输入密码
移除私钥密码
openssl rsa -in server.pem -out server_unencrypted.pem
| 参数 | 作用 |
|---|---|
|
| 使用AES-256算法加密私钥(推荐) |
|
| 输出加密后的私钥文件 |
|
| RSA密钥长度(位) |
| 其他加密算法:-des3,-camellia256 | |
抓包结果

注:首先进行三次握手,其次是SSL/TLS四次握手,后面是发送,最后是断开连接

大家可能看到上面在最后客户端向服务端发送了RST报文,说明异常终止了,需要再客户端最后更新如下代码:
分析原因:从抓包图可以清晰地看到问题所在:
- No.15: 服务端发送了最后的 Application Data
- No.16: 客户端发送
[FIN, ACK],表示"我的数据已发完,想要关闭连接"- No.17: 但服务端的
while循环中的SSL_write又尝试发送数据- No.18: 客户端已经准备关闭连接,收到"意外"数据,只能发送
[RST]强制重置
根源在于服务端的这段代码:当服务端接收来自客户端的数据之后,马上回显,但是客户端那边已经关闭,导致最终客户端发送RST报文来强制断开连接。
/* 处理客户端数据 */
int bytes_received;
while ((bytes_received = SSL_read(ssl, buffer, BUFFER_SIZE)) > 0)
{
buffer[bytes_received] = '\0';
printf("Received: %s\n", buffer);
/* 回显数据 */
SSL_write(ssl, buffer, bytes_received);
}
修改后的代码:
// 只读取数据,不立即回显
bytes_received = SSL_read(ssl, complete_buffer, sizeof(complete_buffer));
complete_buffer[bytes_received] = '\0';
printf("recv data: %s\n", complete_buffer);
// 读取完成后,一次性回显所有数据
if (bytes_received > 0)
{
printf("Complete message: %s\n", complete_buffer);
SSL_write(ssl, complete_buffer, bytes_received);
printf("Echoed %d bytes back to client\n", bytes_received);
// 等待客户端关闭连接
printf("Waiting for client to close connection...\n");
while (SSL_read(ssl, buffer, BUFFER_SIZE) > 0)
{
// 空循环,等待客户端关闭(客户端那边接收完数据之后就会断开连接)
}
printf("Client closed connection\n");
}


注:第一次握手的信息,关于SSL/TLS协议的四次握手过程网上有很多讲解,这里就不过多的赘述了。

可以看到整个数据都是加密的,看不到具体发送了什么数据。
发送数据过程中会同时有PSH和 ACK两个标识?
答:这个组合 [PSH, ACK]理解为:“我这边有新的数据要立刻交给你(PSH),并且我也确认收到了你之前发来的数据(ACK)”,比如以上面传输数据为例子:
- No.4:
38008 → 8080 [PSH, ACK] Seq=1 Ack=1 Win=65536 Len=283- •
[PSH]:客户端(38008端口)告诉服务器(8080端口):“我这里有283字节的应用数据(比如一个HTTP请求),请立即处理”。 - •
[ACK]:客户端同时告诉服务器:“你之前发来的所有数据(比如握手阶段的包)我都收到了,一切正常。我下一个期望收到的序列号是Ack=1”。
- •
- No.6:
8080 → 38008 [PSH, ACK] Seq=1 Ack=284 Win=65536 Len=1421- •
[PSH]:服务器处理完客户端的请求后,生成了1421字节的响应数据(比如一个HTTP响应),并立即推送回去。 - •
[ACK]:服务器同时告诉客户端:“你之前发来的283字节数据我已经收到了,下一个期望收到的序列号是Ack=284(即Seq=1 + Len=283)”。
- •
TCP协议这样设计,主要是为了效率和可靠性的极致结合:
- 减少报文数量(捎带确认 - Piggybacking):
- 理论上,发送数据和确认收到数据可以是两个独立的TCP包。
- 但这样效率太低。如果一方正好有数据要发送 (
PSH),而它又需要确认对方之前发来的数据 (ACK),那么完全可以将这两个信息合并到一个包里发送。这大大减少了网络上的报文数量,提升了效率。
- 及时交付与流量控制:
PSH标志位就像一个“刷新”按钮。没有它,接收方的TCP栈可能会为了优化而暂时将数据缓存起来,等待凑够一个更大的数据块再提交给应用程序,这会引入延迟。- 对于交互式应用(如SSH、HTTP请求/响应),及时交付至关重要。
PSH标志确保了低延迟,让应用程序能立刻对收到的数据做出反应。
想象两个人在用对讲机对话:
- A:“Over.(我说完了)”(这就像
PSH,表示我有一段完整的信息给你) - B:“Roger that. I need backup!(收到。我需要支援!)”(这就像一个
[PSH, ACK]包。Roger that是ACK,确认收到了A的话;I need backup!是PSH,是B要发送的新信息)
参考链接
代码下载地址:https://github.com/yedf2/openssl-example
client-server代码来源:https://space.bilibili.com/193137215?spm_id_from=333.788.upinfo.head.click
关于抓包工具Wireshark使用:https://blog.youkuaiyun.com/qq_39720249/article/details/128157288
基于TCP的四次挥手调用shutdown和close函数区别:
https://blog.youkuaiyun.com/SteveForever/article/details/140638476
378

被折叠的 条评论
为什么被折叠?



