正在显示
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 | +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 | -} |
| @@ -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 |
-
请 注册 或 登录 后发表评论