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

SSL Socket 通信与本地 Mock Server 实践指南

SSL Socket 通信与本地 Mock Server 实践指南

一、背景概念

1.1 什么是 Socket 通信

Socket(套接字)是网络通信的基础抽象。两个程序通过 Socket 建立连接后,可以像读写文件一样发送和接收数据。

与 HTTP 的区别:

对比项HTTP原生 Socket
协议层次应用层协议(基于 TCP)传输层直接通信(TCP)
数据格式有固定的请求/响应格式(Header + Body)无固定格式,双方自行约定报文结构
连接方式短连接为主(请求-响应后断开)可以长连接或短连接
工具支持Postman、curl 等现成工具需要自己编写客户端/服务端程序
典型场景Web API、微服务银行/金融系统、硬件通信、私有协议

1.2 什么是 SSL/TLS

SSL(Secure Sockets Layer)和 TLS(Transport Layer Security)是加密通信协议,用来保证数据在网络传输中不被窃听和篡改。TLS 是 SSL 的升级版本,当前主流使用 TLS 1.2 或 TLS 1.3。

通信过程简化理解:

客户端 服务端 | | |-- 1. 发起连接(ClientHello) ------->| |<-- 2. 返回证书(ServerHello) ------| |-- 3. 验证证书是否可信 ------------> | |-- 4. 协商加密密钥 ---------------> | |<==== 5. 加密通道建立 ============> | |-- 6. 发送业务数据(加密的)-------> | |<-- 7. 接收响应数据(加密的)------| | |

关键概念:

  • 证书(Certificate):服务端的"身份证",包含公钥信息,由 CA(证书颁发机构)签发
  • 信任库(TrustStore):客户端存放"我信任的证书"的容器。客户端收到服务端证书后,会检查它是否在信任库中
  • 密钥库(KeyStore):服务端存放"自己的私钥和证书"的容器,用来证明自己的身份

1.3 什么是 JKS

JKS(Java KeyStore)是 Java 特有的密钥库文件格式,用来存储:

  • 私钥 + 对应的证书链(服务端用)
  • 信任的第三方证书(客户端用)

一个.jks文件就像一个加密的"保险箱",里面可以存多把"钥匙"(按 alias 别名区分),打开保险箱需要密码。

1.4 通信模型总结

┌─────────────┐ ┌─────────────┐ │ 客户端 │ │ 服务端 │ │ │ │ │ │ TrustStore │ ←验证证书是否可信→ │ KeyStore │ │ (ylh.jks) │ │(server.jks) │ │ │ │ │ │ SSLSocket │ ====== TLS 1.2 ===== │SSLServerSocket│ │ │ │ │ │ 发送报文 │ ------TCP数据流----→ │ 接收报文 │ │ 接收响应 │ ←----TCP数据流------ │ 发送响应 │ └─────────────┘ └─────────────┘

注:

博客:

https://blog.csdn.net/badao_liumang_qizhi

二、报文协议格式

2.1 自定义协议结构

由于是私有协议,双方约定了固定的报文格式:

[外层前缀 18字节][内层报文头 10+141=151字节][XML业务数据][MD5校验和 32字节] \_____________________169字节____________________/

客户端收到响应后的解析逻辑:

Stringresponse=rawData.substring(169,rawData.length()-32);// 跳过前169字节报文头,去掉最后32字节校验和,中间就是XML

2.2 报文各部分说明

外层前缀(18字节):

位置长度内容说明
1-33“BADAO”固定标识
4-85“1001”系统编码
9-1810“0000000xxx”后续内容总长度(补零到10位)

内层长度(10字节):

位置长度内容说明
19-2810“0000000xxx”内层报文体总长度(补零到10位)

内层报文头(141字节):

位置长度字段名示例值
1-33业务类型“TEST”
41报文版本“1”
5-95发起系统编码“1001”
10-145目标系统编码“1002”
15-184交易类型“001”
19-202操作类型“02”
21-222报文编码“01”
23-242通讯协议“01”
25-262数据协议“01”
27-4620交易流水号“12345678901234567890”
47-5610XML报文长度“0000000350”
57-7014交易时间“20260608120000”
71-722请求类型"01"请求/"02"响应
73-8816交易签名MD5签名截取
89-913附件个数“000”
92-14150预留位全"0"

XML业务数据(变长):

<?xml version="1.0" encoding="UTF-8" standalone="yes"?><scf><header><txId>交易流水号</txId><txTime>交易时间</txTime><rtCode>状态码</rtCode><message>错误信息</message></header><body><!-- 业务数据 --></body></scf>

MD5校验和(32字节):

对"内层报文头 + XML"部分做 MD5 哈希,得到 32 位十六进制大写字符串,附在最后用于校验数据完整性。


三、keytool 证书工具详解

keytool是 JDK 自带的密钥和证书管理工具,用来创建和管理 JKS 文件。

3.1 常用命令

生成密钥对(创建 KeyStore):

keytool-genkeypair\-aliasmykey\# 别名,标识这对密钥-keyalgRSA\# 算法类型(RSA 最常用)-keysize2048\# 密钥长度(2048位足够安全)-keystoreserver.jks\# 输出的 JKS 文件名-storepass123456\# JKS 文件的密码-dname"CN=localhost"\# 证书主体信息(CN=域名)-validity365# 证书有效期(天)

导出证书:

keytool-exportcert\-aliasmykey\# 要导出的别名-keystoreserver.jks\# 从哪个 JKS 导出-storepass123456\# JKS 密码-fileserver.cer# 导出的证书文件

导入证书到信任库:

keytool-importcert\-aliasmykey\# 导入后在信任库中的别名-keystoreclient.jks\# 客户端的信任库文件-storepass654321\# 信任库密码-fileserver.cer\# 要导入的证书文件-noprompt# 不询问直接导入

查看 JKS 中有哪些证书:

keytool-list\-keystoreserver.jks\-storepass123456

3.2 dname 参数说明

-dname指定证书的主体信息(Distinguished Name),格式为:

CN=Common Name, OU=Organization Unit, O=Organization, L=Locality, ST=State, C=Country

本地测试时只需要CN=localhostCN=127.0.0.1


四、完整独立示例(与业务无关)

以下是一个最简化的 SSL Socket 通信示例,包含服务端和客户端,演示整个 SSL 通信 + 自定义协议解析的过程。

4.1 项目结构

ssl-socket-demo/ ├── generate-certs.bat # Windows 证书生成脚本 ├── server.jks # 服务端密钥库(运行脚本后生成) ├── client.jks # 客户端信任库(运行脚本后生成) ├── SslServer.java # SSL 服务端 └── SslClient.java # SSL 客户端

4.2 证书生成脚本(generate-certs.bat)

@echo off echo === 生成 SSL 证书 === REM 1. 生成服务端密钥库 keytool -genkeypair -alias server -keyalg RSA -keysize 2048 -keystore server.jks -storepass changeit -dname "CN=localhost, OU=Demo, O=Demo, L=City, ST=State, C=CN" -validity 365 REM 2. 导出服务端证书 keytool -exportcert -alias server -keystore server.jks -storepass changeit -file server.cer REM 3. 将服务端证书导入客户端信任库 keytool -importcert -alias server -keystore client.jks -storepass changeit -file server.cer -noprompt REM 4. 清理证书文件 del server.cer echo === 完成 === echo 生成了 server.jks(服务端用)和 client.jks(客户端用) pause

4.3 SSL 服务端(SslServer.java)

importjavax.net.ssl.*;importjava.io.*;importjava.nio.charset.StandardCharsets;importjava.security.KeyStore;importjava.security.MessageDigest;/** * SSL Socket 服务端示例. * 监听端口,接收客户端请求,返回自定义协议格式的响应. */publicclassSslServer{privatestaticfinalintPORT=9999;privatestaticfinalStringKEYSTORE_PATH="server.jks";privatestaticfinalStringKEYSTORE_PASS="changeit";publicstaticvoidmain(String[]args)throwsException{// 1. 加载服务端密钥库KeyStoreks=KeyStore.getInstance("JKS");ks.load(newFileInputStream(KEYSTORE_PATH),KEYSTORE_PASS.toCharArray());// 2. 初始化 KeyManager(管理服务端自己的证书和私钥)KeyManagerFactorykmf=KeyManagerFactory.getInstance("SunX509");kmf.init(ks,KEYSTORE_PASS.toCharArray());// 3. 创建 SSL 上下文SSLContextsslContext=SSLContext.getInstance("TLSv1.2");sslContext.init(kmf.getKeyManagers(),null,null);// 4. 创建 SSL Server SocketSSLServerSocketFactoryfactory=sslContext.getServerSocketFactory();SSLServerSocketserverSocket=(SSLServerSocket)factory.createServerSocket(PORT);serverSocket.setEnabledProtocols(newString[]{"TLSv1.2"});System.out.println("SSL 服务端启动,监听端口: "+PORT);// 5. 循环接收客户端连接while(true){java.net.Socketclient=serverSocket.accept();System.out.println("客户端已连接: "+client.getInetAddress());InputStreaminput=client.getInputStream();OutputStreamoutput=client.getOutputStream();// 读取请求byte[]buf=newbyte[4096];intlen=input.read(buf);Stringrequest=newString(buf,0,len,StandardCharsets.UTF_8);System.out.println("收到请求: "+request);// 构造响应(自定义协议格式)StringxmlBody="<?xml version=\"1.0\" encoding=\"UTF-8\"?>"+"<response>"+"<code>200</code>"+"<message>Hello from SSL Server</message>"+"<data>This is the response data</data>"+"</response>";StringfullResponse=buildProtocolMessage(xmlBody);System.out.println("发送响应,总长度: "+fullResponse.length());output.write(fullResponse.getBytes(StandardCharsets.UTF_8));output.flush();client.close();}}/** * 按自定义协议封装报文. * 格式: [前缀18字节][内层长度10字节][报文头141字节][XML][MD5校验32字节] * 总报文头 = 18 + 10 + 141 = 169字节 */privatestaticStringbuildProtocolMessage(Stringxml)throwsException{// 141字节报文头(简化版,用固定值填充)StringBuilderheader=newStringBuilder();header.append("BIZ");// 业务类型 3位header.append("1");// 版本 1位header.append("SRVID");// 发起系统 5位header.append("CLTID");// 目标系统 5位header.append("0001");// 交易类型 4位header.append("02");// 操作类型 2位header.append("01");// 报文编码 2位header.append("01");// 通讯协议 2位header.append("01");// 数据协议 2位header.append("00000000000000000001");// 流水号 20位header.append(String.format("%010d",xml.getBytes(StandardCharsets.UTF_8).length));// XML长度 10位header.append("20260609120000");// 时间 14位header.append("02");// 响应标识 2位header.append("ABCDEF0123456789");// 签名 16位header.append("000");// 附件数 3位// 预留50位header.append("00000000000000000000000000000000000000000000000000");// 以上合计 141 字符// 内层 = 报文头 + XMLStringinnerContent=header.toString()+xml;// MD5校验和(32字符)StringcheckSum=md5(innerContent.getBytes(StandardCharsets.UTF_8));StringinnerWithChecksum=innerContent+checkSum;// 内层长度(10位)StringinnerLengthStr=String.format("%010d",innerWithChecksum.getBytes(StandardCharsets.UTF_8).length);// 外层 = "ENA" + 系统编码5位 + 外层长度10位 + 内层长度 + 内层内容StringinnerMsg=innerLengthStr+innerWithChecksum;StringouterLengthStr=String.format("%010d",innerMsg.getBytes(StandardCharsets.UTF_8).length);return"ENA"+"SRVID"+outerLengthStr+innerMsg;}privatestaticStringmd5(byte[]data)throwsException{MessageDigestmd=MessageDigest.getInstance("MD5");byte[]digest=md.digest(data);StringBuildersb=newStringBuilder();for(byteb:digest){sb.append(String.format("%02X",0xFF&b));}returnsb.toString();}}

4.4 SSL 客户端(SslClient.java)

importjavax.net.ssl.*;importjava.io.*;importjava.nio.charset.StandardCharsets;importjava.security.KeyStore;/** * SSL Socket 客户端示例. * 连接服务端,发送请求,接收并解析自定义协议格式的响应. */publicclassSslClient{privatestaticfinalStringSERVER_HOST="127.0.0.1";privatestaticfinalintSERVER_PORT=9999;privatestaticfinalStringTRUSTSTORE_PATH="client.jks";privatestaticfinalStringTRUSTSTORE_PASS="changeit";publicstaticvoidmain(String[]args)throwsException{// 1. 加载客户端信任库(包含服务端证书)KeyStorets=KeyStore.getInstance("JKS");ts.load(newFileInputStream(TRUSTSTORE_PATH),TRUSTSTORE_PASS.toCharArray());// 2. 初始化 TrustManager(验证服务端证书)TrustManagerFactorytmf=TrustManagerFactory.getInstance("SunX509");tmf.init(ts);// 3. 创建 SSL 上下文SSLContextsslContext=SSLContext.getInstance("TLSv1.2");sslContext.init(null,tmf.getTrustManagers(),null);// 4. 建立 SSL 连接SSLSocketFactoryfactory=sslContext.getSocketFactory();SSLSocketsocket=(SSLSocket)factory.createSocket(SERVER_HOST,SERVER_PORT);System.out.println("已连接到服务端: "+SERVER_HOST+":"+SERVER_PORT);// 5. 发送请求OutputStreamoutput=socket.getOutputStream();Stringrequest="Hello, this is a test request from client";output.write(request.getBytes(StandardCharsets.UTF_8));output.flush();System.out.println("已发送请求: "+request);// 6. 接收响应InputStreaminput=socket.getInputStream();byte[]buf=newbyte[4096];StringBuildersb=newStringBuilder();intlen;while((len=input.read(buf))>0){sb.append(newString(buf,0,len,StandardCharsets.UTF_8));if(len<buf.length){break;}}StringrawResponse=sb.toString();System.out.println("收到响应,总长度: "+rawResponse.length());// 7. 按协议解析响应// 跳过前169字节报文头,去掉最后32字节校验和,中间是XMLStringxml=rawResponse.substring(169,rawResponse.length()-32);System.out.println("解析出 XML:\n"+xml);socket.close();}}

4.5 运行步骤

# 1. 生成证书generate-certs.bat# 2. 编译javac SslServer.java javac SslClient.java# 3. 启动服务端(新开一个终端窗口)javaSslServer# 4. 运行客户端(另一个终端窗口)javaSslClient

4.6 预期输出

服务端:

SSL 服务端启动,监听端口: 9999 客户端已连接: /127.0.0.1 收到请求: Hello, this is a test request from client 发送响应,总长度: 452

客户端:

已连接到服务端: 127.0.0.1:9999 已发送请求: Hello, this is a test request from client 收到响应,总长度: 452 解析出 XML: <?xml version="1.0" encoding="UTF-8"?><response><code>200</code><message>Hello from SSL Server</message><data>This is the response data</data></response>

五、为什么不能用 Postman 等 HTTP 工具测试

原因说明
协议不同Postman 基于 HTTP 协议,而 Socket 通信是裸 TCP 数据流
报文格式不同HTTP 有固定的GET /path HTTP/1.1格式,Socket 私有协议是自定义的二进制/文本格式
连接方式不同HTTP 有握手过程(协议协商),Socket 直接建立 TCP + TLS 连接后收发数据
端口行为不同HTTP 服务端期望收到 HTTP 格式数据,收到私有格式会报错

所以测试这类接口只能:

  1. 写代码模拟(本文方案)
  2. 使用专业的 TCP 调试工具(如 Hercules、PacketSender)发送原始字节
  3. 抓包工具(Wireshark)观察数据

六、关键概念速查表

概念一句话解释
Socket程序之间网络通信的端点,类似"电话两端"
SSL/TLS在 Socket 上加一层加密,防窃听防篡改
JKSJava 的密钥/证书存储文件格式
KeyStore存自己的私钥+证书,服务端用来证明身份
TrustStore存信任的证书,客户端用来验证服务端身份
keytoolJDK 自带的证书管理命令行工具
Certificate数字证书,包含公钥和身份信息
MD5 checksum报文完整性校验,防止数据被篡改
报文头固定长度的元数据区域,描述报文类型和长度等信息
JAXBJava 的 XML 与对象互转框架

七、总结

整个通信链路可以概括为:

1. 客户端用 TrustStore 验证服务端证书 → 建立加密通道 2. 客户端按约定格式(报文头+XML+校验和)组装请求 → 通过加密通道发送 3. 服务端收到请求 → 处理业务 → 按同样格式组装响应 → 发回 4. 客户端收到响应 → 跳过报文头、去掉校验和 → 取出XML → 反序列化为Java对象

本地 Mock 的核心思路就是:自己启动一个 SSL Server,按照协议格式返回固定数据,让客户端以为在和真实服务端通信。

八、测试效果

http://www.jsqmd.com/news/983948/

相关文章:

  • 2026上海触点润滑脂十大供应商实力榜:六家高精度导电脂技术标杆企业的差异化优势深度解析 - 品牌发掘
  • Corrective RAG与Real-Time PPO实战:重构检索-生成时序耦合
  • CodeIsland与竞争对手对比:为什么它是AI编程助手监控的终极选择 [特殊字符]
  • 纪念币真假鉴别技巧!普通人在家就能查,杜绝高仿假货 - 深鉴新闻
  • 2026年 北京育儿嫂/月嫂服务推荐榜单:朝阳/丰台持证上岗,专业新生儿护理与产后康复口碑之选! - 企业推荐官【官方】
  • 3个技巧让你的浏览器书签管理效率提升300%
  • OpenStitching:智能图像拼接的创新突破与高效实践指南
  • 喜马拉雅音频离线神器:跨平台下载工具全面解析
  • @username 的推文
  • JN5169无线MCU低功耗设计:睡眠模式、唤醒机制与功耗优化实战
  • 2026广州白云区搬家公司综合实力TOP5排行榜:服务、价格与售后全维度评测 - 从来都是英雄出少年
  • ansys 求解过程中出现未知错误。检查“求解信息”对象上的“求解器输出”,查找可能的原因。-静力学分析遇到的,这是什么原因——An unknown error occurred ——未找到解决方法
  • 如何在Windows上安装安卓应用:APK安装器的完整指南
  • 从MySQL到云原生:全面解析阿里云PolarDB数据库及其与MySQL的核心差异
  • 卡梅德生物技术快报|纯化重组蛋白实操详解
  • MATLAB凸优化实战函数包:50+CVX兼容算子,含huber、log_det、quad_over_lin等
  • CodeIsland故障排除:10个常见问题与终极解决方案大全
  • Scala Pickling 源码解析:编译时生成与运行时反射的实现原理
  • 智能对话革命:ChatALL助你一站式管理所有AI助手
  • 动态随机一般均衡建模终极指南:40+实战模型快速掌握
  • 2026食材配送服务商口碑优选榜:六家稳扎稳打的本土供应品牌,从品控溯源到智慧物流的核心实力深度解析 - 品牌发掘
  • Finance-Python部署指南:生产环境配置与性能调优
  • Python 爬虫实战:影视网站影片信息与影评抓取全解析
  • Win32 - 进程间通信(IPC)1
  • 雷达原理与系统基础教程
  • 从SRResNet到SRGAN:一个ResNet块如何‘进化’成GAN,彻底改变图像超分的游戏规则
  • Week 3 -- Day 1:LangGraph 入门
  • QuickLook.Plugin.OfficeViewer-Native:如何通过原生Office组件实现秒级文档预览
  • 三步构建CH55xduino低成本USB微控制器开发环境
  • i.MX RT1015电气特性与接口时序实战解析:从数据手册到硬件设计