diff --git a/neutrino-proxy-client/src/main/resources/app.yml b/neutrino-proxy-client/src/main/resources/app.yml index 5492ea46..8bc09828 100644 --- a/neutrino-proxy-client/src/main/resources/app.yml +++ b/neutrino-proxy-client/src/main/resources/app.yml @@ -3,7 +3,7 @@ solon: add: ./app.yml app: name: neutrino-proxy-client - version: 2.0.2 + version: 2.0.4.t1 # 日志级别 solon.logging.appender: console: diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/base/proxy/ProxyConfiguration.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/base/proxy/ProxyConfiguration.java index 275fd0d3..a9a69fb8 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/base/proxy/ProxyConfiguration.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/base/proxy/ProxyConfiguration.java @@ -22,6 +22,7 @@ import org.dromara.neutrinoproxy.server.proxy.core.UdpVisitorChannelHandler; import org.dromara.neutrinoproxy.server.proxy.security.TcpVisitorSecurityChannelHandler; import org.dromara.neutrinoproxy.server.proxy.security.UdpVisitorSecurityChannelHandler; import org.dromara.neutrinoproxy.server.proxy.security.VisitorFlowLimiterChannelHandler; +import org.dromara.neutrinoproxy.server.util.ProxyUtil; import org.noear.solon.Solon; import org.noear.solon.annotation.Bean; import org.noear.solon.annotation.Configuration; @@ -46,6 +47,8 @@ public class ProxyConfiguration implements LifecycleBean { null : ProxyDataTypeEnum.of((int)proxyMessage.getType()).getName()); Solon.context().wrapAndPut(Dispatcher.class, dispatcher); + + ProxyUtil.init(); } @Bean("tcpServerBossGroup") diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/StatsController.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/StatsController.java new file mode 100644 index 00000000..a2568544 --- /dev/null +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/StatsController.java @@ -0,0 +1,26 @@ +package org.dromara.neutrinoproxy.server.controller; + +import org.dromara.neutrinoproxy.server.base.rest.Authorization; +import org.dromara.neutrinoproxy.server.controller.res.stats.StatsInfoRes; +import org.dromara.neutrinoproxy.server.util.ProxyUtil; +import org.noear.solon.annotation.Controller; +import org.noear.solon.annotation.Get; +import org.noear.solon.annotation.Mapping; + +/** + * + * @author: wen.y + * @date: 2026/1/20 + */ +@Mapping("/stats") +@Controller +public class StatsController { + + @Authorization(login = false) + @Get + @Mapping("/info") + public StatsInfoRes info() { + return new StatsInfoRes().setCacheInfo(ProxyUtil.getCacheInfo()); + } + +} diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/res/stats/StatsInfoRes.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/res/stats/StatsInfoRes.java new file mode 100644 index 00000000..ef5ef39e --- /dev/null +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/controller/res/stats/StatsInfoRes.java @@ -0,0 +1,31 @@ +package org.dromara.neutrinoproxy.server.controller.res.stats; + +import lombok.Data; +import lombok.experimental.Accessors; + +/** + * + * @author: wen.y + * @date: 2026/1/20 + */ +@Accessors(chain = true) +@Data +public class StatsInfoRes { + private CacheInfo cacheInfo; + + @Data + public static class CacheInfo { + private Integer proxyInfoMapSize; + private Integer serverPortToCmdChannelMapSize; + private Integer licenseToCmdChannelMapSize; + private Integer serverPortToVisitorChannelMapSize; + private Integer proxyConnectAttachmentMapSize; + private Integer fullDomainToServerPortMapSize; + private Integer domainToDomainNameIdMapSize; + private Integer licenseIdToClientIdMapSize; + private Integer visitorIdToSocketAddressMapSize; + private Integer socketAddressToVisitorIdMapSize; + private Integer visitorIdToTunnelChannelMapSize; + } + +} diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/handler/ProxyMessageConnectHandler.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/handler/ProxyMessageConnectHandler.java index 06c21571..048cadd7 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/handler/ProxyMessageConnectHandler.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/proxy/handler/ProxyMessageConnectHandler.java @@ -79,14 +79,7 @@ public class ProxyMessageConnectHandler implements ProxyMessageHandler { ctx.channel().close(); return; } - // 获取代理附加对象 - Constants.ProxyAttachment proxyAttachment = visitorChannel.attr(Constants.PROXY_CONNECT_ATTACHMENT).get(); - if (null == proxyAttachment) { - ctx.channel().writeAndFlush(ProxyMessage.buildErrMessage(ExceptionEnum.CONNECT_FAILED, "server error,visitor channel proxy attachment not found!")); - ctx.channel().close(); - visitorChannel.close(); - return; - } + ctx.channel().attr(Constants.VISITOR_ID).set(visitorId); ctx.channel().attr(Constants.LICENSE_ID).set(licenseDO.getId()); ctx.channel().attr(Constants.NEXT_CHANNEL).set(visitorChannel); @@ -95,8 +88,16 @@ public class ProxyMessageConnectHandler implements ProxyMessageHandler { // 代理客户端与后端服务器连接成功,修改用户连接为可读状态 visitorChannel.config().setOption(ChannelOption.AUTO_READ, true); - // 转发来自visitor的首次代理数据 - proxyAttachment.execute(visitorChannel); + + // 获取代理附加对象 + Constants.ProxyAttachment proxyAttachment = visitorChannel.attr(Constants.PROXY_CONNECT_ATTACHMENT).get(); + if (null != proxyAttachment) { + // 转发来自visitor的首次代理数据 + proxyAttachment.execute(visitorChannel); + + // 此处时TCP代理,不一定有代理附加对象,不能因为没有而直接释放visitorChannel + } + // // 获取代理附加对象 // ProxyAttachment proxyAttachment = ProxyUtil.getProxyConnectAttachment(visitorId); diff --git a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/util/ProxyUtil.java b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/util/ProxyUtil.java index 5ce63b71..d2908dcd 100644 --- a/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/util/ProxyUtil.java +++ b/neutrino-proxy-server/src/main/java/org/dromara/neutrinoproxy/server/util/ProxyUtil.java @@ -6,16 +6,21 @@ import org.apache.commons.lang3.StringUtils; import org.dromara.neutrinoproxy.core.ChannelAttribute; import org.dromara.neutrinoproxy.core.Constants; import org.dromara.neutrinoproxy.server.constant.NetworkProtocolEnum; +import org.dromara.neutrinoproxy.server.controller.res.stats.StatsInfoRes; import org.dromara.neutrinoproxy.server.proxy.domain.CmdChannelAttachInfo; import org.dromara.neutrinoproxy.server.proxy.domain.ProxyMapping; import org.dromara.neutrinoproxy.server.proxy.domain.VisitorChannelAttachInfo; import io.netty.channel.Channel; import io.netty.util.AttributeKey; +import org.dromara.solonplugins.job.CustomThreadFactory; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.*; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -88,6 +93,46 @@ public class ProxyUtil { */ private static Map visitorIdToTunnelChannelMap = new HashMap<>(); + /** + * cache扫描器 + */ + private static final ScheduledExecutorService cacheScanner = Executors.newSingleThreadScheduledExecutor(new CustomThreadFactory("cacheScanner")); + + + /** + * 初始化,启动一个定时器,定时清理缓存 + */ + public static void init() { + cacheScanner.scheduleWithFixedDelay(ProxyUtil::cacheScan, 5, 5, TimeUnit.SECONDS); + } + + public static synchronized void cacheScan() { + for (Integer key : serverPortToCmdChannelMap.keySet()) { + Channel channel = serverPortToCmdChannelMap.get(key); + if (null == channel || !channel.isRegistered()) { + serverPortToCmdChannelMap.remove(key); + } + } + for (Integer key : licenseToCmdChannelMap.keySet()) { + Channel channel = licenseToCmdChannelMap.get(key); + if (null == channel || !channel.isRegistered()) { + licenseToCmdChannelMap.remove(key); + } + } + for (Integer key : serverPortToVisitorChannel.keySet()) { + Channel channel = serverPortToVisitorChannel.get(key); + if (null == channel || !channel.isRegistered()) { + serverPortToVisitorChannel.remove(key); + } + } + for (String key : visitorIdToTunnelChannelMap.keySet()) { + Channel channel = visitorIdToTunnelChannelMap.get(key); + if (null == channel || !channel.isRegistered()) { + visitorIdToTunnelChannelMap.remove(key); + } + } + } + /** * 初始化代理信息 * @param licenseId licenseId @@ -524,4 +569,20 @@ public class ProxyUtil { return visitorIdToTunnelChannelMap.get(visitorId); } + public static StatsInfoRes.CacheInfo getCacheInfo() { + return new StatsInfoRes.CacheInfo() + .setProxyInfoMapSize(proxyInfoMap.size()) + .setServerPortToCmdChannelMapSize(serverPortToCmdChannelMap.size()) + .setLicenseToCmdChannelMapSize(licenseToCmdChannelMap.size()) + .setServerPortToVisitorChannelMapSize(serverPortToVisitorChannel.size()) + .setProxyConnectAttachmentMapSize(proxyConnectAttachmentMap.size()) + .setFullDomainToServerPortMapSize(fullDomainToServerPortMap.size()) + .setDomainToDomainNameIdMapSize(domainToDomainNameIdMap.size()) + .setLicenseIdToClientIdMapSize(licenseIdToClientIdMap.size()) + .setVisitorIdToSocketAddressMapSize(visitorIdToSocketAddressMap.size()) + .setSocketAddressToVisitorIdMapSize(socketAddressToVisitorIdMap.size()) + .setVisitorIdToTunnelChannelMapSize(visitorIdToTunnelChannelMap.size()) + ; + } + } diff --git a/neutrino-proxy-server/src/main/resources/app.yml b/neutrino-proxy-server/src/main/resources/app.yml index 162b021e..ec456e6f 100644 --- a/neutrino-proxy-server/src/main/resources/app.yml +++ b/neutrino-proxy-server/src/main/resources/app.yml @@ -5,7 +5,7 @@ server: solon: app: name: neutrino-proxy-server - version: 2.0.2 + version: 2.0.4.t1 config: add: ./app.yml # 日志级别