
本文将从“底层逻辑拆解+权威标准解读+可落地实战示例”三个维度,用通俗的语言讲透OSI七层模型的每一个细节。所有内容均参考ISO/IEC 7498-1官方标准(OSI模型的权威定义),核心论点100%有据可依;实战示例基于Java语言实现,确保可直接编译运行;同时针对易混淆技术点进行明确区分,帮你真正做到“知其然,更知其所以然”。
在OSI模型诞生之前,计算机网络领域处于“群雄割据”的状态:不同厂商(如IBM、DEC)都有自己的网络体系结构,这些体系结构之间互不兼容——比如IBM的SNA网络和DEC的DNA网络,无法直接实现数据互通。这就导致了一个严重的问题:企业如果选择了某一厂商的网络设备,就必须依赖该厂商的全套解决方案,不仅成本高昂,还严重限制了网络技术的发展。
为了解决“兼容性”这一核心痛点,国际标准化组织(ISO)在1984年发布了ISO/IEC 7498-1标准,正式提出了开放式系统互联(Open Systems Interconnection,OSI)参考模型。其核心目标是:将网络通信的复杂流程,拆解为7个相互独立但又协同工作的层次,每个层次负责特定的功能,通过标准化的接口和协议,实现不同厂商、不同体系结构网络的互联互通。
简单来说,OSI七层模型就像“网络通信的通用语言”——无论底层使用的是网线、光纤还是无线信号,无论上层运行的是HTTP、FTP还是自定义协议,都可以按照这7个层次的规范进行通信,从而打破了厂商壁垒,推动了网络技术的标准化和普及化。
这里需要明确一个关键认知:OSI模型是“参考模型”,而非“实际实现”。现实中广泛应用的是TCP/IP模型(如互联网的核心架构),但TCP/IP模型是在OSI模型的基础上简化而来的(TCP/IP为4层或5层结构)。理解OSI模型,是掌握所有网络技术的前提——它能帮你建立完整的网络知识框架,让后续学习各类协议时不再“碎片化”。
OSI七层模型从下到上依次为:物理层(Layer 1)、数据链路层(Layer 2)、网络层(Layer 3)、传输层(Layer 4)、会话层(Layer 5)、表示层(Layer 6)、应用层(Layer 7)。

网络通信的本质,是“数据”在发送端从上层到下层的“封装”过程,以及在接收端从下层到上层的“解封装”过程。

物理层是OSI模型的最底层,负责定义网络物理介质的电气特性、机械特性、功能特性和规程特性,实现比特流的透明传输。
物理层的状态判断依赖操作系统底层API,Java本身无法直接访问,需通过JNA(Java Native Access)调用系统函数。以下示例适用于Windows系统,可判断指定网卡的物理连接状态(是否插网线)。
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>5.14.0</version>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna-platform</artifactId>
<version>5.14.0</version>
</dependency>
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Structure;
import com.sun.jna.platform.win32.WinDef;
import com.sun.jna.win32.W32APIOptions;
import java.util.Arrays;
import java.util.List;
/**
* 物理层实战:判断Windows网卡物理连接状态
*/
publicclass PhysicalLayerStatusChecker {
// 定义Windows系统的IP Helper库接口
publicinterface IpHlpApi extends Library {
IpHlpApi INSTANCE = Native.load("iphlpapi", IpHlpApi.class, W32APIOptions.DEFAULT_OPTIONS);
// 网卡状态结构体
class MIB_IFROW extends Structure {
public WinDef.DWORD dwIndex;
publicbyte[] bDescr = newbyte[256];
publicbyte[] bPhysAddr = newbyte[8];
public WinDef.DWORD dwPhysAddrLen;
public WinDef.DWORD dwType;
public WinDef.DWORD dwMtu;
public WinDef.DWORD dwSpeed;
public WinDef.DWORD dwFlags;
public WinDef.DWORD dwOperStatus; // 操作状态:1=Up,2=Down,3=Testing...
@Override
protected List<String> getFieldOrder() {
return Arrays.asList("dwIndex", "bDescr", "bPhysAddr", "dwPhysAddrLen", "dwType", "dwMtu", "dwSpeed", "dwFlags", "dwOperStatus");
}
}
// 获取指定索引的网卡信息
int GetIfEntry(MIB_IFROW pIfRow);
}
public static void main(String[] args) {
// 遍历网卡索引(1~100,实际可根据系统调整范围)
for (int index = 1; index <= 100; index++) {
IpHlpApi.MIB_IFROW ifRow = new IpHlpApi.MIB_IFROW();
ifRow.dwIndex = new WinDef.DWORD(index);
int result = IpHlpApi.INSTANCE.GetIfEntry(ifRow);
if (result == 0) { // 成功获取网卡信息
String descr = new String(ifRow.bDescr).trim(); // 网卡描述(如"以太网")
String physAddr = bytesToMac(ifRow.bPhysAddr, ifRow.dwPhysAddrLen.intValue()); // MAC地址
int operStatus = ifRow.dwOperStatus.intValue(); // 操作状态
String statusDesc = getStatusDesc(operStatus);
// 过滤掉虚拟网卡(如VPN、虚拟机网卡),只关注物理网卡
if (!descr.contains("Virtual") && !descr.contains("VPN") && !descr.contains("VMware")) {
System.out.printf("网卡描述:%s%n", descr);
System.out.printf("MAC地址:%s%n", physAddr);
System.out.printf("物理连接状态:%s(%d)%n", statusDesc, operStatus);
System.out.println("----------------------------------------");
}
}
}
}
// 将字节数组转换为MAC地址字符串(如00:1A:2B:3C:4D:5E)
private static String bytesToMac(byte[] bytes, int len) {
if (len <= 0 || len > bytes.length) {
return"";
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < len; i++) {
sb.append(String.format("%02X", bytes[i]));
if (i < len - 1) {
sb.append(":");
}
}
return sb.toString();
}
// 解析操作状态描述
private static String getStatusDesc(int status) {
switch (status) {
case1:
return"已连接(Up)- 物理链路正常";
case2:
return"已断开(Down)- 物理链路中断(如未插网线)";
case3:
return"测试中(Testing)";
case4:
return"未知(Unknown)";
case5:
return"休眠(Dormant)";
case6:
return"未就绪(NotPresent)";
case7:
return"低功率(LowerLayerDown)";
default:
return"未定义状态";
}
}
}
iphlpapi.dll库中的GetIfEntry函数,获取网卡的详细信息,其中dwOperStatus字段表示网卡的操作状态:1表示物理链路正常(已插网线),2表示物理链路中断(未插网线)。ioctl函数)。数据链路层位于物理层之上,负责将物理层传输的比特流封装为帧,实现相邻两个节点(如两台直接连接的计算机、计算机与交换机)之间的可靠数据传输,同时处理物理层的传输错误(如比特翻转)。
以太网帧是数据链路层最核心的帧格式,标准结构如下(单位:字节):
字段名称 | 长度 | 功能说明 |
|---|---|---|
前导码(Preamble) | 7 | 同步信号,告知接收端即将有数据传输(由1和0交替组成,共56位)。 |
帧起始定界符(SFD) | 1 | 标记帧的正式开始(二进制10101011)。 |
目的MAC地址(DA) | 6 | 接收方设备的MAC地址(全1为广播地址,即FF:FF:FF:FF:FF:FF)。 |
源MAC地址(SA) | 6 | 发送方设备的MAC地址。 |
类型/长度(Type/Length) | 2 | 若值≥0x0600,标识上层协议类型(如0x0800=IP协议,0x0806=ARP协议);若值<0x0600,标识帧的数据部分长度。 |
数据(Data) | 46~1500 | 来自网络层的数据包(最小46字节,不足时填充;最大1500字节,即MTU=1500)。 |
帧校验序列(FCS) | 4 | CRC-32校验码,用于检测帧在传输过程中是否损坏。 |
以下示例模拟以太网帧的封装(发送端)和解封装(接收端)过程,严格遵循IEEE 802.3标准的帧结构。
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
/**
* 数据链路层实战:以太网帧的封装与解析
*/
publicclass EthernetFrameDemo {
// 以太网帧字段长度常量(单位:字节)
privatestaticfinalint PREAMBLE_LENGTH = 7;
privatestaticfinalint SFD_LENGTH = 1;
privatestaticfinalint MAC_ADDRESS_LENGTH = 6;
privatestaticfinalint TYPE_LENGTH = 2;
privatestaticfinalint FCS_LENGTH = 4;
privatestaticfinalint MIN_DATA_LENGTH = 46;
privatestaticfinalint MAX_DATA_LENGTH = 1500;
// 协议类型常量
publicstaticfinalshort PROTOCOL_IP = 0x0800; // IP协议
publicstaticfinalshort PROTOCOL_ARP = 0x0806; // ARP协议
/**
* 封装以太网帧
* @param srcMac 源MAC地址(如"00:1A:2B:3C:4D:5E")
* @param destMac 目的MAC地址(如"FF:FF:FF:FF:FF:FF")
* @param protocolType 上层协议类型(PROTOCOL_IP/PROTOCOL_ARP)
* @param data 网络层数据(数据包)
* @return 完整的以太网帧字节数组
*/
publicstaticbyte[] encapsulateEthernetFrame(String srcMac, String destMac, short protocolType, byte[] data) {
// 1. 验证数据长度(不足46字节填充,超过1500字节报错)
byte[] dataFilled = newbyte[Math.max(data.length, MIN_DATA_LENGTH)];
System.arraycopy(data, 0, dataFilled, 0, data.length);
if (data.length > MAX_DATA_LENGTH) {
thrownew IllegalArgumentException("数据长度超过MTU(1500字节)");
}
// 2. 计算帧总长度
int frameTotalLength = PREAMBLE_LENGTH + SFD_LENGTH + MAC_ADDRESS_LENGTH * 2 + TYPE_LENGTH + dataFilled.length + FCS_LENGTH;
ByteBuffer frameBuffer = ByteBuffer.allocate(frameTotalLength);
// 3. 写入前导码(0x55重复7次,即10101010交替)
for (int i = 0; i < PREAMBLE_LENGTH; i++) {
frameBuffer.put((byte) 0x55);
}
// 4. 写入帧起始定界符(0xD5,即10101011)
frameBuffer.put((byte) 0xD5);
// 5. 写入目的MAC地址
frameBuffer.put(macStringToBytes(destMac));
// 6. 写入源MAC地址
frameBuffer.put(macStringToBytes(srcMac));
// 7. 写入协议类型(大端序,网络字节序)
frameBuffer.putShort(protocolType);
// 8. 写入数据(填充后的数据)
frameBuffer.put(dataFilled);
// 9. 计算并写入FCS(CRC-32校验码)
byte[] frameWithoutFcs = newbyte[frameBuffer.position()];
frameBuffer.rewind();
frameBuffer.get(frameWithoutFcs);
int crc = calculateCRC32(frameWithoutFcs);
frameBuffer.putInt(crc);
return frameBuffer.array();
}
/**
* 解封装以太网帧
* @param frame 完整的以太网帧字节数组
* @return 解封装后的结果(包含源MAC、目的MAC、协议类型、数据)
*/
public static EthernetFrameDecapsulateResult decapsulateEthernetFrame(byte[] frame) {
ByteBuffer frameBuffer = ByteBuffer.wrap(frame);
// 1. 跳过前导码和SFD
frameBuffer.position(PREAMBLE_LENGTH + SFD_LENGTH);
// 2. 读取目的MAC地址
byte[] destMacBytes = newbyte[MAC_ADDRESS_LENGTH];
frameBuffer.get(destMacBytes);
String destMac = macBytesToString(destMacBytes);
// 3. 读取源MAC地址
byte[] srcMacBytes = newbyte[MAC_ADDRESS_LENGTH];
frameBuffer.get(srcMacBytes);
String srcMac = macBytesToString(srcMacBytes);
// 4. 读取协议类型(大端序)
short protocolType = frameBuffer.getShort();
// 5. 读取数据(排除FCS)
int dataLength = frame.length - (PREAMBLE_LENGTH + SFD_LENGTH + MAC_ADDRESS_LENGTH * 2 + TYPE_LENGTH + FCS_LENGTH);
byte[] data = newbyte[dataLength];
frameBuffer.get(data);
// 6. 读取并验证FCS
int fcs = frameBuffer.getInt();
byte[] frameWithoutFcs = newbyte[frame.length - FCS_LENGTH];
System.arraycopy(frame, 0, frameWithoutFcs, 0, frameWithoutFcs.length);
int calculatedCrc = calculateCRC32(frameWithoutFcs);
boolean isFrameValid = (calculatedCrc == fcs);
returnnew EthernetFrameDecapsulateResult(srcMac, destMac, protocolType, data, isFrameValid);
}
/**
* MAC地址字符串转字节数组(如"00:1A:2B:3C:4D:5E" -> 字节数组)
*/
privatestaticbyte[] macStringToBytes(String mac) {
String[] macParts = mac.split(":");
if (macParts.length != 6) {
thrownew IllegalArgumentException("无效的MAC地址格式");
}
byte[] macBytes = newbyte[6];
for (int i = 0; i < 6; i++) {
macBytes[i] = (byte) Integer.parseInt(macParts[i], 16);
}
return macBytes;
}
/**
* MAC地址字节数组转字符串(如字节数组 -> "00:1A:2B:3C:4D:5E")
*/
private static String macBytesToString(byte[] macBytes) {
if (macBytes.length != 6) {
thrownew IllegalArgumentException("无效的MAC地址字节数组长度");
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 6; i++) {
sb.append(String.format("%02X", macBytes[i]));
if (i < 5) {
sb.append(":");
}
}
return sb.toString();
}
/**
* 计算CRC-32校验码(符合以太网FCS标准)
*/
private static int calculateCRC32(byte[] data) {
int crc = 0xFFFFFFFF; // 初始值
for (byte b : data) {
crc ^= (b & 0xFF);
for (int i = 0; i < 8; i++) {
if ((crc & 0x00000001) != 0) {
crc = (crc >> 1) ^ 0xEDB88320; // 多项式
} else {
crc >>= 1;
}
}
}
return ~crc; // 取反
}
/**
* 解封装结果封装类
*/
publicstaticclass EthernetFrameDecapsulateResult {
privatefinal String srcMac;
privatefinal String destMac;
privatefinalshort protocolType;
privatefinalbyte[] data;
privatefinalboolean isFrameValid;
public EthernetFrameDecapsulateResult(String srcMac, String destMac, short protocolType, byte[] data, boolean isFrameValid) {
this.srcMac = srcMac;
this.destMac = destMac;
this.protocolType = protocolType;
this.data = data;
this.isFrameValid = isFrameValid;
}
// getter方法
public String getSrcMac() { return srcMac; }
public String getDestMac() { return destMac; }
public short getProtocolType() { return protocolType; }
publicbyte[] getData() { return data; }
public boolean isFrameValid() { return isFrameValid; }
}
// 测试方法
public static void main(String[] args) {
try {
// 1. 准备测试数据(模拟网络层IP数据包,这里用字符串模拟)
String ipDataStr = "This is a test IP packet data.";
byte[] ipData = ipDataStr.getBytes(StandardCharsets.UTF_8);
// 2. 封装以太网帧
String srcMac = "00:1A:2B:3C:4D:5E";
String destMac = "FF:FF:FF:FF:FF:FF"; // 广播地址
byte[] ethernetFrame = encapsulateEthernetFrame(srcMac, destMac, PROTOCOL_IP, ipData);
System.out.println("以太网帧封装完成,帧总长度:" + ethernetFrame.length + "字节");
// 3. 解封装以太网帧
EthernetFrameDecapsulateResult result = decapsulateEthernetFrame(ethernetFrame);
System.out.println("\n以太网帧解封装结果:");
System.out.println("源MAC地址:" + result.getSrcMac());
System.out.println("目的MAC地址:" + result.getDestMac());
System.out.println("上层协议类型:" + (result.getProtocolType() == PROTOCOL_IP ? "IP协议(0x0800)" : "其他协议"));
System.out.println("帧有效性:" + (result.isFrameValid() ? "有效(CRC校验通过)" : "无效(CRC校验失败)"));
System.out.println("解封装后的数据(网络层数据包):" + new String(result.getData(), StandardCharsets.UTF_8).trim());
// 4. 测试CRC校验失败场景(修改帧数据)
ethernetFrame[10] = (byte) 0xAA; // 篡改目的MAC地址的一个字节
EthernetFrameDecapsulateResult invalidResult = decapsulateEthernetFrame(ethernetFrame);
System.out.println("\n篡改帧数据后的解封装结果:");
System.out.println("帧有效性:" + (invalidResult.isFrameValid() ? "有效" : "无效(CRC校验失败)"));
} catch (Exception e) {
e.printStackTrace();
}
}
}
ARP协议是数据链路层的核心协议之一,用于将IP地址解析为MAC地址(因为数据链路层需要MAC地址才能实现相邻节点的通信)。以下示例实现ARP请求的发送和ARP响应的解析。
<dependency>
<groupId>org.pcap4j</groupId>
<artifactId>pcap4j-core</artifactId>
<version>1.8.2</version>
</dependency>
<dependency>
<groupId>org.pcap4j</groupId>
<artifactId>pcap4j-packet</artifactId>
<version>1.8.2</version>
</dependency>
<dependency>
<groupId>org.pcap4j</groupId>
<artifactId>pcap4j-nio</artifactId>
<version>1.8.2</version>
</dependency>
import org.pcap4j.core.*;
import org.pcap4j.packet.ArpPacket;
import org.pcap4j.packet.EthernetPacket;
import org.pcap4j.packet.Packet;
import org.pcap4j.packet.namednumber.ArpHardwareType;
import org.pcap4j.packet.namednumber.ArpOperation;
import org.pcap4j.packet.namednumber.EtherType;
import org.pcap4j.util.ByteArrays;
import org.pcap4j.util.MacAddress;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.concurrent.TimeoutException;
/**
* 数据链路层实战:ARP协议实现(IP地址解析为MAC地址)
*/
publicclass ArpProtocolDemo {
// 超时时间(单位:毫秒)
privatestaticfinalint TIMEOUT = 5000;
// 发送ARP请求的次数
privatestaticfinalint SEND_COUNT = 3;
/**
* 发送ARP请求,解析目标IP对应的MAC地址
* @param ifName 网卡名称(如Windows的"以太网",Linux的"eth0")
* @param srcIp 本地IP地址
* @param srcMac 本地MAC地址
* @param targetIp 目标IP地址(需要解析的IP)
* @return 目标IP对应的MAC地址,若解析失败返回null
* @throws PcapNativeException
* @throws NotOpenException
* @throws InterruptedException
* @throws TimeoutException
*/
public static MacAddress sendArpRequest(String ifName, Inet4Address srcIp, MacAddress srcMac, Inet4Address targetIp)
throws PcapNativeException, NotOpenException, InterruptedException, TimeoutException {
// 1. 获取网卡设备
PcapNetworkInterface nif = Pcaps.getDevByName(ifName);
if (nif == null) {
thrownew IllegalArgumentException("未找到网卡:" + ifName);
}
// 2. 打开网卡,设置快照长度(抓取帧的最大长度)和超时时间
int snapLen = 65536;
PcapHandle handle = nif.openLive(snapLen, PcapNetworkInterface.PromiscuousMode.PROMISCUOUS, TIMEOUT);
try {
// 3. 构建ARP请求包
// 3.1 构建ARP头部
ArpPacket.ArpHeader arpHeader = new ArpPacket.ArpHeader.Builder()
.hardwareType(ArpHardwareType.ETHERNET) // 硬件类型:以太网
.protocolType(EtherType.IPV4) // 协议类型:IPV4
.hardwareAddrLength((byte) MacAddress.SIZE_IN_BYTES) // MAC地址长度:6字节
.protocolAddrLength((byte) ByteArrays.INET4_ADDRESS_SIZE_IN_BYTES) // IP地址长度:4字节
.operation(ArpOperation.REQUEST) // 操作类型:请求
.senderHardwareAddr(srcMac) // 发送方MAC地址
.senderProtocolAddr(srcIp) // 发送方IP地址
.targetHardwareAddr(MacAddress.ETHER_BROADCAST_ADDRESS) // 目标MAC地址(广播)
.targetProtocolAddr(targetIp) // 目标IP地址
.build();
// 3.2 构建以太网帧头部(目的MAC为广播地址)
EthernetPacket.EthernetHeader etherHeader = new EthernetPacket.EthernetHeader.Builder()
.dstAddr(MacAddress.ETHER_BROADCAST_ADDRESS) // 广播地址
.srcAddr(srcMac) // 发送方MAC地址
.type(EtherType.ARP) // 帧类型:ARP
.build();
// 3.3 构建完整的ARP数据包(以太网帧 + ARP包)
Packet arpPacket = new EthernetPacket.Builder()
.header(etherHeader)
.payload(new ArpPacket.Builder().header(arpHeader).build())
.build();
// 4. 发送ARP请求
for (int i = 0; i < SEND_COUNT; i++) {
handle.sendPacket(arpPacket);
System.out.printf("第%d次发送ARP请求:目标IP=%s,广播地址=%s%n",
i + 1, targetIp.getHostAddress(), MacAddress.ETHER_BROADCAST_ADDRESS);
Thread.sleep(1000); // 间隔1秒发送一次
}
// 5. 捕获ARP响应包
long startTime = System.currentTimeMillis();
while (System.currentTimeMillis() - startTime < TIMEOUT) {
Packet packet = handle.getNextPacketEx(); // 捕获下一个包
if (packet.contains(ArpPacket.class) && packet.contains(EthernetPacket.class)) {
// 解析ARP包
ArpPacket receivedArpPacket = packet.get(ArpPacket.class);
ArpPacket.ArpHeader receivedArpHeader = receivedArpPacket.getHeader();
// 验证是否是目标IP的ARP响应
if (receivedArpHeader.getOperation() == ArpOperation.REPLY
&& receivedArpHeader.getSenderProtocolAddr().equals(targetIp)
&& receivedArpHeader.getTargetProtocolAddr().equals(srcIp)) {
MacAddress targetMac = receivedArpHeader.getSenderHardwareAddr();
System.out.printf("ARP解析成功:目标IP=%s 对应的MAC地址=%s%n",
targetIp.getHostAddress(), targetMac);
return targetMac;
}
}
}
// 超时未收到响应
System.out.println("ARP解析失败:超时未收到目标IP的响应");
returnnull;
} finally {
handle.close(); // 关闭PcapHandle
}
}
// 测试方法
public static void main(String[] args) {
try {
// 1. 配置参数(需根据实际环境修改)
String ifName = "以太网"; // 网卡名称(Windows系统)
Inet4Address srcIp = (Inet4Address) InetAddress.getByName("192.168.1.100"); // 本地IP
MacAddress srcMac = MacAddress.getByName("00:1A:2B:3C:4D:5E"); // 本地MAC
Inet4Address targetIp = (Inet4Address) InetAddress.getByName("192.168.1.1"); // 目标IP(如路由器IP)
// 2. 发送ARP请求,解析MAC地址
MacAddress targetMac = sendArpRequest(ifName, srcIp, srcMac, targetIp);
if (targetMac != null) {
System.out.println("最终解析结果:" + targetIp.getHostAddress() + " -> " + targetMac);
} else {
System.out.println("解析失败");
}
} catch (UnknownHostException e) {
System.err.println("无效的IP地址:" + e.getMessage());
} catch (PcapNativeException e) {
System.err.println("Pcap库调用失败:" + e.getMessage());
} catch (NotOpenException e) {
System.err.println("网卡未打开:" + e.getMessage());
} catch (InterruptedException e) {
System.err.println("线程中断:" + e.getMessage());
} catch (TimeoutException e) {
System.err.println("捕获数据包超时:" + e.getMessage());
} catch (Exception e) {
e.printStackTrace();
}
}
}
ifName(网卡名称)、srcIp(本地IP)、srcMac(本地MAC)、targetIp(目标IP)为实际环境的参数。网络层位于数据链路层之上,负责实现跨网络(不同网段)的端到端数据传输,核心是通过路由协议选择最佳路径,将数据包从源主机发送到目的主机。
IPv4数据包是网络层最核心的数据单元,标准结构如下(单位:字节):
字段名称 | 长度 | 功能说明 |
|---|---|---|
版本(Version) | 4位 | IP协议版本(IPv4为4,IPv6为6)。 |
头部长度(IHL) | 4位 | IP头部的长度(单位:32位字,即4字节),最小值为5(20字节),最大值为15(60字节)。 |
服务类型(TOS) | 8位 | 用于指定服务质量(QoS),如优先级、延迟要求等。 |
总长度(Total Length) | 16位 | 整个IP数据包的长度(头部+数据),最大值为65535字节。 |
标识(Identification) | 16位 | 用于标识分片的数据包(同一原始数据包的所有分片具有相同的标识)。 |
标志(Flags) | 3位 | 分片控制标志:0位保留,1位DF(Don't Fragment,禁止分片),2位MF(More Fragments,还有分片)。 |
片偏移(Fragment Offset) | 13位 | 分片在原始数据包中的偏移量(单位:8字节),用于接收端重组。 |
生存时间(TTL) | 8位 | 数据包的生存时间(单位:跳数),每经过一个路由器减1,为0时丢弃(防止数据包循环转发)。 |
协议(Protocol) | 8位 | 标识上层协议类型(如6=TCP,17=UDP,1=ICMP)。 |
头部校验和(Header Checksum) | 16位 | 用于检测IP头部是否损坏,接收端校验失败则丢弃数据包。 |
源IP地址(Source Address) | 32位 | 发送方主机的IPv4地址。 |
目的IP地址(Destination Address) | 32位 | 接收方主机的IPv4地址。 |
选项(Options) | 可变 | 可选字段(如记录路由、时间戳),长度可变,最大为40字节(需满足头部长度≤60字节)。 |
填充(Padding) | 可变 | 用于将IP头部长度填充为32位字的整数倍(确保对齐)。 |
数据(Data) | 可变 | 来自传输层的数据(段或数据报)。 |
以下示例模拟IPv4数据包的封装(发送端)和解封装(接收端)过程,严格遵循RFC 791标准的数据包结构,补全完整实现代码:
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* 网络层实战:IPv4数据包的封装与解析
*/
publicclass Ipv4PacketDemo {
// IPv4头部字段长度常量(单位:字节)
privatestaticfinalint VERSION_IHL_LENGTH = 1;
privatestaticfinalint TOS_LENGTH = 1;
privatestaticfinalint TOTAL_LENGTH_LENGTH = 2;
privatestaticfinalint IDENTIFICATION_LENGTH = 2;
privatestaticfinalint FLAGS_FRAG_OFFSET_LENGTH = 2;
privatestaticfinalint TTL_LENGTH = 1;
privatestaticfinalint PROTOCOL_LENGTH = 1;
privatestaticfinalint HEADER_CHECKSUM_LENGTH = 2;
privatestaticfinalint IP_ADDRESS_LENGTH = 4;
privatestaticfinalint MIN_HEADER_LENGTH = 20; // 无选项字段的最小头部长度
privatestaticfinalint MAX_HEADER_LENGTH = 60; // 有选项字段的最大头部长度
// 协议类型常量(对应上层协议)
publicstaticfinalbyte PROTOCOL_TCP = 6; // TCP协议
publicstaticfinalbyte PROTOCOL_UDP = 17; // UDP协议
publicstaticfinalbyte PROTOCOL_ICMP = 1; // ICMP协议
// 版本常量
privatestaticfinalbyte VERSION_IPV4 = 4;
/**
* 封装IPv4数据包
* @param srcIp 源IP地址(如"192.168.1.100")
* @param destIp 目的IP地址(如"192.168.1.1")
* @param protocol 上层协议类型(PROTOCOL_TCP/PROTOCOL_UDP/PROTOCOL_ICMP)
* @param ttl 生存时间(跳数,如64)
* @param identification 标识字段(用于分片重组,如12345)
* @param flags 标志字段(3位,如0x00表示允许分片)
* @param fragOffset 片偏移(单位:8字节,不分片时为0)
* @param options 选项字段(可为null,长度需为4的倍数)
* @param data 传输层数据(段/数据报)
* @return 完整的IPv4数据包字节数组
* @throws UnknownHostException 无效IP地址异常
*/
publicstaticbyte[] encapsulateIpv4Packet(String srcIp, String destIp, byte protocol, int ttl,
int identification, short flags, int fragOffset, byte[] options, byte[] data)
throws UnknownHostException {
// 1. 验证参数有效性
Inet4Address srcInetIp = (Inet4Address) InetAddress.getByName(srcIp);
Inet4Address destInetIp = (Inet4Address) InetAddress.getByName(destIp);
// 计算头部长度(IHL):单位为32位字(4字节),最小值5(20字节)
int headerLength = MIN_HEADER_LENGTH;
if (options != null && options.length > 0) {
// 选项长度必须是4的倍数,否则填充
int optionsLength = (options.length + 3) / 4 * 4;
headerLength += optionsLength;
if (headerLength > MAX_HEADER_LENGTH) {
thrownew IllegalArgumentException("IPv4头部长度超过最大值60字节");
}
}
byte ihl = (byte) (headerLength / 4); // 转换为32位字单位
// 验证标志和片偏移(标志3位,片偏移13位,组合为2字节)
if ((flags & 0xFFF8) != 0) { // 标志仅低3位有效
thrownew IllegalArgumentException("标志字段超出3位有效范围");
}
if (fragOffset > 0x1FFF) { // 片偏移仅13位有效
thrownew IllegalArgumentException("片偏移超出13位有效范围");
}
short flagsFragOffset = (short) ((flags << 13) | fragOffset);
// 2. 计算总长度(头部长度 + 数据长度)
int totalLength = headerLength + (data != null ? data.length : 0);
if (totalLength > 65535) { // 总长度16位,最大值65535
thrownew IllegalArgumentException("IPv4数据包总长度超过最大值65535字节");
}
// 3. 构建IPv4头部字节缓冲区
ByteBuffer headerBuffer = ByteBuffer.allocate(headerLength);
// 3.1 版本 + 头部长度(1字节):版本4(高4位),IHL(低4位)
headerBuffer.put((byte) ((VERSION_IPV4 << 4) | (ihl & 0x0F)));
// 3.2 服务类型(TOS,1字节):默认0(普通服务)
headerBuffer.put((byte) 0x00);
// 3.3 总长度(2字节,大端序)
headerBuffer.putShort((short) totalLength);
// 3.4 标识(2字节,大端序)
headerBuffer.putShort((short) identification);
// 3.5 标志 + 片偏移(2字节,大端序)
headerBuffer.putShort(flagsFragOffset);
// 3.6 生存时间(TTL,1字节)
headerBuffer.put((byte) ttl);
// 3.7 协议类型(1字节)
headerBuffer.put(protocol);
// 3.8 头部校验和(先填0,后续计算)
headerBuffer.putShort((short) 0x0000);
// 3.9 源IP地址(4字节)
headerBuffer.put(srcInetIp.getAddress());
// 3.10 目的IP地址(4字节)
headerBuffer.put(destInetIp.getAddress());
// 3.11 选项字段(可选)
if (options != null && options.length > 0) {
headerBuffer.put(options);
// 填充至32位字倍数(若需)
int paddingLength = headerLength - headerBuffer.position();
for (int i = 0; i < paddingLength; i++) {
headerBuffer.put((byte) 0x00);
}
}
// 4. 计算头部校验和
headerBuffer.rewind();
byte[] headerBytes = newbyte[headerLength];
headerBuffer.get(headerBytes);
short checksum = calculateIpChecksum(headerBytes);
// 5. 重新写入校验和
headerBuffer.rewind();
headerBuffer.position(HEADER_CHECKSUM_LENGTH + VERSION_IHL_LENGTH + TOS_LENGTH + TOTAL_LENGTH_LENGTH + IDENTIFICATION_LENGTH + FLAGS_FRAG_OFFSET_LENGTH + TTL_LENGTH + PROTOCOL_LENGTH);
headerBuffer.putShort(checksum);
// 6. 组合头部和数据,形成完整IPv4数据包
ByteBuffer packetBuffer = ByteBuffer.allocate(totalLength);
packetBuffer.put(headerBuffer.array());
if (data != null) {
packetBuffer.put(data);
}
return packetBuffer.array();
}
/**
* 解封装IPv4数据包
* @param ipPacket 完整的IPv4数据包字节数组
* @return 解封装后的结果(包含源IP、目的IP、协议类型、数据等)
* @throws UnknownHostException IP地址解析异常
*/
public static Ipv4PacketDecapsulateResult decapsulateIpv4Packet(byte[] ipPacket) throws UnknownHostException {
ByteBuffer packetBuffer = ByteBuffer.wrap(ipPacket);
// 1. 读取版本 + 头部长度(1字节)
byte versionIhl = packetBuffer.get();
byte version = (byte) ((versionIhl >> 4) & 0x0F);
if (version != VERSION_IPV4) {
thrownew IllegalArgumentException("非IPv4数据包");
}
int headerLength = ((versionIhl & 0x0F)) * 4; // 转换为字节单位
if (headerLength < MIN_HEADER_LENGTH || headerLength > MAX_HEADER_LENGTH || headerLength > ipPacket.length) {
thrownew IllegalArgumentException("无效的IPv4头部长度");
}
// 2. 读取服务类型(TOS,1字节)
byte tos = packetBuffer.get();
// 3. 读取总长度(2字节,大端序)
short totalLength = packetBuffer.getShort();
if (totalLength != ipPacket.length) {
thrownew IllegalArgumentException("数据包总长度不匹配");
}
// 4. 读取标识(2字节,大端序)
short identification = packetBuffer.getShort();
// 5. 读取标志 + 片偏移(2字节,大端序)
short flagsFragOffset = packetBuffer.getShort();
byte flags = (byte) ((flagsFragOffset >> 13) & 0x07); // 低3位为标志
int fragOffset = flagsFragOffset & 0x1FFF; // 低13位为片偏移
// 6. 读取生存时间(TTL,1字节)
byte ttl = packetBuffer.get();
// 7. 读取协议类型(1字节)
byte protocol = packetBuffer.get();
// 8. 读取并验证头部校验和(2字节,大端序)
short checksum = packetBuffer.getShort();
// 复制头部字节,计算校验和
byte[] headerBytes = newbyte[headerLength];
System.arraycopy(ipPacket, 0, headerBytes, 0, headerLength);
// 临时将校验和字段置0,重新计算
ByteBuffer tempHeaderBuffer = ByteBuffer.wrap(headerBytes);
tempHeaderBuffer.position(HEADER_CHECKSUM_LENGTH + VERSION_IHL_LENGTH + TOS_LENGTH + TOTAL_LENGTH_LENGTH + IDENTIFICATION_LENGTH + FLAGS_FRAG_OFFSET_LENGTH + TTL_LENGTH + PROTOCOL_LENGTH);
tempHeaderBuffer.putShort((short) 0x0000);
short calculatedChecksum = calculateIpChecksum(headerBytes);
boolean isHeaderValid = (calculatedChecksum == checksum);
// 9. 读取源IP地址(4字节)
byte[] srcIpBytes = newbyte[IP_ADDRESS_LENGTH];
packetBuffer.get(srcIpBytes);
Inet4Address srcIp = (Inet4Address) InetAddress.getByAddress(srcIpBytes);
// 10. 读取目的IP地址(4字节)
byte[] destIpBytes = newbyte[IP_ADDRESS_LENGTH];
packetBuffer.get(destIpBytes);
Inet4Address destIp = (Inet4Address) InetAddress.getByAddress(destIpBytes);
// 11. 跳过选项和填充字段
packetBuffer.position(headerLength);
// 12. 读取数据部分
int dataLength = totalLength - headerLength;
byte[] data = newbyte[dataLength];
if (dataLength > 0) {
packetBuffer.get(data);
}
returnnew Ipv4PacketDecapsulateResult(
version, headerLength, tos, totalLength, identification, flags, fragOffset,
ttl, protocol, isHeaderValid, srcIp.getHostAddress(), destIp.getHostAddress(), data
);
}
/**
* 计算IPv4头部校验和(遵循RFC 1071标准)
* @param header 待计算校验和的IPv4头部字节数组
* @return 校验和(16位)
*/
private static short calculateIpChecksum(byte[] header) {
int sum = 0;
int i = 0;
// 按16位(2字节)为单位累加
while (i < header.length) {
sum += ((header[i] & 0xFF) << 8) | (header[i + 1] & 0xFF);
i += 2;
}
// 处理累加溢出(高16位与低16位相加)
while ((sum >> 16) != 0) {
sum = (sum & 0xFFFF) + (sum >> 16);
}
// 取反得到校验和
return (short) ~sum;
}
/**
* 解封装结果封装类
*/
publicstaticclass Ipv4PacketDecapsulateResult {
privatefinalbyte version;
privatefinalint headerLength;
privatefinalbyte tos;
privatefinalshort totalLength;
privatefinalshort identification;
privatefinalbyte flags;
privatefinalint fragOffset;
privatefinalbyte ttl;
privatefinalbyte protocol;
privatefinalboolean isHeaderValid;
privatefinal String srcIp;
privatefinal String destIp;
privatefinalbyte[] data;
public Ipv4PacketDecapsulateResult(byte version, int headerLength, byte tos, short totalLength,
short identification, byte flags, int fragOffset, byte ttl, byte protocol,
boolean isHeaderValid, String srcIp, String destIp, byte[] data) {
this.version = version;
this.headerLength = headerLength;
this.tos = tos;
this.totalLength = totalLength;
this.identification = identification;
this.flags = flags;
this.fragOffset = fragOffset;
this.ttl = ttl;
this.protocol = protocol;
this.isHeaderValid = isHeaderValid;
this.srcIp = srcIp;
this.destIp = destIp;
this.data = data;
}
// getter方法
public byte getVersion() { return version; }
public int getHeaderLength() { return headerLength; }
public byte getTos() { return tos; }
public short getTotalLength() { return totalLength; }
public short getIdentification() { return identification; }
public byte getFlags() { return flags; }
public int getFragOffset() { return fragOffset; }
public byte getTtl() { return ttl; }
public byte getProtocol() { return protocol; }
public boolean isHeaderValid() { return isHeaderValid; }
public String getSrcIp() { return srcIp; }
public String getDestIp() { return destIp; }
publicbyte[] getData() { return data; }
}
// 测试方法
public static void main(String[] args) {
try {
// 1. 准备测试数据(模拟传输层TCP数据)
String tcpDataStr = "This is a test TCP segment data.";
byte[] tcpData = tcpDataStr.getBytes(StandardCharsets.UTF_8);
// 2. 封装IPv4数据包
String srcIp = "192.168.1.100";
String destIp = "192.168.1.1";
byte protocol = PROTOCOL_TCP;
int ttl = 64;
int identification = 12345;
short flags = 0x00; // 允许分片,无更多分片
int fragOffset = 0; // 不分片
byte[] options = null; // 无选项字段
byte[] ipv4Packet = encapsulateIpv4Packet(srcIp, destIp, protocol, ttl, identification,
flags, fragOffset, options, tcpData);
System.out.println("IPv4数据包封装完成,总长度:" + ipv4Packet.length + "字节");
// 3. 解封装IPv4数据包
Ipv4PacketDecapsulateResult result = decapsulateIpv4Packet(ipv4Packet);
System.out.println("\nIPv4数据包解封装结果:");
System.out.println("IP版本:IPv" + result.getVersion());
System.out.println("头部长度:" + result.getHeaderLength() + "字节");
System.out.println("源IP地址:" + result.getSrcIp());
System.out.println("目的IP地址:" + result.getDestIp());
System.out.println("上层协议类型:" + (result.getProtocol() == PROTOCOL_TCP ? "TCP协议(6)" : "其他协议"));
System.out.println("生存时间(TTL):" + result.getTtl() + "跳");
System.out.println("标识:" + result.getIdentification());
System.out.println("标志:" + result.getFlags() + "(0=允许分片)");
System.out.println("片偏移:" + result.getFragOffset() + "(不分片)");
System.out.println("头部有效性:" + (result.isHeaderValid() ? "有效(校验和通过)" : "无效(校验和失败)"));
System.out.println("解封装后的数据(传输层数据):" + new String(result.getData(), StandardCharsets.UTF_8));
// 4. 测试校验和失败场景(篡改头部数据)
ipv4Packet[10] = (byte) 0xAA; // 篡改源IP地址的一个字节
Ipv4PacketDecapsulateResult invalidResult = decapsulateIpv4Packet(ipv4Packet);
System.out.println("\n篡改头部数据后的解封装结果:");
System.out.println("头部有效性:" + (invalidResult.isHeaderValid() ? "有效" : "无效(校验和失败)"));
} catch (Exception e) {
e.printStackTrace();
}
}
}
ICMP协议是网络层的核心辅助协议,用于传输网络控制消息(如错误报告、网络探测),ping命令的底层就是基于ICMP的请求/响应机制。以下示例实现ICMP Echo Request(请求)和Echo Reply(响应)的发送与解析。
<dependency>
<groupId>org.pcap4j</groupId>
<artifactId>pcap4j-core</artifactId>
<version>1.8.2</version>
</dependency>
<dependency>
<groupId>org.pcap4j</groupId>
<artifactId>pcap4j-packet</artifactId>
<version>1.8.2</version>
</dependency>
<dependency>
<groupId>org.pcap4j</groupId>
<artifactId>pcap4j-nio</artifactId>
<version>1.8.2</version>
</dependency>
import org.pcap4j.core.*;
import org.pcap4j.packet.*;
import org.pcap4j.packet.namednumber.EtherType;
import org.pcap4j.packet.namednumber.IpNumber;
import org.pcap4j.packet.namednumber.IpVersion;
import org.pcap4j.util.Inet4AddressUtils;
import org.pcap4j.util.MacAddress;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.concurrent.TimeoutException;
/**
* 网络层实战:ICMP协议实现(模拟ping命令)
*/
publicclass IcmpProtocolDemo {
// 超时时间(单位:毫秒)
privatestaticfinalint TIMEOUT = 3000;
// 发送ICMP请求的次数
privatestaticfinalint SEND_COUNT = 4;
// ICMP Echo Request/Reply 类型码
privatestaticfinalbyte ICMP_ECHO_REQUEST_TYPE = 8;
privatestaticfinalbyte ICMP_ECHO_REPLY_TYPE = 0;
privatestaticfinalbyte ICMP_ECHO_CODE = 0;
/**
* 发送ICMP Echo Request,接收Echo Reply(模拟ping)
* @param ifName 网卡名称(如Windows的"以太网",Linux的"eth0")
* @param srcIp 本地IPv4地址
* @param destIp 目标IPv4地址(需要ping的IP)
* @param sequence 序列号(用于匹配请求和响应)
* @param id 标识(通常为进程ID,用于区分不同ping实例)
*/
public static void ping(String ifName, Inet4Address srcIp, Inet4Address destIp, short sequence, int id) {
PcapHandle handle = null;
try {
// 1. 获取网卡设备并打开
PcapNetworkInterface nif = Pcaps.getDevByName(ifName);
if (nif == null) {
thrownew IllegalArgumentException("未找到网卡:" + ifName);
}
int snapLen = 65536;
handle = nif.openLive(snapLen, PcapNetworkInterface.PromiscuousMode.PROMISCUOUS, TIMEOUT);
// 2. 构建ICMP Echo Request包
for (int i = 0; i < SEND_COUNT; i++) {
// 2.1 构建ICMP头部(Echo Request)
IcmpV4CommonPacket.IcmpV4CommonHeader icmpHeader = new IcmpV4CommonPacket.IcmpV4CommonHeader.Builder()
.type(ICMP_ECHO_REQUEST_TYPE) // 类型:Echo Request
.code(ICMP_ECHO_CODE) // 代码:0
.checksum((short) 0) // 先填0,后续计算
.id((short) id) // 标识
.sequence(sequence) // 序列号
.build();
// 2.2 构建ICMP payload(可选,通常为时间戳)
long timestamp = System.currentTimeMillis();
byte[] payloadData = String.valueOf(timestamp).getBytes();
Packet icmpPayload = new SimplePacket.Builder().payload(ByteBuffer.wrap(payloadData)).build();
// 2.3 构建完整ICMP包(计算校验和)
IcmpV4CommonPacket icmpPacket = new IcmpV4CommonPacket.Builder()
.header(icmpHeader)
.payload(icmpPayload)
.build();
// 计算ICMP校验和(需组合ICMP头部和payload)
short checksum = calculateIcmpChecksum(icmpPacket);
icmpPacket = new IcmpV4CommonPacket.Builder(icmpPacket)
.header(new IcmpV4CommonPacket.IcmpV4CommonHeader.Builder(icmpPacket.getHeader())
.checksum(checksum)
.build())
.build();
// 2.4 构建IPv4头部(协议类型为ICMP)
IpV4Packet.IpV4Header ipHeader = new IpV4Packet.IpV4Header.Builder()
.version(IpVersion.IPV4)
.ihl((byte) 5) // 头部长度20字节
.tos((byte) 0)
.totalLength((short) (20 + icmpPacket.getRawData().length)) // IP头部20字节 + ICMP包长度
.identification((short) (id + i)) // 标识递增
.ttl((byte) 64) // 生存时间64跳
.protocol(IpNumber.ICMPV4) // 协议类型:ICMP
.srcAddr(srcIp)
.dstAddr(destIp)
.checksum((short) 0) // 先填0,后续计算
.build();
// 2.5 构建完整IPv4数据包(计算校验和)
IpV4Packet ipPacket = new IpV4Packet.Builder()
.header(ipHeader)
.payload(icmpPacket)
.build();
short ipChecksum = calculateIpChecksum(ipPacket.getHeader().getRawData());
ipPacket = new IpV4Packet.Builder(ipPacket)
.header(new IpV4Packet.IpV4Header.Builder(ipPacket.getHeader())
.checksum(ipChecksum)
.build())
.build();
// 2.6 构建以太网帧(通过ARP获取目的MAC,此处简化为广播或已知MAC,实际需调用ARP解析)
MacAddress srcMac = MacAddress.getByInetAddress(srcIp); // 获取本地IP对应的MAC
MacAddress destMac = getDestMacByArp(handle, srcIp, srcMac, destIp); // ARP解析目的MAC
if (destMac == null) {
System.err.println("ARP解析目的MAC失败,无法发送ICMP请求");
continue;
}
EthernetPacket.EthernetHeader etherHeader = new EthernetPacket.EthernetHeader.Builder()
.dstAddr(destMac)
.srcAddr(srcMac)
.type(EtherType.IPV4)
.build();
Packet finalPacket = new EthernetPacket.Builder()
.header(etherHeader)
.payload(ipPacket)
.build();
// 3. 发送ICMP请求
long sendTime = System.currentTimeMillis();
handle.sendPacket(finalPacket);
System.out.printf("发送ICMP Echo Request:目标IP=%s,序列号=%d,发送时间=%dms%n",
destIp.getHostAddress(), sequence, sendTime);
// 4. 捕获并解析ICMP Echo Reply
captureIcmpReply(handle, srcIp, destIp, sequence, id, sendTime);
sequence++;
Thread.sleep(1000); // 间隔1秒发送一次
}
} catch (PcapNativeException | NotOpenException | InterruptedException | TimeoutException e) {
e.printStackTrace();
} finally {
if (handle != null) {
handle.close();
}
}
}
/**
* 捕获并解析ICMP Echo Reply
*/
private static void captureIcmpReply(PcapHandle handle, Inet4Address srcIp, Inet4Address destIp,
short sequence, int id, long sendTime) throws TimeoutException {
try {
Packet packet = handle.getNextPacketEx();
// 过滤:仅处理IPv4 + ICMP包
if (packet.contains(IpV4Packet.class) && packet.contains(IcmpV4CommonPacket.class)) {
IpV4Packet ipPacket = packet.get(IpV4Packet.class);
IcmpV4CommonPacket icmpPacket = packet.get(IcmpV4CommonPacket.class);
// 验证:是否为目标IP的ICMP Echo Reply,且标识和序列号匹配
if (ipPacket.getHeader().getSrcAddr().equals(destIp)
&& ipPacket.getHeader().getDstAddr().equals(srcIp)
&& icmpPacket.getHeader().getType() == ICMP_ECHO_REPLY_TYPE
&& icmpPacket.getHeader().getCode() == ICMP_ECHO_CODE
&& icmpPacket.getHeader().getId() == (short) id
&& icmpPacket.getHeader().getSequence() == sequence) {
long receiveTime = System.currentTimeMillis();
long delay = receiveTime - sendTime;
System.out.printf("收到ICMP Echo Reply:来自IP=%s,序列号=%d,接收时间=%dms,延迟=%dms%n",
destIp.getHostAddress(), sequence, receiveTime, delay);
}
}
} catch (TimeoutException e) {
System.err.printf("未收到ICMP Echo Reply:目标IP=%s,序列号=%d,超时%dms%n",
destIp.getHostAddress(), sequence, TIMEOUT);
throw e;
} catch (PcapNativeException | NotOpenException e) {
e.printStackTrace();
}
}
/**
* 调用ARP解析目的IP对应的MAC地址(复用3.2.6的ARP逻辑)
*/
private static MacAddress getDestMacByArp(PcapHandle handle, Inet4Address srcIp, MacAddress srcMac, Inet4Address targetIp) {
try {
// 构建ARP请求包
ArpPacket.ArpHeader arpHeader = new ArpPacket.ArpHeader.Builder()
.hardwareType(ArpHardwareType.ETHERNET)
.protocolType(EtherType.IPV4)
.hardwareAddrLength((byte) MacAddress.SIZE_IN_BYTES)
.protocolAddrLength((byte) 4)
.operation(ArpOperation.REQUEST)
.senderHardwareAddr(srcMac)
.senderProtocolAddr(srcIp)
.targetHardwareAddr(MacAddress.ETHER_BROADCAST_ADDRESS)
.targetProtocolAddr(targetIp)
.build();
EthernetPacket.EthernetHeader etherHeader = new EthernetPacket.EthernetHeader.Builder()
.dstAddr(MacAddress.ETHER_BROADCAST_ADDRESS)
.srcAddr(srcMac)
.type(EtherType.ARP)
.build();
Packet arpPacket = new EthernetPacket.Builder()
.header(etherHeader)
.payload(new ArpPacket.Builder().header(arpHeader).build())
.build();
// 发送ARP请求
handle.sendPacket(arpPacket);
// 捕获ARP响应
long startTime = System.currentTimeMillis();
while (System.currentTimeMillis() - startTime < TIMEOUT) {
Packet packet = handle.getNextPacketEx();
if (packet.contains(ArpPacket.class)) {
ArpPacket receivedArp = packet.get(ArpPacket.class);
if (receivedArp.getHeader().getOperation() == ArpOperation.REPLY
&& receivedArp.getHeader().getSenderProtocolAddr().equals(targetIp)
&& receivedArp.getHeader().getTargetProtocolAddr().equals(srcIp)) {
return receivedArp.getHeader().getSenderHardwareAddr();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
returnnull;
}
/**
* 计算ICMP校验和(遵循RFC 1071)
*/
private static short calculateIcmpChecksum(IcmpV4CommonPacket icmpPacket) {
byte[] data = icmpPacket.getRawData();
int sum = 0;
int i = 0;
while (i < data.length) {
sum += ((data[i] & 0xFF) << 8) | (data[i + 1] & 0xFF);
i += 2;
}
// 处理奇数长度(ICMP包长度为偶数,此处备用)
if (data.length % 2 != 0) {
sum += (data[data.length - 1] & 0xFF) << 8;
}
while ((sum >> 16) != 0) {
sum = (sum & 0xFFFF) + (sum >> 16);
}
return (short) ~sum;
}
/**
* 计算IPv4头部校验和
*/
private static short calculateIpChecksum(byte[] ipHeaderData) {
int sum = 0;
int i = 0;
while (i < ipHeaderData.length) {
sum += ((ipHeaderData[i] & 0xFF) << 8) | (ipHeaderData[i + 1] & 0xFF);
i += 2;
}
while ((sum >> 16) != 0) {
sum = (sum & 0xFFFF) + (sum >> 16);
}
return (short) ~sum;
}
// 测试方法
public static void main(String[] args) {
try {
// 配置参数(需根据实际环境修改)
String ifName = "以太网"; // 网卡名称
Inet4Address srcIp = (Inet4Address) InetAddress.getByName("192.168.1.100"); // 本地IP
Inet4Address destIp = (Inet4Address) InetAddress.getByName("192.168.1.1"); // 目标IP(如路由器)
short sequence = 1; // 初始序列号
int id = ProcessHandle.current().pid() > Short.MAX_VALUE ? (int) (ProcessHandle.current().pid() % Short.MAX_VALUE) : (int) ProcessHandle.current().pid(); // 标识(进程ID)
System.out.printf("开始ping %s(本地进程ID:%d)%n", destIp.getHostAddress(), id);
ping(ifName, srcIp, destIp, sequence, id);
} catch (UnknownHostException e) {
System.err.println("无效的IP地址:" + e.getMessage());
}
}
}
传输层位于网络层之上,负责为应用层提供端到端的可靠或高效的数据传输服务,屏蔽网络层的路由和分片细节,确保数据从源应用程序准确交付到目的应用程序。
TCP段是传输层TCP协议的数据单元,标准结构如下(单位:字节):
字段名称 | 长度 | 功能说明 |
|---|---|---|
源端口(Source Port) | 16位 | 发送方应用程序的端口号。 |
目的端口(Destination Port) | 16位 | 接收方应用程序的端口号。 |
序列号(Sequence Number) | 32位 | 标识当前段的第一个字节在整个字节流中的位置(用于按序重组、去重)。 |
确认号(Acknowledgment Number) | 32位 | 期望接收的下一个字节的序列号(用于确认已收到的数据),仅在ACK标志置1时有效。 |
数据偏移(Data Offset) | 4位 | TCP头部的长度(单位:32位字,即4字节),最小值为5(20字节),最大值为15(60字节)。 |
保留(Reserved) | 6位 | 保留字段,未使用,置0。 |
标志(Flags) | 6位 | 控制标志:URG(紧急指针有效)、ACK(确认号有效)、PSH(推送数据)、RST(重置连接)、SYN(同步序列号,建立连接)、FIN(结束连接)。 |
窗口大小(Window Size) | 16位 | 接收端的接收缓冲区大小(用于流量控制,告知发送方可发送的最大字节数)。 |
校验和(Checksum) | 16位 | 用于检测TCP段(头部+数据)是否损坏,需结合伪头部计算。 |
紧急指针(Urgent Pointer) | 16位 | 指向紧急数据的最后一个字节(仅在URG标志置1时有效)。 |
选项(Options) | 可变 | 可选字段(如最大段大小MSS、窗口扩大因子、时间戳),长度可变,最大为40字节(需满足头部长度≤60字节)。 |
填充(Padding) | 可变 | 用于将TCP头部长度填充为32位字的整数倍。 |
数据(Data) | 可变 | 来自应用层的数据。 |
UDP数据报是传输层UDP协议的数据单元,结构简单,如下(单位:字节):
字段名称 | 长度 | 功能说明 |
|---|---|---|
源端口(Source Port) | 16位 | 发送方应用程序的端口号(可选,可为0,表示无端口)。 |
目的端口(Destination Port) | 16位 | 接收方应用程序的端口号。 |
长度(Length) | 16位 | UDP数据报的总长度(头部8字节 + 数据长度),最小值为8字节。 |
校验和(Checksum) | 16位 | 用于检测UDP数据报(头部+数据)是否损坏,需结合伪头部计算(可选,IPv4中可置0表示不校验)。 |
数据(Data) | 可变 | 来自应用层的数据。 |
以下示例基于Java Socket API实现TCP客户端与服务端的完整通信流程,包含三次握手建立连接、数据传输、四次挥手断开连接,直观展示TCP的可靠传输特性。
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 传输层实战:TCP服务端(可靠连接与数据传输)
*/
publicclass TcpServerDemo {
privatestaticfinalint PORT = 8888; // 监听端口
privatestaticfinalint BUFFER_SIZE = 1024; // 缓冲区大小
public static void main(String[] args) {
ServerSocket serverSocket = null;
try {
// 1. 创建ServerSocket,绑定端口,开始监听客户端连接
serverSocket = new ServerSocket(PORT);
System.out.println("TCP服务端启动成功,监听端口:" + PORT + ",等待客户端连接...");
// 循环监听,支持多客户端(简化版,单线程处理一个客户端)
while (true) {
// 2. 等待客户端连接(阻塞方法,直到有客户端连接)
Socket clientSocket = serverSocket.accept();
System.out.println("客户端连接成功:" + clientSocket.getInetAddress() + ":" + clientSocket.getPort());
// 3. 处理客户端数据(输入流读取数据,输出流响应数据)
try (InputStream inputStream = clientSocket.getInputStream();
OutputStream outputStream = clientSocket.getOutputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
PrintWriter writer = new PrintWriter(new OutputStreamWriter(outputStream, "UTF-8"), true)) {
String receivedData;
// 读取客户端发送的数据(按行读取,直到客户端关闭连接)
while ((receivedData = reader.readLine()) != null) {
System.out.println("收到客户端[" + clientSocket.getPort() + "]数据:" + receivedData);
// 响应客户端(确认收到数据)
String response = "服务端已收到:" + receivedData;
writer.println(response);
System.out.println("向客户端[" + clientSocket.getPort() + "]发送响应:" + response);
}
System.out.println("客户端[" + clientSocket.getPort() + "]断开连接");
} catch (IOException e) {
System.err.println("处理客户端[" + clientSocket.getPort() + "]数据异常:" + e.getMessage());
} finally {
// 关闭客户端连接
clientSocket.close();
}
}
} catch (IOException e) {
System.err.println("TCP服务端启动失败:" + e.getMessage());
} finally {
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
import java.io.*;
import java.net.Socket;
/**
* 传输层实战:TCP客户端(可靠连接与数据传输)
*/
publicclass TcpClientDemo {
privatestaticfinal String SERVER_IP = "127.0.0.1"; // 服务端IP
privatestaticfinalint SERVER_PORT = 8888; // 服务端端口
privatestaticfinalint BUFFER_SIZE = 1024; // 缓冲区大小
public static void main(String[] args) {
Socket socket = null;
try {
// 1. 创建Socket,发起连接(三次握手在此过程中完成)
socket = new Socket(SERVER_IP, SERVER_PORT);
System.out.println("TCP客户端连接服务端成功:" + SERVER_IP + ":" + SERVER_PORT);
// 2. 向服务端发送数据(输出流),并接收响应(输入流)
try (OutputStream outputStream = socket.getOutputStream();
InputStream inputStream = socket.getInputStream();
PrintWriter writer = new PrintWriter(new OutputStreamWriter(outputStream, "UTF-8"), true);
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"))) {
// 发送测试数据
String[] testData = {"Hello TCP Server!", "This is a reliable data transfer test.", "I'm TCP Client."};
for (String data : testData) {
writer.println(data);
System.out.println("向服务端发送数据:" + data);
// 接收服务端响应(阻塞方法,直到收到响应)
String response = reader.readLine();
System.out.println("收到服务端响应:" + response);
Thread.sleep(1000); // 间隔1秒发送一次
}
// 发送结束标志
writer.println("exit");
System.out.println("向服务端发送结束标志:exit");
}
} catch (IOException | InterruptedException e) {
System.err.println("TCP客户端通信异常:" + e.getMessage());
} finally {
if (socket != null) {
try {
// 关闭Socket(四次挥手在此过程中完成)
socket.close();
System.out.println("TCP客户端断开连接");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
ServerSocket绑定端口并监听,通过accept()阻塞等待客户端连接,连接建立后通过输入流读取客户端数据,输出流发送响应。Socket发起连接(底层完成三次握手),通过输出流发送数据,输入流接收服务端响应,通信完成后关闭Socket(底层完成四次挥手)。BufferedReader/PrintWriter)按行读取/发送数据,简化数据处理,确保数据完整性。以下示例基于Java DatagramSocket API实现UDP客户端与服务端的通信流程,展示UDP无连接、无确认、高效的传输特性,适用于实时性要求高的场景。
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* 传输层实战:UDP服务端(高效数据传输)
*/
publicclass UdpServerDemo {
privatestaticfinalint PORT = 9999; // 监听端口
privatestaticfinalint BUFFER_SIZE = 1024; // 缓冲区大小
public static void main(String[] args) {
DatagramSocket datagramSocket = null;
try {
// 1. 创建DatagramSocket,绑定端口(UDP无需监听连接,仅绑定端口即可)
datagramSocket = new DatagramSocket(PORT);
System.out.println("UDP服务端启动成功,监听端口:" + PORT + ",等待接收数据...");
// 2. 创建接收缓冲区和DatagramPacket(用于接收数据)
byte[] receiveBuffer = newbyte[BUFFER_SIZE];
DatagramPacket receivePacket = new DatagramPacket(receiveBuffer, receiveBuffer.length);
// 循环接收数据(UDP无连接,无需建立连接,直接接收)
while (true) {
// 3. 接收UDP数据报(阻塞方法,直到收到数据)
datagramSocket.receive(receivePacket);
// 4. 解析接收的数据
String receivedData = new String(receivePacket.getData(), 0, receivePacket.getLength(), "UTF-8");
InetAddress clientIp = receivePacket.getAddress();
int clientPort = receivePacket.getPort();
System.out.printf("收到客户端[%s:%d]数据:%s%n", clientIp.getHostAddress(), clientPort, receivedData);
// 5. 可选:响应客户端(UDP无确认机制,需手动发送响应)
if ("exit".equals(receivedData)) {
System.out.printf("客户端[%s:%d]请求退出,服务端停止接收该客户端数据%n", clientIp.getHostAddress(), clientPort);
String response = "服务端已收到退出请求";
byte[] responseBuffer = response.getBytes("UTF-8");
DatagramPacket responsePacket = new DatagramPacket(responseBuffer, responseBuffer.length, clientIp, clientPort);
datagramSocket.send(responsePacket);
continue;
}
String response = "服务端已收到:" + receivedData;
byte[] responseBuffer = response.getBytes("UTF-8");
// 创建响应数据报(指定客户端IP和端口)
DatagramPacket responsePacket = new DatagramPacket(responseBuffer, responseBuffer.length, clientIp, clientPort);
// 发送响应
datagramSocket.send(responsePacket);
System.out.printf("向客户端[%s:%d]发送响应:%s%n", clientIp.getHostAddress(), clientPort, response);
// 清空接收缓冲区(避免残留数据)
receivePacket.setLength(receiveBuffer.length);
}
} catch (Exception e) {
System.err.println("UDP服务端运行异常:" + e.getMessage());
} finally {
if (datagramSocket != null && !datagramSocket.isClosed()) {
datagramSocket.close();
System.out.println("UDP服务端关闭");
}
}
}
}
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* 传输层实战:UDP客户端(高效数据传输)
*/
publicclass UdpClientDemo {
privatestaticfinal String SERVER_IP = "127.0.0.1"; // 服务端IP
privatestaticfinalint SERVER_PORT = 9999; // 服务端端口
privatestaticfinalint BUFFER_SIZE = 1024; // 缓冲区大小
public static void main(String[] args) {
DatagramSocket datagramSocket = null;
try {
// 1. 创建DatagramSocket(UDP无需指定端口,系统自动分配动态端口)
datagramSocket = new DatagramSocket();
System.out.println("UDP客户端启动成功,系统分配端口:" + datagramSocket.getLocalPort());
// 2. 准备发送的数据
String[] testData = {"Hello UDP Server!", "This is an efficient data transfer test.", "I'm UDP Client."};
InetAddress serverIp = InetAddress.getByName(SERVER_IP);
for (String data : testData) {
// 3. 封装UDP数据报(指定服务端IP、端口和数据)
byte[] sendBuffer = data.getBytes("UTF-8");
DatagramPacket sendPacket = new DatagramPacket(sendBuffer, sendBuffer.length, serverIp, SERVER_PORT);
// 4. 发送UDP数据报(无连接,直接发送,无需建立连接)
datagramSocket.send(sendPacket);
System.out.printf("向服务端[%s:%d]发送数据:%s%n", SERVER_IP, SERVER_PORT, data);
// 5. 接收服务端响应(UDP无确认,需手动接收,非阻塞可设置超时)
byte[] receiveBuffer = newbyte[BUFFER_SIZE];
DatagramPacket receivePacket = new DatagramPacket(receiveBuffer, receiveBuffer.length);
datagramSocket.setSoTimeout(2000); // 设置接收超时2秒
try {
datagramSocket.receive(receivePacket);
String response = new String(receivePacket.getData(), 0, receivePacket.getLength(), "UTF-8");
System.out.printf("收到服务端[%s:%d]响应:%s%n", receivePacket.getAddress().getHostAddress(), receivePacket.getPort(), response);
} catch (Exception e) {
System.err.printf("未收到服务端响应,超时2秒:%s%n", e.getMessage());
}
Thread.sleep(1000); // 间隔1秒发送一次
}
// 发送退出标志
String exitData = "exit";
byte[] exitBuffer = exitData.getBytes("UTF-8");
DatagramPacket exitPacket = new DatagramPacket(exitBuffer, exitBuffer.length, serverIp, SERVER_PORT);
datagramSocket.send(exitPacket);
System.out.printf("向服务端[%s:%d]发送退出标志:%s%n", SERVER_IP, SERVER_PORT, exitData);
// 接收退出响应
byte[] exitResponseBuffer = newbyte[BUFFER_SIZE];
DatagramPacket exitResponsePacket = new DatagramPacket(exitResponseBuffer, exitResponseBuffer.length);
datagramSocket.receive(exitResponsePacket);
String exitResponse = new String(exitResponsePacket.getData(), 0, exitResponsePacket.getLength(), "UTF-8");
System.out.printf("收到服务端退出响应:%s%n", exitResponse);
} catch (Exception e) {
System.err.println("UDP客户端运行异常:" + e.getMessage());
} finally {
if (datagramSocket != null && !datagramSocket.isClosed()) {
datagramSocket.close();
System.out.println("UDP客户端关闭");
}
}
}
}
DatagramSocket:UDP通信的核心类,服务端绑定端口,客户端动态分配端口。DatagramPacket:封装UDP数据报,包含数据、目标IP、目标端口(发送时)或源IP、源端口(接收时)。setSoTimeout():设置接收超时,避免客户端无限阻塞等待响应(UDP无连接,可能永远收不到响应)。会话层位于传输层之上、表示层之下,负责建立、维护和终止表示层实体之间的通信会话(Session),并提供会话管理、同步和恢复机制,是传输层连接的“精细化管理者”。
以下示例模拟会话层的同步点机制,实现文件传输的断点续传(基于TCP传输层连接,会话层添加同步点和会话管理)。
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
/**
* 会话层实战:同步点与断点续传(基于TCP)
*/
publicclass SessionLayerBreakpointResumeDemo {
// 同步点间隔(每传输1024字节插入一个同步点)
privatestaticfinalint CHECKPOINT_INTERVAL = 1024;
// 会话存储:key=会话ID,value=已传输字节数(同步点)
privatestaticfinal Map<String, Long> sessionCheckpoints = new HashMap<>();
/**
* 服务端:接收文件,支持断点续传
*/
staticclass FileServer {
privatestaticfinalint PORT = 8080;
public void start() {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(PORT);
System.out.println("文件服务端启动,监听端口:" + PORT);
while (true) {
Socket clientSocket = serverSocket.accept();
System.out.println("客户端连接:" + clientSocket.getInetAddress());
// 处理文件传输会话
new Thread(() -> handleFileSession(clientSocket)).start();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void handleFileSession(Socket clientSocket) {
try (InputStream in = clientSocket.getInputStream();
OutputStream out = clientSocket.getOutputStream();
DataInputStream dataIn = new DataInputStream(in);
DataOutputStream dataOut = new DataOutputStream(out)) {
// 1. 接收会话ID和文件名(会话标识)
String sessionId = dataIn.readUTF();
String fileName = dataIn.readUTF();
System.out.printf("会话[%s]:开始接收文件[%s]%n", sessionId, fileName);
// 2. 检查同步点(断点),返回已传输字节数
long checkpoint = sessionCheckpoints.getOrDefault(sessionId, 0L);
dataOut.writeLong(checkpoint);
dataOut.flush();
System.out.printf("会话[%s]:已传输字节数(同步点):%d%n", sessionId, checkpoint);
// 3. 接收文件数据(从同步点开始)
File file = new File("server_" + fileName);
try (RandomAccessFile raf = new RandomAccessFile(file, "rw")) {
raf.seek(checkpoint); // 定位到同步点位置
byte[] buffer = newbyte[1024];
int len;
long totalReceived = checkpoint;
while ((len = dataIn.read(buffer)) != -1) {
// 传输完成标志(len=0表示结束)
if (len == 0) break;
raf.write(buffer, 0, len);
totalReceived += len;
// 插入同步点(每CHECKPOINT_INTERVAL字节更新一次)
if (totalReceived % CHECKPOINT_INTERVAL == 0) {
sessionCheckpoints.put(sessionId, totalReceived);
System.out.printf("会话[%s]:更新同步点:%d字节%n", sessionId, totalReceived);
}
}
// 传输完成,清除会话同步点
sessionCheckpoints.remove(sessionId);
System.out.printf("会话[%s]:文件[%s]传输完成,总大小:%d字节%n", sessionId, fileName, totalReceived);
}
} catch (IOException e) {
System.err.printf("会话异常:%s%n", e.getMessage());
} finally {
try {
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 客户端:发送文件,支持断点续传
*/
staticclass FileClient {
privatefinal String serverIp;
privatefinalint serverPort;
public FileClient(String serverIp, int serverPort) {
this.serverIp = serverIp;
this.serverPort = serverPort;
}
/**
* 发送文件
* @param sessionId 唯一会话ID
* @param filePath 本地文件路径
*/
public void sendFile(String sessionId, String filePath) {
Socket socket = null;
try {
socket = new Socket(serverIp, serverPort);
File file = new File(filePath);
if (!file.exists()) {
System.err.println("文件不存在:" + filePath);
return;
}
try (OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();
DataOutputStream dataOut = new DataOutputStream(out);
DataInputStream dataIn = new DataInputStream(in);
RandomAccessFile raf = new RandomAccessFile(file, "r")) {
// 1. 发送会话ID和文件名
dataOut.writeUTF(sessionId);
dataOut.writeUTF(file.getName());
dataOut.flush();
// 2. 接收服务端的同步点(已传输字节数)
long checkpoint = dataIn.readLong();
System.out.printf("会话[%s]:从同步点开始传输:%d字节%n", sessionId, checkpoint);
// 3. 从同步点开始发送文件数据
raf.seek(checkpoint);
byte[] buffer = newbyte[1024];
int len;
long totalSent = checkpoint;
while ((len = raf.read(buffer)) != -1) {
dataOut.write(buffer, 0, len);
dataOut.flush();
totalSent += len;
// 模拟传输中断(每传输2个同步点中断一次,测试断点续传)
if (totalSent % (CHECKPOINT_INTERVAL * 2) == 0 && checkpoint == 0) {
System.out.println("模拟传输中断!");
thrownew IOException("手动中断传输");
}
}
// 发送结束标志(空数据)
dataOut.write(buffer, 0, 0);
dataOut.flush();
System.out.printf("会话[%s]:文件传输完成,总大小:%d字节%n", sessionId, totalSent);
} catch (IOException e) {
System.err.printf("传输中断,已发送:%d字节,可断点续传%n", totalSent);
// 重新发送(断点续传)
System.out.println("尝试断点续传...");
sendFile(sessionId, filePath);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
// 测试方法
public static void main(String[] args) {
// 启动服务端线程
new Thread(() -> new SessionLayerBreakpointResumeDemo().new FileServer().start()).start();
// 等待服务端启动
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 客户端发送文件(会话ID=test_session_001,文件路径需修改为本地文件)
FileClient client = new SessionLayerBreakpointResumeDemo().new FileClient("127.0.0.1", 8080);
client.sendFile("test_session_001", "test_file.txt"); // 替换为实际本地文件路径
}
}
sessionId唯一标识一个文件传输会话,服务端存储每个会话的同步点(已传输字节数);test_file.txt(任意内容);server_test_file.txt,与原文件内容一致,验证断点续传的有效性。表示层位于会话层之上、应用层之下,负责处理在两个通信系统中交换信息的表示方式,包括数据的编码、解码、加密、解密、压缩、解压缩,确保接收方能够理解发送方的数据格式。
以下示例整合表示层的三大核心功能:JSON序列化(格式转换)、AES加密(安全处理)、GZIP压缩(效率优化),模拟跨系统数据传输的表示层处理流程。
<!-- JSON序列化:Jackson -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
<!-- 加密工具:BouncyCastle(增强加密算法支持) -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.76</version>
</dependency>
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Base64;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import java.security.Security;
/**
* 表示层实战:序列化(JSON)+ 加密(AES)+ 压缩(GZIP)
*/
publicclass PresentationLayerDemo {
// 初始化加密提供者
static {
Security.addProvider(new BouncyCastleProvider());
}
// AES加密配置(128位密钥,ECB模式,PKCS7填充)
privatestaticfinal String AES_ALGORITHM = "AES/ECB/PKCS7Padding";
privatestaticfinal String AES_KEY = "1234567890ABCDEF"; // 16字节密钥(128位)
// JSON序列化工具
privatestaticfinal ObjectMapper OBJECT_MAPPER = new ObjectMapper();
/**
* 表示层发送端处理:对象→JSON序列化→AES加密→GZIP压缩
* @param data 待传输的业务对象
* @return 处理后的字节流(可直接通过传输层发送)
* @throws Exception 处理异常
*/
publicstaticbyte[] sendSideProcess(Object data) throws Exception {
// 1. 第一步:JSON序列化(对象→JSON字符串→字节流)
String jsonStr = OBJECT_MAPPER.writeValueAsString(data);
byte[] jsonBytes = jsonStr.getBytes("UTF-8");
System.out.println("表示层-序列化:" + jsonStr);
// 2. 第二步:AES加密(保护敏感数据)
byte[] encryptedBytes = aesEncrypt(jsonBytes);
String encryptedBase64 = Base64.getEncoder().encodeToString(encryptedBytes);
System.out.println("表示层-加密后(Base64):" + encryptedBase64);
// 3. 第三步:GZIP压缩(减少传输量)
byte[] compressedBytes = gzipCompress(encryptedBytes);
System.out.println("表示层-压缩后字节数:" + compressedBytes.length);
return compressedBytes;
}
/**
* 表示层接收端处理:GZIP解压缩→AES解密→JSON反序列化→对象
* @param receivedBytes 传输层接收的字节流
* @param clazz 目标对象类型
* @return 解析后的业务对象
* @throws Exception 处理异常
*/
publicstatic <T> T receiveSideProcess(byte[] receivedBytes, Class<T> clazz) throws Exception {
// 1. 第一步:GZIP解压缩
byte[] decompressedBytes = gzipDecompress(receivedBytes);
System.out.println("表示层-解压缩后字节数:" + decompressedBytes.length);
// 2. 第二步:AES解密
byte[] decryptedBytes = aesDecrypt(decompressedBytes);
String decryptedJson = new String(decryptedBytes, "UTF-8");
System.out.println("表示层-解密后(JSON):" + decryptedJson);
// 3. 第三步:JSON反序列化(字节流→JSON字符串→对象)
T data = OBJECT_MAPPER.readValue(decryptedJson, clazz);
System.out.println("表示层-反序列化:" + data);
return data;
}
/**
* AES加密
*/
privatestaticbyte[] aesEncrypt(byte[] data) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(AES_KEY.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
return cipher.doFinal(data);
}
/**
* AES解密
*/
privatestaticbyte[] aesDecrypt(byte[] encryptedData) throws Exception {
SecretKeySpec keySpec = new SecretKeySpec(AES_KEY.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, keySpec);
return cipher.doFinal(encryptedData);
}
/**
* GZIP压缩
*/
privatestaticbyte[] gzipCompress(byte[] data) throws IOException {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
GZIPOutputStream gzipOut = new GZIPOutputStream(bos)) {
gzipOut.write(data);
gzipOut.finish(); // 完成压缩,确保所有数据写入
return bos.toByteArray();
}
}
/**
* GZIP解压缩
*/
privatestaticbyte[] gzipDecompress(byte[] compressedData) throws IOException {
try (ByteArrayInputStream bis = new ByteArrayInputStream(compressedData);
GZIPInputStream gzipIn = new GZIPInputStream(bis);
ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
byte[] buffer = newbyte[1024];
int len;
while ((len = gzipIn.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
return bos.toByteArray();
}
}
// 测试用业务对象(模拟应用层数据)
staticclass UserData {
private String username;
private String password; // 敏感数据,需加密
privateint age;
private String address;
// 无参构造(Jackson反序列化必需)
public UserData() {}
public UserData(String username, String password, int age, String address) {
this.username = username;
this.password = password;
this.age = age;
this.address = address;
}
// Getter & Setter
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String getAddress() { return address; }
public void setAddress(String address) { this.address = address; }
@Override
public String toString() {
return"UserData{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", age=" + age +
", address='" + address + '\'' +
'}';
}
}
// 测试方法
public static void main(String[] args) {
try {
// 1. 应用层数据(模拟待传输的业务对象)
UserData sendData = new UserData("zhangsan", "12345678", 25, "北京市海淀区");
System.out.println("应用层原始数据:" + sendData);
// 2. 发送端表示层处理
byte[] sendBytes = sendSideProcess(sendData);
System.out.println("----------------------------------------");
// 3. 模拟传输层传输(直接传递字节流,无额外处理)
byte[] receivedBytes = sendBytes;
// 4. 接收端表示层处理
UserData receivedData = receiveSideProcess(receivedBytes, UserData.class);
System.out.println("----------------------------------------");
System.out.println("应用层最终接收数据:" + receivedData);
// 验证数据一致性
System.out.println("数据传输一致性:" + sendData.toString().equals(receivedData.toString()));
} catch (Exception e) {
System.err.println("表示层处理异常:" + e.getMessage());
e.printStackTrace();
}
}
}
应用层位于OSI七层模型的最顶层,直接面向用户和应用程序,负责为应用程序提供网络通信服务,定义应用程序之间的通信协议和交互逻辑,是用户与网络的直接交互接口。
应用层协议是互联网应用的核心,不同协议对应不同的应用场景,核心协议如下:
协议名称 | 中文名称 | 核心功能 | 默认端口 | 典型应用场景 |
|---|---|---|---|---|
HTTP | 超文本传输协议 | 传输超文本(网页、图片、JSON数据) | 80 | 浏览器访问网页、API接口调用 |
HTTPS | 安全超文本传输协议 | 基于TLS加密的HTTP传输,保障数据安全 | 443 | 网上银行、电商支付、HTTPS接口 |
FTP | 文件传输协议 | 客户端与服务器之间的文件上传/下载 | 21(控制)/20(数据) | 服务器文件管理、批量文件传输 |
SMTP | 简单邮件传输协议 | 发送电子邮件(客户端→邮件服务器) | 25(明文)/465(SSL) | 邮件客户端发送邮件(如Outlook) |
POP3 | 邮局协议版本3 | 接收电子邮件(邮件服务器→客户端) | 110(明文)/995(SSL) | 邮件客户端接收邮件 |
IMAP | 互联网邮件访问协议 | 接收电子邮件(支持邮件同步、文件夹管理) | 143(明文)/993(SSL) | 多设备邮件同步(如手机+电脑) |
DNS | 域名系统协议 | 将域名(如www.baidu.com)解析为IP地址 | 53(UDP/TCP) | 浏览器访问域名、应用域名解析 |
Telnet | 远程终端协议 | 远程登录服务器,执行命令 | 23 | 服务器远程管理(明文,不安全) |
SSH | 安全外壳协议 | 加密的远程登录与文件传输 | 22 | 服务器安全远程管理、SCP文件传输 |
DHCP | 动态主机配置协议 | 为客户端自动分配IP地址、子网掩码、网关等 | 67(服务器)/68(客户端) | 局域网设备自动获取IP(如家用电脑、手机) |
HTTP是应用层最核心的协议,以下示例基于Java原生Socket模拟HTTP服务端,接收浏览器的HTTP请求,返回HTML响应,直观理解HTTP协议的交互逻辑。
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;
/**
* 应用层实战:基于Socket实现简单HTTP服务端
*/
publicclass HttpServerDemo {
privatestaticfinalint PORT = 80; // HTTP默认端口
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
System.out.println("HTTP服务端启动成功,监听端口:" + PORT);
System.out.println("请在浏览器中访问:http://127.0.0.1");
// 循环接收浏览器请求
while (true) {
// 等待客户端连接(浏览器发起连接)
Socket clientSocket = serverSocket.accept();
System.out.println("收到客户端连接:" + clientSocket.getInetAddress());
// 多线程处理请求(支持并发)
new Thread(() -> handleHttpRequest(clientSocket)).start();
}
} catch (IOException e) {
System.err.println("HTTP服务端启动失败:" + e.getMessage());
e.printStackTrace();
}
}
/**
* 处理HTTP请求,返回HTTP响应
*/
private static void handleHttpRequest(Socket clientSocket) {
try (InputStream in = clientSocket.getInputStream();
OutputStream out = clientSocket.getOutputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in, "UTF-8"));
PrintWriter writer = new PrintWriter(new OutputStreamWriter(out, "UTF-8"), true)) {
// 1. 读取HTTP请求行(第一行)
String requestLine = reader.readLine();
if (requestLine == null) {
return;
}
System.out.println("HTTP请求行:" + requestLine);
// 2. 读取HTTP请求头(直到空行结束)
String headerLine;
while ((headerLine = reader.readLine()) != null && !headerLine.isEmpty()) {
System.out.println("HTTP请求头:" + headerLine);
}
System.out.println("----------------------------------------");
// 3. 构造HTTP响应(响应行 + 响应头 + 响应体)
// 3.1 响应行(HTTP/1.1 200 OK)
String responseLine = "HTTP/1.1 200 OK";
// 3.2 响应头(Date、Content-Type、Content-Length等)
String dateHeader = "Date: " + new Date();
String contentTypeHeader = "Content-Type: text/html; charset=UTF-8";
String serverHeader = "Server: MySimpleHttpServer/1.0";
// 3.3 响应体(HTML内容,浏览器展示用)
String htmlBody = "<!DOCTYPE html>" +
"<html>" +
"<head><title>简单HTTP服务端</title></head>" +
"<body>" +
"<h1>Hello, HTTP Application Layer!</h1>" +
"<p>这是基于Java Socket实现的应用层HTTP服务端</p>" +
"<p>请求时间:" + new Date() + "</p>" +
"</body>" +
"</html>";
// 3.4 响应体长度(字节数)
String contentLengthHeader = "Content-Length: " + htmlBody.getBytes("UTF-8").length;
// 4. 发送HTTP响应(响应行→响应头→空行→响应体)
writer.println(responseLine);
writer.println(dateHeader);
writer.println(contentTypeHeader);
writer.println(serverHeader);
writer.println(contentLengthHeader);
writer.println(); // 空行(响应头与响应体的分隔符)
writer.println(htmlBody);
System.out.println("HTTP响应发送完成");
System.out.println("----------------------------------------");
} catch (IOException e) {
System.err.println("处理HTTP请求异常:" + e.getMessage());
e.printStackTrace();
} finally {
// 关闭客户端连接
try {
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
GET / HTTP/1.1)、请求头(如Host: 127.0.0.1、User-Agent: Chrome);http://127.0.0.1,即可看到HTML页面;DNS是应用层的核心协议之一,负责域名→IP的解析。以下示例基于Java DatagramSocket实现简单DNS客户端,发送DNS查询请求,解析域名对应的IP地址,理解DNS协议的交互逻辑。
DNS查询请求/响应基于UDP传输,核心格式(简化):
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.nio.ByteBuffer;
/**
* 应用层实战:基于UDP实现简单DNS解析客户端
*/
publicclass DnsClientDemo {
// DNS服务器IP(公共DNS:阿里云223.5.5.5)
privatestaticfinal String DNS_SERVER_IP = "223.5.5.5";
// DNS默认端口(UDP 53)
privatestaticfinalint DNS_SERVER_PORT = 53;
// 缓冲区大小
privatestaticfinalint BUFFER_SIZE = 1024;
public static void main(String[] args) {
// 待解析的域名
String domain = "www.baidu.com";
try {
System.out.println("开始解析域名:" + domain);
String ip = resolveDomain(domain);
System.out.println("域名[" + domain + "]解析结果:" + ip);
} catch (Exception e) {
System.err.println("DNS解析异常:" + e.getMessage());
e.printStackTrace();
}
}
/**
* 解析域名:发送DNS查询请求,接收并解析响应
*/
private static String resolveDomain(String domain) throws Exception {
try (DatagramSocket socket = new DatagramSocket()) {
socket.setSoTimeout(3000); // 超时3秒
// 1. 构造DNS查询请求字节流
byte[] requestData = buildDnsRequest(domain);
// 2. 发送DNS查询请求(UDP)
InetAddress dnsServerAddr = InetAddress.getByName(DNS_SERVER_IP);
DatagramPacket sendPacket = new DatagramPacket(requestData, requestData.length, dnsServerAddr, DNS_SERVER_PORT);
socket.send(sendPacket);
System.out.println("已向DNS服务器[" + DNS_SERVER_IP + ":" + DNS_SERVER_PORT + "]发送查询请求");
// 3. 接收DNS响应
byte[] receiveBuffer = newbyte[BUFFER_SIZE];
DatagramPacket receivePacket = new DatagramPacket(receiveBuffer, receiveBuffer.length);
socket.receive(receivePacket);
System.out.println("收到DNS服务器响应,长度:" + receivePacket.getLength());
// 4. 解析DNS响应,提取IP地址
return parseDnsResponse(receivePacket.getData());
}
}
/**
* 构造DNS查询请求
* DNS头部(12字节):ID(2)+Flags(2)+QDCOUNT(2)+ANCOUNT(2)+NSCOUNT(2)+ARCOUNT(2)
*/
privatestaticbyte[] buildDnsRequest(String domain) {
ByteBuffer buffer = ByteBuffer.allocate(512);
// 1. 头部(12字节)
buffer.putShort((short) 0x1234); // ID(随机数,用于匹配请求与响应)
buffer.putShort((short) 0x0100); // Flags:标准查询(0x0000)+ 递归查询(0x0100)
buffer.putShort((short) 1); // QDCOUNT:查询问题数=1
buffer.putShort((short) 0); // ANCOUNT:回答数=0
buffer.putShort((short) 0); // NSCOUNT:权威服务器数=0
buffer.putShort((short) 0); // ARCOUNT:附加记录数=0
// 2. 查询问题:域名(按点分割,每个部分前加长度)+ 类型(A记录,2字节)+ 类(IN,2字节)
String[] domainParts = domain.split("\\.");
for (String part : domainParts) {
byte[] partBytes = part.getBytes();
buffer.put((byte) partBytes.length); // 每个域名片段的长度
buffer.put(partBytes); // 域名片段字节
}
buffer.put((byte) 0); // 域名结束标志(0x00)
buffer.putShort((short) 1); // QTYPE:1=A记录(IPv4地址)
buffer.putShort((short) 1); // QCLASS:1=IN(互联网地址)
// 压缩缓冲区,返回有效字节
byte[] requestData = newbyte[buffer.position()];
System.arraycopy(buffer.array(), 0, requestData, 0, buffer.position());
return requestData;
}
/**
* 解析DNS响应,提取IPv4地址
*/
private static String parseDnsResponse(byte[] responseData) {
ByteBuffer buffer = ByteBuffer.wrap(responseData);
// 1. 跳过头部(12字节)
buffer.position(12);
// 2. 跳过查询问题(先读取域名,直到0x00结束)
while (true) {
byte len = buffer.get();
if (len == 0) {
break;
}
buffer.position(buffer.position() + len); // 跳过当前域名片段
}
buffer.position(buffer.position() + 4); // 跳过QTYPE(2)和QCLASS(2)
// 3. 解析回答资源记录(ANCOUNT≥1)
// 先读取ANCOUNT(回答数)
buffer.position(6); // ANCOUNT位于头部第7-8字节(索引6-7)
short anCount = buffer.getShort();
if (anCount == 0) {
thrownew RuntimeException("DNS响应中无回答记录");
}
// 解析每个回答记录
for (int i = 0; i < anCount; i++) {
// 跳过NAME(可能是压缩格式,简化处理:直接跳过2字节)
buffer.position(buffer.position() + 2);
// 读取TYPE(2字节):1=A记录
short type = buffer.getShort();
// 读取CLASS(2字节):1=IN
short clazz = buffer.getShort();
// 跳过TTL(4字节)
buffer.position(buffer.position() + 4);
// 读取数据长度(2字节)
short dataLen = buffer.getShort();
// 若为A记录,提取IP地址
if (type == 1 && clazz == 1) {
byte[] ipBytes = newbyte[dataLen];
buffer.get(ipBytes);
// 转换为点分十进制IP
return String.format("%d.%d.%d.%d",
ipBytes[0] & 0xFF,
ipBytes[1] & 0xFF,
ipBytes[2] & 0xFF,
ipBytes[3] & 0xFF);
} else {
// 非A记录,跳过数据部分
buffer.position(buffer.position() + dataLen);
}
}
thrownew RuntimeException("未找到IPv4地址记录(A记录)");
}
}
InetAddress.getByName()方法的底层逻辑。www.baidu.com的IP地址;domain变量解析其他域名(如www.google.com、www.github.com);层级 | 核心职责 | 关键关键词 |
|---|---|---|
物理层 | 比特流传输(电信号/光信号),定义物理介质 | 比特流、物理介质、接口标准 |
数据链路层 | 帧封装/解封装,MAC寻址,差错控制 | 帧、MAC地址、ARP、差错控制 |
网络层 | 数据包路由,IP寻址,分片/重组 | 数据包、IP地址、路由、TTL |
传输层 | 端到端可靠/高效传输,端口寻址,流量控制 | 段/数据报、端口、TCP/UDP、三次握手 |
会话层 | 会话建立/维护/终止,同步点与恢复 | 会话、同步点、断点续传 |
表示层 | 数据格式转换,加密/压缩,序列化 | 编码、加密、压缩、JSON/Protobuf |
应用层 | 应用协议定义,业务逻辑,用户交互 | HTTP/DNS/SMTP、业务服务、用户接口 |
TCP/IP模型是实际互联网使用的简化模型(4层/5层),与OSI七层模型的对应关系:
OSI七层模型 | TCP/IP四层模型 | 核心协议/功能 |
|---|---|---|
应用层 | 应用层 | HTTP、DNS、SMTP、FTP等 |
表示层 | 应用层(隐含) | TLS/SSL、JSON/XML、加密/压缩 |
会话层 | 应用层(隐含) | RPC、会话管理 |
传输层 | 传输层 | TCP、UDP、端口寻址 |
网络层 | 网络层(互联层) | IP、ICMP、路由协议 |
数据链路层 | 网络接口层 | 以太网、ARP、帧封装 |
物理层 | 网络接口层 | 物理介质、比特流传输 |
核心差异: