作者 钟来

开发透传服务

正在显示 31 个修改的文件 包含 341 行增加784 行删除
1 driverClassName=com.mysql.cj.jdbc.Driver 1 driverClassName=com.mysql.cj.jdbc.Driver
2 -#url=jdbc:mysql://rm-wz9740un21f09iokuao.mysql.rds.aliyuncs.com:3306/mqtt_broker?useUnicode=true&characterEncoding=utf8&autoReconnect=true  
3 -#username=luhui  
4 -#password=Luhui586  
5 -url=jdbc:mysql://rm-wz9446bn79p0r80ew0o.mysql.rds.aliyuncs.com:3306/runing_fish?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai 2 +url=jdbc:mysql://rm-wz9740un21f09iokuao.mysql.rds.aliyuncs.com:3306/mqtt_broker?useUnicode=true&characterEncoding=utf8&autoReconnect=true
6 username=luhui 3 username=luhui
7 password=Luhui586 4 password=Luhui586
  5 +#url=jdbc:mysql://rm-wz9446bn79p0r80ew0o.mysql.rds.aliyuncs.com:3306/runing_fish?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai
  6 +#username=luhui
  7 +#password=Luhui586
8 #\u6700\u5927\u8FDE\u63A5\u6570\u91CF 8 #\u6700\u5927\u8FDE\u63A5\u6570\u91CF
9 maxActive=100 9 maxActive=100
10 #\u6700\u5927\u7A7A\u95F2\u8FDE\u63A5 10 #\u6700\u5927\u7A7A\u95F2\u8FDE\u63A5
1 -package com.zhonglai.luhui.neutrino.proxy.client;  
2 -  
3 -import com.fasterxml.jackson.databind.ObjectMapper;  
4 -import com.zhonglai.luhui.neutrino.proxy.common.RegisterMessage;  
5 -  
6 -import java.io.BufferedReader;  
7 -import java.io.InputStreamReader;  
8 -import java.io.PrintWriter;  
9 -import java.net.Socket;  
10 -import java.util.HashMap;  
11 -import java.util.Map;  
12 -  
13 -public class ClientMain {  
14 - public static void main(String[] args) throws Exception {  
15 - Socket socket = new Socket("127.0.0.1", 9000);  
16 - PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);  
17 - BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));  
18 -  
19 - ObjectMapper mapper = new ObjectMapper();  
20 - RegisterMessage register = new RegisterMessage("clientA", "abc123");  
21 -  
22 - writer.println(mapper.writeValueAsString(register));  
23 -  
24 - String response = reader.readLine();  
25 - System.out.println("服务端响应:" + response);  
26 - new Thread(new HeartbeatTask(socket)).start();  
27 - }  
28 -}  
1 -package com.zhonglai.luhui.neutrino.proxy.client;  
2 -  
3 -import com.fasterxml.jackson.databind.ObjectMapper;  
4 -  
5 -import java.io.PrintWriter;  
6 -import java.net.Socket;  
7 -import java.util.HashMap;  
8 -import java.util.Map;  
9 -  
10 -/**  
11 - * 心跳线程(每30秒发送)  
12 - */  
13 -public class HeartbeatTask implements Runnable {  
14 - private final Socket socket;  
15 -  
16 - public HeartbeatTask(Socket socket) {  
17 - this.socket = socket;  
18 - }  
19 -  
20 - @Override  
21 - public void run() {  
22 - try {  
23 - PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);  
24 - ObjectMapper mapper = new ObjectMapper();  
25 - while (!socket.isClosed()) {  
26 - Map<String, String> heartbeat = new HashMap<>();  
27 - heartbeat.put("type", "heartbeat");  
28 - heartbeat.put("clientId", "clientA");  
29 - writer.println(mapper.writeValueAsString(heartbeat));  
30 - Thread.sleep(30000); // 30秒心跳  
31 - }  
32 - } catch (Exception e) {  
33 - System.out.println("心跳发送失败:" + e.getMessage());  
34 - }  
35 - }  
36 -}  
@@ -19,8 +19,26 @@ @@ -19,8 +19,26 @@
19 19
20 <dependencies> 20 <dependencies>
21 <dependency> 21 <dependency>
22 - <groupId>com.fasterxml.jackson.core</groupId>  
23 - <artifactId>jackson-databind</artifactId> 22 + <groupId>org.slf4j</groupId>
  23 + <artifactId>slf4j-api</artifactId>
  24 + </dependency>
  25 +
  26 + <dependency>
  27 + <groupId>ch.qos.logback</groupId>
  28 + <artifactId>logback-classic</artifactId>
  29 + </dependency>
  30 + <!-- mqtt -->
  31 + <dependency>
  32 + <groupId>org.eclipse.paho</groupId>
  33 + <artifactId>org.eclipse.paho.client.mqttv3</artifactId>
  34 + </dependency>
  35 + <dependency>
  36 + <groupId>com.alibaba</groupId>
  37 + <artifactId>fastjson</artifactId>
  38 + </dependency>
  39 + <dependency>
  40 + <groupId>cn.hutool</groupId>
  41 + <artifactId>hutool-all</artifactId>
24 </dependency> 42 </dependency>
25 </dependencies> 43 </dependencies>
26 44
1 -package com.zhonglai.luhui.neutrino.proxy.common;  
2 -  
3 -/**  
4 - * 注册协议类  
5 - */  
6 -public class RegisterMessage {  
7 - public String type = "register";  
8 - public String clientId;  
9 - public String token;  
10 -  
11 - public RegisterMessage() {}  
12 -  
13 - public RegisterMessage(String clientId, String token) {  
14 - this.clientId = clientId;  
15 - this.token = token;  
16 - }  
17 -}  
1 -package com.zhonglai.luhui.neutrino.proxy.common;  
2 -  
3 -/**  
4 - * 隧道请求协议  
5 - */  
6 -public class TunnelMessage {  
7 - public String type = "tunnel";  
8 - public String tunnelId; // UUID  
9 - public String clientId;  
10 - public int remotePort; // 公网端口  
11 - public String targetHost;  
12 - public int targetPort;  
13 -  
14 -}  
  1 +package com.zhonglai.luhui.neutrino.proxy.common.mqtt;
  2 +
  3 +import com.alibaba.fastjson.JSONObject;
  4 +import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
  5 +import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
  6 +import org.eclipse.paho.client.mqttv3.MqttMessage;
  7 +import org.slf4j.Logger;
  8 +import org.slf4j.LoggerFactory;
  9 +
  10 +import java.util.Set;
  11 +
  12 +/**
  13 + * mqtt回调
  14 + */
  15 +public class MqttCallback implements MqttCallbackExtended {
  16 + private final Logger log = LoggerFactory.getLogger(this.getClass());
  17 +
  18 + private MqttService mqttclient;
  19 + public MqttCallback(MqttService mqttclient)
  20 + {
  21 + this.mqttclient = mqttclient;
  22 + }
  23 + @Override
  24 + public void connectComplete(boolean b, String s) {
  25 + log.info("连接成功");
  26 + Set<String> topics = mqttclient.subscribe();
  27 + log.info("-----------订阅成功:{}--------------------",topics);
  28 + }
  29 +
  30 + @Override
  31 + public void connectionLost(Throwable cause) {
  32 + log.error("连接丢失重新链接",cause);
  33 + while (!mqttclient.connect())
  34 + {
  35 + try {
  36 + Thread.sleep(60000);
  37 + } catch (InterruptedException e) {
  38 + e.printStackTrace();
  39 + }
  40 + }
  41 + }
  42 +
  43 + @Override
  44 + public void messageArrived(String topic, MqttMessage message) throws Exception {
  45 + log.info("mqtt发来消息>>>>>>>:{} 《===============》 字符串:【{}】",topic,message.toString());
  46 + String str = new String(message.getPayload());
  47 + mqttclient.operate(new Topic(topic),JSONObject.parseObject(str));
  48 + }
  49 +
  50 + @Override
  51 + public void deliveryComplete(IMqttDeliveryToken token) {
  52 + log.info("消息发送完成");
  53 + }
  54 +
  55 +
  56 +}
  1 +package com.zhonglai.luhui.neutrino.proxy.common.mqtt;
  2 +
  3 +import java.io.FileReader;
  4 +import java.util.HashSet;
  5 +import java.util.Properties;
  6 +import java.util.Set;
  7 +
  8 +public class MqttConfig {
  9 + public static String mqttBroker;
  10 + public static String mqttUserName;
  11 + public static String mqttPassword;
  12 + public static String mqttClientId;
  13 +
  14 + public static Set<String> subTopic;
  15 +
  16 + public static void init()
  17 + {
  18 + Properties properties = loadProperties();
  19 + mqttBroker = properties.getProperty("broker");
  20 + mqttUserName = properties.getProperty("username");
  21 + mqttPassword = properties.getProperty("password");
  22 + mqttClientId = properties.getProperty("clientId");
  23 + subTopic = new HashSet<>();
  24 +
  25 + String topicsStr = properties.getProperty("subTopic");
  26 + if(null != topicsStr && !"".equals(topicsStr))
  27 + {
  28 + if (topicsStr != null && !topicsStr.trim().isEmpty()) {
  29 + String[] topics = topicsStr.split(",");
  30 + for (String topic : topics) {
  31 + subTopic.add(topic.trim());
  32 + }
  33 + }
  34 + }
  35 + }
  36 + public static Properties loadProperties()
  37 + {
  38 + Properties properties = new Properties();
  39 + try {
  40 + if(null != System.getProperty("configPath") && !"".equals(System.getProperty("configPath")))
  41 + {
  42 + properties.load(new FileReader(System.getProperty("configPath")+"/mqtt.properties"));
  43 + }else{
  44 + properties.load(MqttConfig.class.getClassLoader().getResourceAsStream("mqtt.properties"));
  45 + }
  46 +
  47 + } catch (Exception e) {
  48 + e.printStackTrace();
  49 + }
  50 + return properties;
  51 + }
  52 +}
  1 +package com.zhonglai.luhui.neutrino.proxy.common.mqtt;
  2 +
  3 +import com.alibaba.fastjson.JSONObject;
  4 +import org.eclipse.paho.client.mqttv3.MqttClient;
  5 +import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
  6 +import org.eclipse.paho.client.mqttv3.MqttException;
  7 +import org.eclipse.paho.client.mqttv3.MqttMessage;
  8 +import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
  9 +import org.slf4j.Logger;
  10 +import org.slf4j.LoggerFactory;
  11 +
  12 +import javax.annotation.PreDestroy;
  13 +import java.util.Set;
  14 +
  15 +/**
  16 + * 通过mqtt实现消息推送,用来处理长链接
  17 + */
  18 +public class MqttService {
  19 + private final Logger log = LoggerFactory.getLogger(this.getClass());
  20 +
  21 + private MqttClient mqttclient;
  22 + private MqttConnectOptions options;
  23 +
  24 + private OperateService operateService;
  25 +
  26 + // 使用 volatile 确保多线程内存可见性和禁止指令重排
  27 + private static volatile MqttService instance;
  28 +
  29 + private MqttService(OperateService operateService) {
  30 + this.operateService = operateService;
  31 + // 初始化代码
  32 + try {
  33 + sart();
  34 + } catch (MqttException e) {
  35 + throw new RuntimeException(e);
  36 + }
  37 + }
  38 +
  39 + public static MqttService getInstance( OperateService operateService) {
  40 + if (instance == null) {
  41 + synchronized (MqttService.class) {
  42 + if (instance == null) {
  43 + instance = new MqttService(operateService);
  44 + }
  45 + }
  46 + }
  47 + return instance;
  48 + }
  49 +
  50 + private void init() throws MqttException {
  51 + if(null == mqttclient)
  52 + {
  53 + mqttclient = new MqttClient(MqttConfig.mqttBroker, MqttConfig.mqttClientId, new MemoryPersistence());
  54 + }
  55 + options = new MqttConnectOptions();
  56 + options.setCleanSession(true);
  57 + options.setConnectionTimeout(15);
  58 + options.setKeepAliveInterval(60); // 添加心跳设置,单位为秒
  59 + //设置断开后重新连接
  60 + options.setAutomaticReconnect(true);
  61 + mqttclient.setCallback(new MqttCallback(this));
  62 + }
  63 +
  64 + public boolean connect() {
  65 + try {
  66 + options.setUserName(MqttConfig.mqttUserName);
  67 + options.setPassword(MqttConfig.mqttPassword.toCharArray());
  68 + mqttclient.connect(options);
  69 + return true;
  70 + } catch (MqttException e) {
  71 + log.error("-----------mqtt连接服务器失败--------------------",e);
  72 + }
  73 + return false;
  74 + }
  75 +
  76 + public void sart() throws MqttException{
  77 + MqttConfig.init();
  78 + log.info("-----------开始启动mqtt监听服务--------------------");
  79 + init();
  80 + connect();
  81 + log.info("-----------mqtt连接服务器成功--------------------");
  82 +
  83 + }
  84 +
  85 + @PreDestroy
  86 + public void stop() throws MqttException {
  87 + if(null != mqttclient)
  88 + {
  89 + log.info("退出mqtt服务");
  90 + mqttclient.unsubscribe(MqttConfig.subTopic.toArray(new String[MqttConfig.subTopic.size()]));
  91 + mqttclient.disconnect();
  92 + mqttclient.close();
  93 + }
  94 + instance = null;
  95 + }
  96 + public void publish(String topic, String messageStr) throws MqttException {
  97 + MqttMessage message = new MqttMessage();
  98 + message.setPayload(messageStr.getBytes());
  99 + mqttclient.publish(topic,message);
  100 + }
  101 +
  102 + public Set<String> subscribe()
  103 + {
  104 + try {
  105 + mqttclient.subscribe(MqttConfig.subTopic.toArray(new String[MqttConfig.subTopic.size()]));
  106 + } catch (MqttException e) {
  107 + e.printStackTrace();
  108 + }
  109 + return MqttConfig.subTopic;
  110 + }
  111 +
  112 + public boolean isConnect()
  113 + {
  114 + return mqttclient != null && mqttclient.isConnected();
  115 + }
  116 +
  117 + public void operate(Topic topic, JSONObject payloaddata)
  118 + {
  119 + operateService.operate(topic,payloaddata);
  120 + }
  121 +}
  1 +package com.zhonglai.luhui.neutrino.proxy.common.mqtt;
  2 +
  3 +import com.alibaba.fastjson.JSONObject;
  4 +
  5 +public interface OperateService {
  6 + /**
  7 + * 指令操作
  8 + * @param topic
  9 + * @param data
  10 + */
  11 + void operate(Topic topic, JSONObject data);
  12 +}
  1 +package com.zhonglai.luhui.neutrino.proxy.common.mqtt;
  2 +
  3 +public class Topic {
  4 + public static final String TOPIC_PUT = "PUT";
  5 + public static final String TOPIC_READ = "READ";
  6 + private String topicType;
  7 + private String time;
  8 + public Topic()
  9 + {
  10 +
  11 + }
  12 + public Topic(String stopicStr)
  13 + {
  14 + String[] strs = stopicStr.split("/");
  15 + topicType = strs[0];
  16 + time = strs[1];
  17 + }
  18 +
  19 + public String getTopicType() {
  20 + return topicType;
  21 + }
  22 +
  23 + public void setTopicType(String topicType) {
  24 + this.topicType = topicType;
  25 + }
  26 +
  27 + public String getTime() {
  28 + return time;
  29 + }
  30 +
  31 + public void setTime(String time) {
  32 + this.time = time;
  33 + }
  34 +}
1 -package com.zhonglai.luhui.neutrino.proxy.server;  
2 -  
3 -import io.netty.channel.Channel;  
4 -  
5 -import java.util.Map;  
6 -import java.util.concurrent.ConcurrentHashMap;  
7 -  
8 -/**  
9 - * 服务端维护客户端连接  
10 - */  
11 -public class ClientSessionManager {  
12 - private static final Map<String, Channel> clientMap = new ConcurrentHashMap<>();  
13 -  
14 - public static void register(String clientId, Channel channel) {  
15 - clientMap.put(clientId, channel);  
16 - System.out.println("注册客户端:" + clientId);  
17 - }  
18 -  
19 - public static void remove(Channel channel) {  
20 - clientMap.entrySet().removeIf(entry -> entry.getValue().equals(channel));  
21 - }  
22 -  
23 - public static void heartbeat(String clientId) {  
24 - // 更新最后心跳时间(后续可扩展)  
25 - System.out.println("收到心跳:" + clientId);  
26 - }  
27 -  
28 - public static boolean isClientConnected(String clientId) {  
29 - return clientMap.containsKey(clientId);  
30 - }  
31 -  
32 - public static Channel getChannel(String clientId) {  
33 - return clientMap.get(clientId);  
34 - }  
35 -}  
1 package com.zhonglai.luhui.neutrino.proxy.server; 1 package com.zhonglai.luhui.neutrino.proxy.server;
2 2
3 -import com.zhonglai.luhui.neutrino.proxy.server.httpservice.SimpleHttpServer;  
4 -import com.zhonglai.luhui.neutrino.proxy.server.proxy.ProxyServer; 3 +
  4 +import com.zhonglai.luhui.neutrino.proxy.common.mqtt.MqttConfig;
  5 +import com.zhonglai.luhui.neutrino.proxy.common.mqtt.MqttService;
  6 +import com.zhonglai.luhui.neutrino.proxy.server.operate.OperateServiceImpl;
  7 +import org.eclipse.paho.client.mqttv3.MqttException;
  8 +import org.slf4j.Logger;
  9 +import org.slf4j.LoggerFactory;
5 10
6 public class ControlServer { 11 public class ControlServer {
  12 + private static Logger logger = LoggerFactory.getLogger(ControlServer.class);
7 public static void main(String[] args) throws Exception { 13 public static void main(String[] args) throws Exception {
8 - //启动代理服务  
9 - ProxyServer.start(9000); 14 + MqttService mqttService = MqttService.getInstance(new OperateServiceImpl());
  15 + mqttService.sart();
10 16
11 - //启动接口服务  
12 - SimpleHttpServer.startHttpServer(8080); 17 + // 使用Lambda表达式添加关闭钩子
  18 + Runtime.getRuntime().addShutdownHook(new Thread(() -> {
  19 + logger.info("正在关闭应用程序...");
  20 + try {
  21 + mqttService.stop();
  22 + } catch (MqttException e) {
  23 + logger.error("MQTT服务停止异常", e);
  24 + }
  25 + logger.info("应用程序关闭成功");
  26 + }));
13 } 27 }
14 } 28 }
1 -package com.zhonglai.luhui.neutrino.proxy.server.function;  
2 -  
3 -import com.fasterxml.jackson.databind.ObjectMapper;  
4 -import com.fasterxml.jackson.databind.node.ObjectNode;  
5 -import com.zhonglai.luhui.neutrino.proxy.common.TunnelMessage;  
6 -import com.zhonglai.luhui.neutrino.proxy.server.ClientSessionManager;  
7 -import io.netty.channel.Channel;  
8 -  
9 -import java.io.*;  
10 -import java.net.*;  
11 -import java.util.Map;  
12 -import java.util.UUID;  
13 -import java.util.concurrent.*;  
14 -  
15 -public class PortForwardManager {  
16 - public static final Map<Integer, ServerSocket> activeServers = new ConcurrentHashMap<>();  
17 - public static final Map<String, Integer> clientBandwidthMap = new ConcurrentHashMap<>();  
18 -  
19 - public void startPortForward(int remotePort, String clientId, int targetPort) throws IOException {  
20 - if (activeServers.containsKey(remotePort)) return;  
21 -  
22 - ServerSocket serverSocket = new ServerSocket(remotePort);  
23 - activeServers.put(remotePort, serverSocket);  
24 -  
25 - new Thread(() -> {  
26 - System.out.println("端口转发监听启动: " + remotePort + " => " + clientId + ":" + targetPort);  
27 - while (true) {  
28 - try {  
29 - Socket externalSocket = serverSocket.accept(); // 有外部连接过来了  
30 - String tunnelId = UUID.randomUUID().toString();  
31 -  
32 - // 1. 注册外部连接 socket(暂时只 createBridge)  
33 - TunnelBridgePool.createBridge(tunnelId, externalSocket);  
34 -  
35 - // 2. 通过 control 通道通知客户端建立内网连接  
36 - Channel channel = ClientSessionManager.getChannel(clientId);  
37 - if (channel != null) {  
38 - ObjectMapper mapper = new ObjectMapper();  
39 - ObjectNode msg = mapper.createObjectNode();  
40 - msg.put("type", "create_tunnel");  
41 - msg.put("tunnelId", tunnelId);  
42 - msg.put("targetPort", targetPort);  
43 - channel.writeAndFlush(msg.toString() + "\n");  
44 - } else {  
45 - System.err.println("客户端未连接,无法建立隧道: " + clientId);  
46 - externalSocket.close();  
47 - }  
48 -  
49 - // ✅ 注意:此时 **不要 bindAndStart**,等客户端连接进来后才执行。  
50 -  
51 - } catch (IOException e) {  
52 - e.printStackTrace();  
53 - }  
54 - }  
55 - }).start();  
56 - }  
57 -}  
1 -package com.zhonglai.luhui.neutrino.proxy.server.function;  
2 -  
3 -import com.zhonglai.luhui.neutrino.proxy.server.function.ratelimit.RateLimitedInputStream;  
4 -import com.zhonglai.luhui.neutrino.proxy.server.function.ratelimit.RateLimitedOutputStream;  
5 -import com.zhonglai.luhui.neutrino.proxy.server.function.ratelimit.TokenBucket;  
6 -  
7 -import java.io.IOException;  
8 -import java.io.InputStream;  
9 -import java.io.OutputStream;  
10 -import java.net.Socket;  
11 -import java.util.Map;  
12 -import java.util.concurrent.ConcurrentHashMap;  
13 -  
14 -/**  
15 - * 服务端建立转发通道并绑定 tunnelId  
16 - */  
17 -public class TunnelBridgePool {  
18 - private static final Map<String, Socket> bridgeMap = new ConcurrentHashMap<>();  
19 - private static final Map<String, Integer> bandwidthMap = new ConcurrentHashMap<>();  
20 -  
21 - public static void createBridge(String tunnelId, Socket externalSocket) {  
22 - bridgeMap.put(tunnelId, externalSocket);  
23 - }  
24 -  
25 - public static void bindAndStart(String tunnelId, Socket tunnelSocket, int rate) {  
26 - bandwidthMap.put(tunnelId, rate);  
27 - bindAndStart(tunnelId, tunnelSocket);  
28 - }  
29 -  
30 - public static void bindAndStart(String tunnelId, Socket tunnelSocket) {  
31 - Socket externalSocket = bridgeMap.remove(tunnelId);  
32 - int rate = bandwidthMap.remove(tunnelId);  
33 - if (externalSocket != null) {  
34 - try {  
35 - TokenBucket upload = new TokenBucket(rate, rate);  
36 - TokenBucket download = new TokenBucket(rate, rate);  
37 -  
38 - InputStream fromClient = new RateLimitedInputStream(tunnelSocket.getInputStream(), upload);  
39 - OutputStream toExternal = new RateLimitedOutputStream(externalSocket.getOutputStream(), upload);  
40 -  
41 - InputStream fromExternal = new RateLimitedInputStream(externalSocket.getInputStream(), download);  
42 - OutputStream toClient = new RateLimitedOutputStream(tunnelSocket.getOutputStream(), download);  
43 -  
44 - new Thread(() -> copy(fromClient, toExternal)).start();  
45 - new Thread(() -> copy(fromExternal, toClient)).start();  
46 - } catch (IOException e) {  
47 - e.printStackTrace();  
48 - }  
49 - }  
50 - }  
51 -  
52 - private static void copy(InputStream in, OutputStream out) {  
53 - try {  
54 - byte[] buffer = new byte[8192];  
55 - int len;  
56 - while ((len = in.read(buffer)) != -1) {  
57 - out.write(buffer, 0, len);  
58 - out.flush();  
59 - }  
60 - } catch (IOException ignored) {  
61 - }  
62 - }  
63 -}  
1 -package com.zhonglai.luhui.neutrino.proxy.server.function;  
2 -  
3 -import java.io.BufferedReader;  
4 -import java.io.InputStreamReader;  
5 -import java.net.ServerSocket;  
6 -import java.net.Socket;  
7 -  
8 -/**  
9 - * 服务端独立启动数据转发端口(如 9100)  
10 - */  
11 -public class TunnelSocketListener {  
12 - public void start() throws Exception {  
13 - ServerSocket serverSocket = new ServerSocket(9100);  
14 - System.out.println("数据转发端口监听中: 9100");  
15 - while (true) {  
16 - Socket socket = serverSocket.accept();  
17 - BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));  
18 - String tunnelId = reader.readLine();  
19 - TunnelBridgePool.bindAndStart(tunnelId, socket);  
20 - }  
21 - }  
22 -}  
1 -package com.zhonglai.luhui.neutrino.proxy.server.function.ratelimit;  
2 -  
3 -import java.io.FilterInputStream;  
4 -import java.io.IOException;  
5 -import java.io.InputStream;  
6 -  
7 -/**  
8 - * 限速 InputStream 和 OutputStream 封装类  
9 - */  
10 -public class RateLimitedInputStream extends FilterInputStream {  
11 - private final TokenBucket bucket;  
12 -  
13 - public RateLimitedInputStream(InputStream in, TokenBucket bucket) {  
14 - super(in);  
15 - this.bucket = bucket;  
16 - }  
17 -  
18 - @Override  
19 - public int read(byte[] b, int off, int len) throws IOException {  
20 - try {  
21 - bucket.consume(len);  
22 - } catch (InterruptedException e) {  
23 - Thread.currentThread().interrupt();  
24 - return -1;  
25 - }  
26 - return super.read(b, off, len);  
27 - }  
28 -}  
1 -package com.zhonglai.luhui.neutrino.proxy.server.function.ratelimit;  
2 -  
3 -import java.io.FilterOutputStream;  
4 -import java.io.IOException;  
5 -import java.io.OutputStream;  
6 -  
7 -public class RateLimitedOutputStream extends FilterOutputStream {  
8 - private final TokenBucket bucket;  
9 -  
10 - public RateLimitedOutputStream(OutputStream out, TokenBucket bucket) {  
11 - super(out);  
12 - this.bucket = bucket;  
13 - }  
14 -  
15 - @Override  
16 - public void write(byte[] b, int off, int len) throws IOException {  
17 - try {  
18 - bucket.consume(len);  
19 - } catch (InterruptedException e) {  
20 - Thread.currentThread().interrupt();  
21 - return;  
22 - }  
23 - super.write(b, off, len);  
24 - }  
25 -}  
1 -package com.zhonglai.luhui.neutrino.proxy.server.function.ratelimit;  
2 -  
3 -/**  
4 - * 核心限速器  
5 - */  
6 -public class TokenBucket {  
7 - private final long capacity;  
8 - private final long refillRate;  
9 - private long tokens;  
10 - private long lastRefillTime;  
11 -  
12 - public TokenBucket(long capacity, long refillRate) {  
13 - this.capacity = capacity;  
14 - this.refillRate = refillRate;  
15 - this.tokens = capacity;  
16 - this.lastRefillTime = System.nanoTime();  
17 - }  
18 -  
19 - public synchronized void consume(long bytes) throws InterruptedException {  
20 - refill();  
21 - while (tokens < bytes) {  
22 - Thread.sleep(5);  
23 - refill();  
24 - }  
25 - tokens -= bytes;  
26 - }  
27 -  
28 - private void refill() {  
29 - long now = System.nanoTime();  
30 - long elapsed = now - lastRefillTime;  
31 - long refillTokens = (elapsed * refillRate) / 1_000_000_000L;  
32 - if (refillTokens > 0) {  
33 - tokens = Math.min(capacity, tokens + refillTokens);  
34 - lastRefillTime = now;  
35 - }  
36 - }  
37 -}  
1 -package com.zhonglai.luhui.neutrino.proxy.server.httpservice;  
2 -  
3 -import com.alibaba.fastjson.JSON;  
4 -import com.alibaba.fastjson.JSONObject;  
5 -import com.sun.net.httpserver.HttpExchange;  
6 -import com.sun.net.httpserver.HttpHandler;  
7 -import com.zhonglai.luhui.neutrino.proxy.server.function.PortForwardManager;  
8 -import com.zhonglai.luhui.neutrino.proxy.server.httpservice.dto.ResponseMessage;  
9 -  
10 -import java.io.*;  
11 -import java.net.URLDecoder;  
12 -import java.nio.charset.StandardCharsets;  
13 -import java.util.HashMap;  
14 -import java.util.Map;  
15 -  
16 -/**  
17 - * 端口映射操作  
18 - */  
19 -public class PortForwardHandler implements HttpHandler {  
20 - @Override  
21 - public void handle(HttpExchange httpExchange) throws IOException {  
22 - try {  
23 - Map<String,String> params = getParamsMap(httpExchange);  
24 - String operate = params.get("operate");  
25 - switch (operate)  
26 - {  
27 - case "map": //端口映射  
28 - map(httpExchange,params);  
29 - return;  
30 - case "updateBandwidth": //限制带宽  
31 - updateBandwidth(httpExchange,params);  
32 - return;  
33 - default:  
34 - response(httpExchange,new ResponseMessage("不支持的操作", 0));  
35 - }  
36 - }catch (Exception e)  
37 - {  
38 - response(httpExchange,new ResponseMessage("接口请求失败", 0));  
39 - }  
40 - }  
41 -  
42 - private void map(HttpExchange httpExchange, Map<String, String> params)throws IOException  
43 - {  
44 -  
45 - int serverPort = Integer.parseInt(params.get("serverPort"));  
46 - String clientId =params.get("clientId");  
47 - int targetPort = Integer.parseInt(params.get("targetPort"));  
48 - int bandwidth = null != params.get("bandwidth") ? Integer.parseInt(params.get("bandwidth")) : 100 * 1024;  
49 - PortForwardManager.clientBandwidthMap.put(clientId, bandwidth);  
50 -  
51 - new PortForwardManager().startPortForward(serverPort, clientId, targetPort);  
52 - response(httpExchange,new ResponseMessage("映射成功", 1));  
53 - }  
54 -  
55 - private void updateBandwidth(HttpExchange httpExchange, Map<String, String> params)throws IOException  
56 - {  
57 - String clientId = params.get("clientId");  
58 - int bandwidth = Integer.parseInt(params.get("bandwidth"));  
59 - PortForwardManager.clientBandwidthMap.put(clientId, bandwidth);  
60 - response(httpExchange,new ResponseMessage("带宽更新成功", 1));  
61 - }  
62 -  
63 - private void response(HttpExchange httpExchange, ResponseMessage responseMessage) throws IOException  
64 - {  
65 - String response = JSON.toJSONString(responseMessage);  
66 - OutputStream os = httpExchange.getResponseBody();  
67 - httpExchange.getResponseHeaders().set("Content-Type", "application/json; charset=UTF-8");  
68 - byte[] bytes = response.getBytes(StandardCharsets.UTF_8);  
69 - httpExchange.sendResponseHeaders(200, bytes.length);  
70 - os.write(bytes);  
71 - os.close();  
72 - }  
73 -  
74 - private Map<String, String> getQueryParams(HttpExchange exchange) throws UnsupportedEncodingException {  
75 - String query = exchange.getRequestURI().getQuery(); // 获取 name=张三&age=18  
76 - return parseQuery(query);  
77 - }  
78 -  
79 - private Map<String, String> parseQuery(String query) throws UnsupportedEncodingException {  
80 - Map<String, String> result = new HashMap<>();  
81 - if (query == null || query.isEmpty()) return result;  
82 -  
83 - for (String param : query.split("&")) {  
84 - String[] pair = param.split("=", 2);  
85 - if (pair.length == 2) {  
86 - result.put(URLDecoder.decode(pair[0], StandardCharsets.UTF_8.toString()),URLDecoder.decode(pair[1], StandardCharsets.UTF_8.toString()));  
87 - } else if (pair.length == 1) {  
88 - result.put(URLDecoder.decode(pair[0], StandardCharsets.UTF_8.toString()), "");  
89 - }  
90 - }  
91 - return result;  
92 - }  
93 -  
94 - private Map<String, String> getParamsMap(HttpExchange httpExchange) throws IOException {  
95 - String method = httpExchange.getRequestMethod();  
96 - Map<String, String> params = null;  
97 - if ("POST".equalsIgnoreCase(method))  
98 - {  
99 - params = getPostParams(httpExchange);  
100 - } else if ("GET".equalsIgnoreCase(method))  
101 - {  
102 - params = getQueryParams(httpExchange);  
103 - }  
104 - return params;  
105 - }  
106 -  
107 - private Map<String, String> getPostParams(HttpExchange exchange) throws IOException {  
108 - String body = readRequestBody(exchange);  
109 - return parseQuery(body); // 和 GET 参数一样解析  
110 - }  
111 -  
112 - private String readRequestBody(HttpExchange exchange) throws IOException {  
113 - BufferedReader reader = new BufferedReader(  
114 - new InputStreamReader(exchange.getRequestBody(), StandardCharsets.UTF_8));  
115 -  
116 - StringBuilder sb = new StringBuilder();  
117 - String line;  
118 - while ((line = reader.readLine()) != null) {  
119 - sb.append(line);  
120 - }  
121 - return sb.toString();  
122 - }  
123 -  
124 -}  
1 -package com.zhonglai.luhui.neutrino.proxy.server.httpservice;  
2 -  
3 -import com.sun.net.httpserver.HttpServer;  
4 -  
5 -import java.io.IOException;  
6 -import java.net.InetSocketAddress;  
7 -  
8 -public class SimpleHttpServer {  
9 - public static void startHttpServer(int port) throws IOException  
10 - {  
11 - HttpServer server = HttpServer.create(new InetSocketAddress(port), 0);  
12 -  
13 - // 映射 /static/ 到 static/ 目录  
14 - server.createContext("/static/", new StaticFileHandler("static"));  
15 -  
16 - server.createContext("/portForward", new PortForwardHandler());  
17 -  
18 - server.setExecutor(null); // 默认线程池  
19 - server.start();  
20 - System.out.println("Server started at http://localhost:" + port);  
21 - }  
22 -}  
1 -package com.zhonglai.luhui.neutrino.proxy.server.httpservice;  
2 -  
3 -import com.sun.net.httpserver.HttpExchange;  
4 -import com.sun.net.httpserver.HttpHandler;  
5 -  
6 -import java.io.IOException;  
7 -import java.io.OutputStream;  
8 -import java.nio.file.Files;  
9 -import java.nio.file.Path;  
10 -import java.nio.file.Paths;  
11 -  
12 -/**  
13 - * 静态文件处理器  
14 - */  
15 -public class StaticFileHandler implements HttpHandler {  
16 - private final Path basePath;  
17 -  
18 - public StaticFileHandler(String rootDir) {  
19 - this.basePath = Paths.get(rootDir).toAbsolutePath();  
20 - }  
21 -  
22 - @Override  
23 - public void handle(HttpExchange exchange) throws IOException {  
24 - String uriPath = exchange.getRequestURI().getPath();  
25 - String other = uriPath.replaceFirst("^/[^/]+/", "");  
26 - Path filePath = basePath.resolve(other).normalize();  
27 - //生成  
28 - if (!filePath.startsWith(basePath) || !Files.exists(filePath)) {  
29 - exchange.sendResponseHeaders(404, -1);  
30 - return;  
31 - }  
32 -  
33 - String contentType = guessContentType(filePath);  
34 - byte[] fileBytes = Files.readAllBytes(filePath);  
35 - exchange.getResponseHeaders().add("Content-Type", contentType);  
36 - exchange.sendResponseHeaders(200, fileBytes.length);  
37 - try (OutputStream os = exchange.getResponseBody()) {  
38 - os.write(fileBytes);  
39 - }  
40 - }  
41 -  
42 - private String guessContentType(Path path) {  
43 - String name = path.getFileName().toString().toLowerCase();  
44 - if (name.endsWith(".html")) return "text/html";  
45 - if (name.endsWith(".js")) return "application/javascript";  
46 - if (name.endsWith(".css")) return "text/css";  
47 - if (name.endsWith(".m3u8")) return "application/vnd.apple.mpegurl";  
48 - if (name.endsWith(".ts")) return "video/MP2T";  
49 - return "application/octet-stream";  
50 - }  
51 -}  
1 -package com.zhonglai.luhui.neutrino.proxy.server.httpservice.dto;  
2 -  
3 -public class MappingPort {  
4 - private Integer clientPort; //客服端端口  
5 - private Integer servicePort; //服务器端口  
6 -  
7 - public Integer getClientPort() {  
8 - return clientPort;  
9 - }  
10 -  
11 - public void setClientPort(Integer clientPort) {  
12 - this.clientPort = clientPort;  
13 - }  
14 -  
15 - public Integer getServicePort() {  
16 - return servicePort;  
17 - }  
18 -  
19 - public void setServicePort(Integer servicePort) {  
20 - this.servicePort = servicePort;  
21 - }  
22 -}  
1 -package com.zhonglai.luhui.neutrino.proxy.server.httpservice.dto;  
2 -  
3 -import java.util.List;  
4 -import java.util.Map;  
5 -  
6 -/**  
7 - * 端口映射内容  
8 - */  
9 -public class PortForwardMapBody {  
10 - private String clientId;  
11 - private List<MappingPort> mappingPortList;  
12 -  
13 - public String getClientId() {  
14 - return clientId;  
15 - }  
16 -  
17 - public void setClientId(String clientId) {  
18 - this.clientId = clientId;  
19 - }  
20 -  
21 - public List<MappingPort> getMappingPortList() {  
22 - return mappingPortList;  
23 - }  
24 -  
25 - public void setMappingPortList(List<MappingPort> mappingPortList) {  
26 - this.mappingPortList = mappingPortList;  
27 - }  
28 -}  
1 -package com.zhonglai.luhui.neutrino.proxy.server.httpservice.dto;  
2 -  
3 -public class ResponseMessage {  
4 - private String msg;  
5 - private Integer code;  
6 - private Object data;  
7 -  
8 - public ResponseMessage(String msg, Integer code) {  
9 - this.msg = msg;  
10 - this.code = code;  
11 - }  
12 -  
13 - public ResponseMessage(String msg, Integer code, Object data) {  
14 - this.msg = msg;  
15 - this.code = code;  
16 - this.data = data;  
17 - }  
18 -  
19 - public String getMsg() {  
20 - return msg;  
21 - }  
22 -  
23 - public void setMsg(String msg) {  
24 - this.msg = msg;  
25 - }  
26 -  
27 - public Integer getCode() {  
28 - return code;  
29 - }  
30 -  
31 - public void setCode(Integer code) {  
32 - this.code = code;  
33 - }  
34 -  
35 - public Object getData() {  
36 - return data;  
37 - }  
38 -  
39 - public void setData(Object data) {  
40 - this.data = data;  
41 - }  
42 -}  
  1 +package com.zhonglai.luhui.neutrino.proxy.server.operate;
  2 +
  3 +import com.alibaba.fastjson.JSONObject;
  4 +import com.zhonglai.luhui.neutrino.proxy.common.mqtt.OperateService;
  5 +import com.zhonglai.luhui.neutrino.proxy.common.mqtt.Topic;
  6 +
  7 +public class OperateServiceImpl implements OperateService {
  8 + @Override
  9 + public void operate(Topic topic, JSONObject data) {
  10 +
  11 + }
  12 +}
1 -package com.zhonglai.luhui.neutrino.proxy.server.proxy;  
2 -  
3 -import com.zhonglai.luhui.neutrino.proxy.server.proxy.handler.ControlHandler;  
4 -import com.zhonglai.luhui.neutrino.proxy.server.proxy.handler.HeartbeatTimeoutHandler;  
5 -import io.netty.bootstrap.ServerBootstrap;  
6 -import io.netty.channel.ChannelFuture;  
7 -import io.netty.channel.ChannelFutureListener;  
8 -import io.netty.channel.ChannelInitializer;  
9 -import io.netty.channel.nio.NioEventLoopGroup;  
10 -import io.netty.channel.socket.SocketChannel;  
11 -import io.netty.channel.socket.nio.NioServerSocketChannel;  
12 -import io.netty.handler.codec.LineBasedFrameDecoder;  
13 -import io.netty.handler.codec.string.StringDecoder;  
14 -import io.netty.handler.codec.string.StringEncoder;  
15 -import io.netty.handler.timeout.IdleStateHandler;  
16 -  
17 -/**  
18 - * 代理服务  
19 - */  
20 -public class ProxyServer {  
21 - public static void start(int proxyPort) throws Exception  
22 - {  
23 - NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);  
24 - NioEventLoopGroup workerGroup = new NioEventLoopGroup();  
25 -  
26 - ServerBootstrap bootstrap = new ServerBootstrap()  
27 - .group(bossGroup, workerGroup)  
28 - .channel(NioServerSocketChannel.class)  
29 - .childHandler(new ChannelInitializer<SocketChannel>() {  
30 - @Override  
31 - protected void initChannel(SocketChannel ch) {  
32 - ch.pipeline().addLast(new LineBasedFrameDecoder(1024));  
33 - ch.pipeline().addLast(new IdleStateHandler(60, 0, 0));  
34 - ch.pipeline().addLast(new HeartbeatTimeoutHandler());  
35 - ch.pipeline().addLast(new StringDecoder());  
36 - ch.pipeline().addLast(new StringEncoder());  
37 - ch.pipeline().addLast(new ControlHandler());  
38 - }  
39 - });  
40 -  
41 - ChannelFuture future = bootstrap.bind(proxyPort).sync(); // bind 完成阻塞等待  
42 -  
43 - future.channel().closeFuture().addListener((ChannelFutureListener) closeFuture -> {  
44 - System.out.println("Server channel closed.");  
45 - });  
46 - System.out.println("Netty server started on port: " + proxyPort);  
47 -  
48 - // ✅ 注册优雅关闭钩子,不阻塞主线程  
49 - Runtime.getRuntime().addShutdownHook(new Thread(() -> {  
50 - System.out.println("Shutdown signal received. Closing Netty gracefully...");  
51 - bossGroup.shutdownGracefully();  
52 - workerGroup.shutdownGracefully();  
53 - }));  
54 -  
55 -  
56 - }  
57 -}  
1 -package com.zhonglai.luhui.neutrino.proxy.server.proxy.handler;  
2 -  
3 -import com.fasterxml.jackson.databind.JsonNode;  
4 -import com.fasterxml.jackson.databind.ObjectMapper;  
5 -import com.zhonglai.luhui.neutrino.proxy.common.RegisterMessage;  
6 -import com.zhonglai.luhui.neutrino.proxy.server.ClientSessionManager;  
7 -import com.zhonglai.luhui.neutrino.proxy.server.function.PortForwardManager;  
8 -import io.netty.channel.ChannelHandlerContext;  
9 -import io.netty.channel.SimpleChannelInboundHandler;  
10 -import org.apache.commons.io.IOUtils;  
11 -  
12 -import java.io.IOException;  
13 -import java.io.PrintWriter;  
14 -import java.net.Socket;  
15 -  
16 -public class ControlHandler extends SimpleChannelInboundHandler<String> {  
17 - private static final ObjectMapper mapper = new ObjectMapper();  
18 - private String clientId;  
19 -  
20 - @Override  
21 - protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {  
22 - JsonNode json = mapper.readTree(msg);  
23 - String type = json.get("type").asText();  
24 -  
25 - switch (type) {  
26 - case "register":  
27 - clientId = json.get("clientId").asText();  
28 - ClientSessionManager.register(clientId, ctx.channel());  
29 - ctx.writeAndFlush("{\"type\":\"register_ack\",\"status\":\"ok\"}\n");  
30 - break;  
31 -  
32 - case "heartbeat":  
33 - ClientSessionManager.heartbeat(json.get("clientId").asText());  
34 - break;  
35 - default:  
36 - System.out.println("未知消息类型: " + type);  
37 - }  
38 - }  
39 -  
40 - @Override  
41 - public void channelInactive(ChannelHandlerContext ctx) throws Exception {  
42 - ClientSessionManager.remove(ctx.channel());  
43 - System.out.println("客户端断开连接:" + clientId);  
44 - }  
45 -  
46 -}  
1 -package com.zhonglai.luhui.neutrino.proxy.server.proxy.handler;  
2 -  
3 -import io.netty.channel.ChannelHandlerContext;  
4 -import io.netty.channel.ChannelInboundHandlerAdapter;  
5 -import io.netty.handler.timeout.IdleState;  
6 -import io.netty.handler.timeout.IdleStateEvent;  
7 -  
8 -public class HeartbeatTimeoutHandler extends ChannelInboundHandlerAdapter {  
9 - @Override  
10 - public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {  
11 - if (evt instanceof IdleStateEvent && ((IdleStateEvent)evt).state() == IdleState.READER_IDLE) {  
12 - System.out.println("心跳超时,断开客户端:" + ctx.channel().remoteAddress());  
13 - ctx.close();  
14 - } else {  
15 - super.userEventTriggered(ctx, evt);  
16 - }  
17 - }  
18 -}  
  1 +broker=
  2 +username=
  3 +password=
  4 +clientId=
  5 +subTopic=
@@ -615,6 +615,11 @@ @@ -615,6 +615,11 @@
615 <version>1.18</version> 615 <version>1.18</version>
616 </dependency> 616 </dependency>
617 617
  618 + <!--solon aot start(用于 aot 时注册 native 元信息)-->
  619 + <dependency>
  620 + <groupId>org.noear</groupId>
  621 + <artifactId>solon.aot</artifactId>
  622 + </dependency>
618 </dependencies> 623 </dependencies>
619 624
620 625