代码
package com.study;
// ========================================
// Aeron 高性能消息传递示例 - IPC 通信
// ========================================
import io.aeron.Aeron; // Aeron 客户端主类,用于创建发布者和订阅者
import io.aeron.Publication; // 发布者接口,用于发送消息
import io.aeron.Subscription; // 订阅者接口,用于接收消息
import io.aeron.driver.MediaDriver; // MediaDriver: Aeron 的核心组件,负责底层网络通信
import io.aeron.logbuffer.FragmentHandler; // 消息片段处理器接口
import lombok.extern.slf4j.Slf4j; // Lombok 注解,自动生成 SLF4J 日志对象
import org.agrona.concurrent.IdleStrategy; // 空闲策略接口,用于控制 CPU 占用
import org.agrona.concurrent.SleepingIdleStrategy; // 基于睡眠的空闲策略实现
import org.agrona.concurrent.UnsafeBuffer; // 高性能缓冲区,避免 JVM 堆内存拷贝
import java.nio.ByteBuffer; // NIO 字节缓冲区
/**
* Simple001: Aeron IPC(进程间通信)示例
*
* 本示例演示如何使用 Aeron 在同一台机器上的两个进程之间进行高效消息传递。
* 使用 IPC(Unix Domain Socket)通道,延迟极低。
*/
@Slf4j // Lombok 注解:为类自动生成 log 字段,用于日志输出
public class Simple001 {
public static void main(String[] args) {
// 入口方法:启动示例程序
sample_run();
}
public static void sample_run() {
// -------------------------------------------------------
// 第一步:定义通信参数
// -------------------------------------------------------
// channel:通信通道地址
// "aeron:ipc" 表示使用 IPC(进程间通信)协议,通过 Unix Domain Socket 传输
// 相比 UDP/TCP,IPC 延迟更低,适合同一机器上的进程间通信
final String channel = "aeron:ipc";
// 要发送的消息内容
final String message = "hello aeron";
// streamId:流 ID,用于区分同一通道上的不同数据流
// 类似 UDP 的端口号,可以在同一通道上建立多个独立的流
final Integer streamId = 10;
// -------------------------------------------------------
// 第二步:初始化 Aeron 组件
// -------------------------------------------------------
// IdleStrategy(空闲策略):当没有数据时可控制 CPU 占用
// SleepingIdleStrategy:使用 Thread.sleep() 释放 CPU,比自旋更节能
// 其他策略还有:BusySpinIdleStrategy(忙等)、YieldingIdleStrategy(让出CPU)等
final IdleStrategy idleStrategy = new SleepingIdleStrategy();
// UnsafeBuffer: Aeron 使用的高性能缓冲区
// allocateDirect(256):分配 256 字节的直接内存缓冲区
// 直接内存(Direct Memory)不受 JVM 堆管理,可避免 GC 压力和内存拷贝
// Aeron 底层使用这种缓冲区进行零拷贝数据传输
final UnsafeBuffer unsafeBuffer = new UnsafeBuffer(ByteBuffer.allocateDirect(256));
// -------------------------------------------------------
// 第三步:启动 MediaDriver 并创建 Aeron 客户端
// -------------------------------------------------------
// try-with-resources:自动资源管理,确保所有资源使用完毕后自动关闭
// MediaDriver.launch():启动嵌入式 MediaDriver
// MediaDriver 负责底层的发送接收队列管理、可靠性和流量控制
try (
MediaDriver driver = MediaDriver.launch();
// Aeron.connect():创建 Aeron 客户端
// 客户端是应用与 MediaDriver 之间的桥梁
Aeron aeron = Aeron.connect();
// aeron.addSubscription():添加订阅者(接收端)
// 订阅者监听指定 channel 和 streamId 上的消息
// 注意:此时订阅者尚未与任何发布者建立连接
Subscription subscription = aeron.addSubscription(channel, streamId);
// aeron.addPublication():添加发布者(发送端)
// 发布者向指定 channel 和 streamId 发送消息
// 注意:此时发布者尚未与任何订阅者建立连接
Publication publication = aeron.addPublication(channel, streamId)
) {
// -------------------------------------------------------
// 第四步:等待发布者与订阅者建立连接
// -------------------------------------------------------
// publication.isConnected():检查发布者是否已与至少一个订阅者建立连接
// 在发布者和订阅者完成握手之前,发送操作不会成功
// 这是一个可靠性机制:确保消息不会丢失在无人接收的通道中
while (!publication.isConnected()) {
// 尚未连接时,调用 idle() 释放 CPU 并等待
// 这里使用 SleepingIdleStrategy,所以会使用短时睡眠
idleStrategy.idle();
}
// 连接建立后,可以开始发送消息
// -------------------------------------------------------
// 第五步:发送消息
// -------------------------------------------------------
// unsafeBuffer.putStringAscii(0, message):
// 将 Java 字符串以 ASCII 编码写入缓冲区的起始位置(偏移量 0)
// 写入内容包括字符串长度信息和实际字节数据
unsafeBuffer.putStringAscii(0, message);
// 日志输出:使用 Slf4j 的 log.info()
// {} 是占位符,会被 message 的值替换
log.info("send {}", message);
// publication.offer(unsafeBuffer):
// 尝试将缓冲区内容作为消息发送
// 返回值:
// - 正数(位置索引):消息已入队,等待发送
// - 0 或负数:通道背压(channel back-pressure),需要重试
// 这是非阻塞操作,如果通道满会立即返回而不等待
while (publication.offer(unsafeBuffer) < 0) {
// 发送失败时,调用 idle() 等待一下再重试
// 实际应用中可以通过调整空闲策略来控制重试频率
idleStrategy.idle();
}
// -------------------------------------------------------
// 第六步:接收消息
// -------------------------------------------------------
// FragmentHandler:消息片段处理器函数式接口
// lambda 表达式实现:(buffer, offset, length, header) -> { ... }
// 参数说明:
// - buffer:包含消息的 UnsafeBuffer
// - offset:消息在缓冲区中的起始偏移量
// - length:消息的实际长度(字节数)
// - header:消息头信息(通常用不到)
final FragmentHandler handler = (buffer, offset, length, header) -> {
// buffer.getStringAscii(offset):
// 从缓冲区指定偏移量读取 ASCII 字符串
// 注意:这里使用 length 参数更准确,但 getStringAscii 会自动读取长度信息
log.info("receive {}", buffer.getStringAscii(offset));
};
// subscription.poll(handler, 1):
// 轮询订阅者队列,尝试接收消息
// 参数:
// - handler:消息处理器
// - 1:每次最多处理 1 个消息片段(fragment)
// 返回值:实际处理的消息数量
// 返回值 <= 0 表示没有收到消息
while (subscription.poll(handler, 1) <= 0) {
// 没有消息时,调用 idle() 等待
idleStrategy.idle();
}
// 当 subscription.poll() 返回 > 0 时,handler 会被调用,日志会输出 "receive hello aeron"
}
// try-with-resources 自动关闭:
// 关闭顺序与创建顺序相反:publication -> subscription -> aeron -> driver
// 确保所有资源正确释放
}
}