lruTracker = new LinkedHashMap<>(16, 0.75f, true);
+ private static final int MAX_CONNECTIONS = RpcConfig.getInstance().getMaxConnections();
public static Channel get(InetSocketAddress inetSocketAddress, Bootstrap bootstrap) {
String key = inetSocketAddress.toString();
@@ -25,9 +29,12 @@ public static Channel get(InetSocketAddress inetSocketAddress, Bootstrap bootstr
if (channels.containsKey(key)) {
Channel channel = channels.get(key);
if (channel != null && channel.isActive()) {
+ synchronized (lruTracker) {
+ lruTracker.put(key, System.currentTimeMillis());
+ }
return channel;
} else {
- channels.remove(key);
+ removeChannel(key);
}
}
@@ -36,12 +43,42 @@ public static Channel get(InetSocketAddress inetSocketAddress, Bootstrap bootstr
// 新连接建立成功后放回缓存
if (channel != null) {
- channels.put(key, channel);
+ addChannel(key, channel);
}
return channel;
}
+ private static void addChannel(String key, Channel channel) {
+ synchronized (lruTracker) {
+ if (channels.size() >= MAX_CONNECTIONS) {
+ evictLRU();
+ }
+ channels.put(key, channel);
+ lruTracker.put(key, System.currentTimeMillis());
+ }
+ }
+
+ private static void removeChannel(String key) {
+ synchronized (lruTracker) {
+ channels.remove(key);
+ lruTracker.remove(key);
+ }
+ }
+
+ private static void evictLRU() {
+ if (lruTracker.isEmpty()) {
+ return;
+ }
+ String oldestKey = lruTracker.keySet().iterator().next();
+ Channel oldChannel = channels.remove(oldestKey);
+ lruTracker.remove(oldestKey);
+ if (oldChannel != null && oldChannel.isActive()) {
+ oldChannel.close();
+ }
+ log.info("连接池已满,淘汰最久未使用的连接: {}", oldestKey);
+ }
+
private static Channel connect(Bootstrap bootstrap, InetSocketAddress inetSocketAddress) {
CountDownLatch latch = new CountDownLatch(1);
final Channel[] channelHolder = new Channel[1];
diff --git a/rpc-transport-netty/src/main/java/com/xiaoyu/rpc/core/protocol/ProtocolDetectHandler.java b/rpc-transport-netty/src/main/java/com/xiaoyu/rpc/core/protocol/ProtocolDetectHandler.java
new file mode 100644
index 0000000..84c9683
--- /dev/null
+++ b/rpc-transport-netty/src/main/java/com/xiaoyu/rpc/core/protocol/ProtocolDetectHandler.java
@@ -0,0 +1,122 @@
+package com.xiaoyu.rpc.core.protocol;
+
+import com.xiaoyu.rpc.core.server.NettyRpcHandler;
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.ByteToMessageDecoder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+/**
+ * 协议嗅探器 —— 服务端自动识别多种协议
+ *
+ * 原理:连接建立后,偷看(peek)入站数据的前几个字节,根据特征判断协议类型:
+ *
+ * - 0xAABBCCDD → TCP 私有协议(NettyProtocol)
+ * - 0x50524920 ("PRI ") → HTTP/2 Connection Preface(Http2Protocol 或
+ * GrpcProtocol)
+ * - HTTP 方法名(GET / POST / PUT / HEAD / DELETE / OPTIONS / PATCH)→
+ * HTTP/1.1(HttpProtocol)
+ *
+ * 识别后动态配置 pipeline 并移除自身,后续按确定的协议处理。
+ *
+ * 注意:gRPC 底层也是 HTTP/2 传输,无法在字节层面与普通 HTTP/2 区分。
+ * 通过构造函数的 {@code http2ProtocolName} 参数控制 HTTP/2 连接的处理方式。
+ */
+public class ProtocolDetectHandler extends ByteToMessageDecoder {
+
+ private static final Logger log = LoggerFactory.getLogger(ProtocolDetectHandler.class);
+
+ /** TCP 私有协议魔数,与 NettyRpcEncoder/NettyRpcDecoder 一致 */
+ private static final int NETTY_MAGIC = 0xAABBCCDD;
+
+ /** HTTP/2 Connection Preface 前 4 字节: "PRI " = 0x50524920 */
+ private static final int HTTP2_MAGIC = 0x50524920;
+
+ // 常见 HTTP/1.1 方法的首字母 ASCII 码
+ private static final byte BYTE_G = 'G'; // GET
+ private static final byte BYTE_P = 'P'; // POST, PUT, PATCH
+ private static final byte BYTE_D = 'D'; // DELETE
+ private static final byte BYTE_H = 'H'; // HEAD
+ private static final byte BYTE_O = 'O'; // OPTIONS
+ private static final byte BYTE_T = 'T'; // TRACE
+ private static final byte BYTE_C = 'C'; // CONNECT
+
+ /**
+ * 当检测到 HTTP/2 Connection Preface 时使用的协议名。
+ * 因为 gRPC 底层也是 HTTP/2,无法在字节层面区分,
+ * 所以通过这个参数指定:可以是 "http2" 或 "grpc"。
+ * 默认为 "http2"。
+ */
+ private final String http2ProtocolName;
+
+ /**
+ * 默认构造函数,HTTP/2 连接使用 Http2Protocol 处理
+ */
+ public ProtocolDetectHandler() {
+ this("http2");
+ }
+
+ /**
+ * 指定 HTTP/2 连接的处理协议
+ *
+ * @param http2ProtocolName HTTP/2 连接使用的协议名("http2" 或 "grpc")
+ */
+ public ProtocolDetectHandler(String http2ProtocolName) {
+ this.http2ProtocolName = http2ProtocolName;
+ }
+
+ @Override
+ protected void decode(ChannelHandlerContext ctx, ByteBuf in, List