├── META-INF └── native-image │ └── .lock ├── _doc ├── todo.txt ├── img │ ├── 1.jpg │ ├── 2.jpg │ ├── 3.jpg │ ├── 4.jpg │ ├── hppt.png │ ├── hpptdemo.jpg │ └── kafkademo.jpg ├── readme.pptx ├── ProtoMessage.proto └── demo │ ├── hppt.md │ └── websocket.md ├── run ├── src │ ├── main │ │ ├── resources │ │ │ ├── META-INF │ │ │ │ └── native-image │ │ │ │ │ ├── predefined-classes-config.json │ │ │ │ │ ├── serialization-config.json │ │ │ │ │ ├── jni-config.json │ │ │ │ │ └── resource-config.json │ │ │ ├── debug.ini │ │ │ └── logback.xml │ │ └── java │ │ │ └── org │ │ │ └── wowtools │ │ │ └── hppt │ │ │ ├── run │ │ │ ├── ss │ │ │ │ ├── file │ │ │ │ │ ├── FileCtx.java │ │ │ │ │ └── FileServerSessionService.java │ │ │ │ ├── rpost │ │ │ │ │ ├── RPostCtx.java │ │ │ │ │ └── RPostServerSessionService.java │ │ │ │ ├── post │ │ │ │ │ ├── PostCtx.java │ │ │ │ │ └── PostServerSessionService.java │ │ │ │ ├── common │ │ │ │ │ └── Receiver.java │ │ │ │ ├── util │ │ │ │ │ └── SsUtil.java │ │ │ │ ├── RunSs.java │ │ │ │ ├── rhppt │ │ │ │ │ └── RHpptServerSessionService.java │ │ │ │ ├── hppt │ │ │ │ │ └── HpptServerSessionService.java │ │ │ │ ├── websocket │ │ │ │ │ └── WebsocketServerSessionService.java │ │ │ │ └── pojo │ │ │ │ │ └── SsConfig.java │ │ │ ├── sc │ │ │ │ ├── common │ │ │ │ │ ├── Receiver.java │ │ │ │ │ └── SsReceiver.java │ │ │ │ ├── RunSc.java │ │ │ │ ├── util │ │ │ │ │ └── ScUtil.java │ │ │ │ ├── ClientSessionServiceBuilder.java │ │ │ │ ├── file │ │ │ │ │ └── FileClientSessionService.java │ │ │ │ ├── hppt │ │ │ │ │ └── HpptClientSessionService.java │ │ │ │ └── rhppt │ │ │ │ │ └── RHpptClientSessionService.java │ │ │ └── Run.java │ │ │ └── common │ │ │ ├── util │ │ │ ├── CommonConfig.java │ │ │ ├── RoughTimeUtil.java │ │ │ ├── GridAesCipherUtil.java │ │ │ ├── FileProducerBuffer.java │ │ │ ├── AddonsLoader.java │ │ │ ├── ResourcesReader.java │ │ │ ├── FileConsumerBuffer.java │ │ │ ├── Constant.java │ │ │ ├── NettyObjectBuilder.java │ │ │ ├── DebugConfig.java │ │ │ ├── AesCipherUtil.java │ │ │ ├── BufferPool.java │ │ │ ├── JsonConfig.java │ │ │ ├── DirChangeWatcher.java │ │ │ └── HttpUtil.java │ │ │ ├── pojo │ │ │ ├── SendAbleSessionBytes.java │ │ │ ├── BytesList.java │ │ │ ├── SessionBytes.java │ │ │ └── TalkMessage.java │ │ │ ├── server │ │ │ ├── ServerSessionManagerBuilder.java │ │ │ ├── ServerSessionLifecycle.java │ │ │ └── ServerSession.java │ │ │ └── client │ │ │ ├── ClientBytesSender.java │ │ │ ├── ClientSessionLifecycle.java │ │ │ ├── ClientSessionManagerBuilder.java │ │ │ └── ClientSession.java │ └── test │ │ ├── java │ │ ├── org │ │ │ └── wowtools │ │ │ │ └── hppt │ │ │ │ ├── common │ │ │ │ ├── util │ │ │ │ │ ├── BytesUtilTest.java │ │ │ │ │ ├── GridAesCipherUtilTest.java │ │ │ │ │ └── AesCipherUtilTest.java │ │ │ │ ├── client │ │ │ │ │ └── ClientSessionManagerTest.java │ │ │ │ └── server │ │ │ │ │ └── ServerSessionManagerTest.java │ │ │ │ └── SsrDemo.java │ │ ├── t1 │ │ │ ├── BytesCallBack.java │ │ │ ├── SessionBytes.java │ │ │ ├── ServerPort.java │ │ │ ├── ClientSession.java │ │ │ ├── ClientSessionManager.java │ │ │ └── ServerSessionManager.java │ │ ├── test │ │ │ ├── ReadYmlConfig.java │ │ │ ├── ByteArrayCompressionExample.java │ │ │ ├── LZ4CompressionExample.java │ │ │ ├── SimpleNettyServer.java │ │ │ ├── TCPForwardingServer.java │ │ │ ├── SymmetricEncryptionExample.java │ │ │ └── SimpleNettyClient.java │ │ └── nettytest │ │ │ ├── NettyServer.java │ │ │ └── NettyClient.java │ │ └── resources │ │ └── demo │ │ ├── ssrpost │ │ ├── ss.yml │ │ ├── ssr.yml │ │ └── sc.yml │ │ ├── websocket │ │ ├── ss.yml │ │ └── sc.yml │ │ ├── kafka │ │ ├── ss.yml │ │ └── sc.yml │ │ ├── hppt │ │ ├── ss.yml │ │ └── sc.yml │ │ ├── post │ │ ├── ss.yml │ │ └── sc.yml │ │ ├── rhppt │ │ ├── ss.yml │ │ └── sc.yml │ │ └── rpost │ │ ├── ss.yml │ │ └── sc.yml └── pom.xml ├── kafkademo ├── src │ └── main │ │ ├── java │ │ └── org │ │ │ └── wowtools │ │ │ └── hppt │ │ │ └── kafkademo │ │ │ ├── KafkaCtx.java │ │ │ ├── Config.java │ │ │ ├── ClientDemo.java │ │ │ ├── ServerDemo.java │ │ │ └── KafkaUtil.java │ │ └── resources │ │ ├── debug.ini │ │ └── logback.xml └── pom.xml ├── addons-kafka ├── src │ └── main │ │ ├── java │ │ └── org │ │ │ └── wowtools │ │ │ └── hppt │ │ │ └── addons │ │ │ └── kafka │ │ │ ├── KafkaCtx.java │ │ │ ├── Config.java │ │ │ ├── KafkaClientSessionService.java │ │ │ └── KafkaServerSessionService.java │ │ └── resources │ │ └── config-kafka.yml └── pom.xml ├── .gitignore ├── usbdemo ├── pom.xml └── src │ └── main │ └── java │ └── org │ └── wowtools │ └── hppt │ └── usb │ ├── UsbExample.java │ └── UsbHidCommunication.java ├── _localtest └── pom.xml └── pom.xml /META-INF/native-image/.lock: -------------------------------------------------------------------------------- 1 | 22916 -------------------------------------------------------------------------------- /_doc/todo.txt: -------------------------------------------------------------------------------- 1 | post 下载文件会卡住 2 | ws 速度较慢 3 | -------------------------------------------------------------------------------- /_doc/img/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingmiao/hppt/HEAD/_doc/img/1.jpg -------------------------------------------------------------------------------- /_doc/img/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingmiao/hppt/HEAD/_doc/img/2.jpg -------------------------------------------------------------------------------- /_doc/img/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingmiao/hppt/HEAD/_doc/img/3.jpg -------------------------------------------------------------------------------- /_doc/img/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingmiao/hppt/HEAD/_doc/img/4.jpg -------------------------------------------------------------------------------- /_doc/readme.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingmiao/hppt/HEAD/_doc/readme.pptx -------------------------------------------------------------------------------- /_doc/img/hppt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingmiao/hppt/HEAD/_doc/img/hppt.png -------------------------------------------------------------------------------- /_doc/img/hpptdemo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingmiao/hppt/HEAD/_doc/img/hpptdemo.jpg -------------------------------------------------------------------------------- /_doc/img/kafkademo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codingmiao/hppt/HEAD/_doc/img/kafkademo.jpg -------------------------------------------------------------------------------- /run/src/main/resources/META-INF/native-image/predefined-classes-config.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "type" : "agent-extracted", 3 | "classes" : [ ] 4 | } ] -------------------------------------------------------------------------------- /run/src/main/resources/META-INF/native-image/serialization-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "lambdaCapturingTypes" : [ ], 3 | "proxies" : [ ], 4 | "types" : [ ] 5 | } -------------------------------------------------------------------------------- /run/src/test/java/org/wowtools/hppt/common/util/BytesUtilTest.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.common.util; 2 | 3 | 4 | public class BytesUtilTest { 5 | 6 | 7 | } 8 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/run/ss/file/FileCtx.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.run.ss.file; 2 | 3 | /** 4 | * @author liuyu 5 | * @date 2024/7/5 6 | */ 7 | public class FileCtx { 8 | } 9 | -------------------------------------------------------------------------------- /kafkademo/src/main/java/org/wowtools/hppt/kafkademo/KafkaCtx.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.kafkademo; 2 | 3 | /** 4 | * @author liuyu 5 | * @date 2024/6/15 6 | */ 7 | public class KafkaCtx { 8 | } 9 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/run/ss/rpost/RPostCtx.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.run.ss.rpost; 2 | 3 | /** 4 | * @author liuyu 5 | * @date 2024/4/16 6 | */ 7 | public class RPostCtx { 8 | } 9 | -------------------------------------------------------------------------------- /addons-kafka/src/main/java/org/wowtools/hppt/addons/kafka/KafkaCtx.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.addons.kafka; 2 | 3 | /** 4 | * @author liuyu 5 | * @date 2024/6/15 6 | */ 7 | public class KafkaCtx { 8 | } 9 | -------------------------------------------------------------------------------- /run/src/test/resources/demo/ssrpost/ss.yml: -------------------------------------------------------------------------------- 1 | 2 | type: hppt 3 | #服务http端口 4 | port: 20872 5 | # 允许的客户端账号和密码 6 | clients: 7 | - user: user1 8 | password: 12345 9 | - user: user2 10 | password: 112233 11 | -------------------------------------------------------------------------------- /run/src/test/resources/demo/websocket/ss.yml: -------------------------------------------------------------------------------- 1 | type: websocket 2 | #服务端口 3 | port: 20871 4 | # 允许的客户端账号和密码 5 | clients: 6 | - user: user1 7 | password: 12345 8 | - user: user2 9 | password: 112233 10 | -------------------------------------------------------------------------------- /run/src/test/java/t1/BytesCallBack.java: -------------------------------------------------------------------------------- 1 | package t1; 2 | 3 | /** 4 | * @author liuyu 5 | * @date 2023/11/16 6 | */ 7 | @FunctionalInterface 8 | public interface BytesCallBack { 9 | void cb(byte[] bytes); 10 | } 11 | -------------------------------------------------------------------------------- /run/src/test/resources/demo/ssrpost/ssr.yml: -------------------------------------------------------------------------------- 1 | type: post 2 | #服务http端口 3 | port: 20871 4 | 5 | relayScConfig: 6 | 7 | type: hppt 8 | hppt: 9 | #服务端地址 10 | host: "localhost" 11 | #服务端端口 12 | port: 20872 13 | -------------------------------------------------------------------------------- /run/src/test/resources/demo/kafka/ss.yml: -------------------------------------------------------------------------------- 1 | # 通讯协议 客户端与服务端保持一致 2 | type: 'org.wowtools.hppt.addons.kafka.KafkaServerSessionService' 3 | 4 | # 允许的客户端账号和密码 5 | clients: 6 | - user: user1 7 | password: 12345 8 | - user: user2 9 | password: 112233 10 | -------------------------------------------------------------------------------- /run/src/test/resources/demo/hppt/ss.yml: -------------------------------------------------------------------------------- 1 | type: hppt 2 | #服务端口 3 | port: 20871 4 | # 允许的客户端账号和密码 5 | clients: 6 | - user: user1 7 | password: 12345 8 | - user: user2 9 | password: 112233 10 | 11 | # 心跳包检查周期,多少毫秒没有客户端发来心跳包则重启服务 12 | heartbeatTimeout: 3600000 13 | -------------------------------------------------------------------------------- /run/src/test/resources/demo/post/ss.yml: -------------------------------------------------------------------------------- 1 | #使用http post协议传输数据,此协议最为简单,但性能略差,如有需要请查看websocket协议或hppt协议或自定义协议 2 | type: post 3 | #服务http端口 4 | port: 20871 5 | # 允许的客户端账号和密码 6 | clients: 7 | - user: user1 8 | password: 12345 9 | - user: user2 10 | password: 112233 11 | -------------------------------------------------------------------------------- /run/src/test/resources/demo/rhppt/ss.yml: -------------------------------------------------------------------------------- 1 | # 通讯协议 客户端与服务端保持一致 2 | type: rhppt 3 | 4 | # 指向上一步启动的服务的ip和端口 5 | rhppt: 6 | host: "localhost" 7 | port: 20871 8 | 9 | # 允许的客户端账号和密码 10 | clients: 11 | - user: user1 12 | password: 12345 13 | - user: user2 14 | password: 112233 15 | -------------------------------------------------------------------------------- /run/src/test/resources/demo/rpost/ss.yml: -------------------------------------------------------------------------------- 1 | #使用http post协议传输数据,此协议最为简单,但性能略差,如有需要请查看websocket协议或hppt协议或自定义协议 2 | type: rpost 3 | 4 | rpost: 5 | # 服务端http地址,可以填nginx转发过的地址 6 | serverUrl: "http://localhost:20871" 7 | 8 | # 允许的客户端 9 | clients: 10 | - user: user1 11 | password: 12345 12 | 13 | -------------------------------------------------------------------------------- /run/src/test/java/org/wowtools/hppt/SsrDemo.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt; 2 | 3 | import org.wowtools.hppt.run.ss.RunSs; 4 | 5 | /** 6 | * @author liuyu 7 | * @date 2024/10/9 8 | */ 9 | public class SsrDemo { 10 | public static void main(String[] args) throws Exception { 11 | RunSs.main(new String[]{"ss", "ssr.yml"}); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /run/src/test/resources/demo/kafka/sc.yml: -------------------------------------------------------------------------------- 1 | type: 'org.wowtools.hppt.addons.kafka.KafkaClientSessionService' 2 | 3 | localHost: 127.0.0.1 4 | # 客户端用户名,每个sc进程用一个,不要重复 5 | clientUser: user1 6 | # 客户端密码 7 | clientPassword: 12345 8 | 9 | #是否启用内容加密,默认启用 需和服务端保持一致 10 | enableEncrypt: true 11 | 12 | forwards: 13 | - localPort: 10022 14 | remoteHost: "wsl" 15 | remotePort: 22 16 | -------------------------------------------------------------------------------- /run/src/main/resources/debug.ini: -------------------------------------------------------------------------------- 1 | #调试配置文件,请保证服务端和客户端配置一致,否则可能引起异常 2 | 3 | 4 | # netty内存泄露检查级别 0 DISABLED 1 SIMPLE 2 ADVANCED 3 PARANOID 5 | NettyResourceLeakDetectorLevel = 3 6 | # 是否开启消息流水号 1为开启 用于调试消息后发先至等问题,非调试时流水号为空 7 | OpenSerialNumber = 0 8 | 9 | # 是否开启缓冲池监控 1为开启 用于调试各个生产者、消费者的缓冲池使用情况 10 | OpenBufferPoolDetector = 0 11 | # 缓冲池高水位线,缓冲池中元素个数超过此值且继续向其中添加要素则触发日志 12 | BufferPoolWaterline = 20 13 | -------------------------------------------------------------------------------- /kafkademo/src/main/resources/debug.ini: -------------------------------------------------------------------------------- 1 | #调试配置文件,请保证服务端和客户端配置一致,否则可能引起异常 2 | 3 | 4 | # netty内存泄露检查级别 0 DISABLED 1 SIMPLE 2 ADVANCED 3 PARANOID 5 | NettyResourceLeakDetectorLevel = 3 6 | # 是否开启消息流水号 1为开启 用于调试消息后发先至等问题,非调试时流水号为空 7 | OpenSerialNumber = 0 8 | 9 | # 是否开启缓冲池监控 1为开启 用于调试各个生产者、消费者的缓冲池使用情况 10 | OpenBufferPoolDetector = 0 11 | # 缓冲池高水位线,缓冲池中元素个数超过此值且继续向其中添加要素则触发日志 12 | BufferPoolWaterline = 20 13 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/common/util/CommonConfig.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.common.util; 2 | 3 | /** 4 | * @author liuyu 5 | * @date 2024/1/25 6 | */ 7 | public class CommonConfig { 8 | // /** 9 | // * 是否启用压缩,默认启用 10 | // */ 11 | // public boolean enableCompress = true; 12 | 13 | /** 14 | * 是否启用内容加密,默认启用 15 | */ 16 | public boolean enableEncrypt = true; 17 | } 18 | -------------------------------------------------------------------------------- /kafkademo/src/main/java/org/wowtools/hppt/kafkademo/Config.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.kafkademo; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * @author liuyu 7 | * @date 2025/7/7 8 | */ 9 | public class Config { 10 | //客户端发数据的topic 11 | public String clientSendTopic = "client-send-01"; 12 | //服务端发数据的topic 13 | public String serverSendTopic = "server-send-01"; 14 | 15 | public Map properties; 16 | } 17 | -------------------------------------------------------------------------------- /addons-kafka/src/main/java/org/wowtools/hppt/addons/kafka/Config.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.addons.kafka; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * @author liuyu 7 | * @date 2025/7/7 8 | */ 9 | public class Config { 10 | //客户端发数据的topic 11 | public String clientSendTopic = "client-send-01"; 12 | //服务端发数据的topic 13 | public String serverSendTopic = "server-send-01"; 14 | 15 | public String tag; 16 | 17 | public Map properties; 18 | } 19 | -------------------------------------------------------------------------------- /run/src/test/resources/demo/rhppt/sc.yml: -------------------------------------------------------------------------------- 1 | # 通讯协议 客户端与服务端保持一致 2 | type: rhppt 3 | # 客户端用户名,每个sc进程用一个,不要重复 4 | clientUser: user1 5 | # 客户端密码 6 | clientPassword: 12345 7 | 8 | # 服务端口 9 | rhppt: 10 | port: 20871 11 | 12 | forwards: 13 | # 把192.168.0.2的22端口代理到本机的10022端口 14 | - localPort: 10022 15 | remoteHost: "wsl" 16 | remotePort: 22 17 | # 同理也可以代理数据库等任意TCP端口,只要服务端的hppt所在服务器能访问到的端口都行 18 | - localPort: 10023 19 | remoteHost: "192.168.0.3" 20 | remotePort: 3306 21 | 22 | -------------------------------------------------------------------------------- /run/src/test/resources/demo/rpost/sc.yml: -------------------------------------------------------------------------------- 1 | # 和服务端的type保持一致 2 | type: rpost 3 | # 客户端用户名,每个sc进程用一个,不要重复 4 | clientUser: user1 5 | # 客户端密码 6 | clientPassword: 12345 7 | 8 | 9 | rpost: 10 | #服务端口 11 | port: 20871 12 | 13 | forwards: 14 | # 把192.168.0.2的22端口代理到本机的10022端口 15 | - localPort: 10022 16 | remoteHost: "wsl" 17 | remotePort: 22 18 | # 同理也可以代理数据库等任意TCP端口,只要服务端的hppt所在服务器能访问到的端口都行 19 | - localPort: 10023 20 | remoteHost: "192.168.0.3" 21 | remotePort: 3306 22 | 23 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/common/pojo/SendAbleSessionBytes.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.common.pojo; 2 | 3 | /** 4 | * 可发送的SessionBytes,被发送后会触发回调函数并告知是否成功 5 | * 6 | * @author liuyu 7 | * @date 2024/10/20 8 | */ 9 | public record SendAbleSessionBytes(SessionBytes sessionBytes, 10 | org.wowtools.hppt.common.pojo.SendAbleSessionBytes.CallBack callBack) { 11 | public interface CallBack { 12 | void cb(boolean success); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/run/sc/common/Receiver.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.run.sc.common; 2 | 3 | 4 | import org.wowtools.hppt.common.client.ClientSession; 5 | 6 | /** 7 | * @author liuyu 8 | * @date 2024/9/27 9 | */ 10 | public sealed interface Receiver permits PortReceiver, SsReceiver { 11 | void receiveServerBytes(byte[] bytes) throws Exception; 12 | 13 | void closeClientSession(ClientSession clientSession); 14 | 15 | void exit(); 16 | 17 | boolean notUsed(); 18 | } 19 | -------------------------------------------------------------------------------- /addons-kafka/src/main/resources/config-kafka.yml: -------------------------------------------------------------------------------- 1 | clientSendTopic: "client-send-02" 2 | serverSendTopic: "server-send-02" 3 | tag: tt 4 | properties: 5 | #kafka配置 6 | "key.serializer": "org.apache.kafka.common.serialization.StringSerializer" 7 | "value.serializer": "org.apache.kafka.common.serialization.ByteArraySerializer" 8 | "key.deserializer": "org.apache.kafka.common.serialization.StringDeserializer" 9 | "value.deserializer": "org.apache.kafka.common.serialization.ByteArrayDeserializer" 10 | "bootstrap.servers": "wsl:9092" 11 | -------------------------------------------------------------------------------- /run/src/test/java/t1/SessionBytes.java: -------------------------------------------------------------------------------- 1 | package t1; 2 | 3 | /** 4 | * @author liuyu 5 | * @date 2023/11/17 6 | */ 7 | public class SessionBytes { 8 | private final int sessionId; 9 | private final byte[] bytes; 10 | 11 | public int getSessionId() { 12 | return sessionId; 13 | } 14 | 15 | public byte[] getBytes() { 16 | return bytes; 17 | } 18 | 19 | public SessionBytes(int sessionId, byte[] bytes) { 20 | this.sessionId = sessionId; 21 | this.bytes = bytes; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /run/src/test/resources/demo/websocket/sc.yml: -------------------------------------------------------------------------------- 1 | # 和服务端的type保持一致 2 | type: websocket 3 | # 客户端用户名,每个sc进程用一个,不要重复 4 | clientUser: user1 5 | # 客户端密码 6 | clientPassword: 12345 7 | 8 | 9 | websocket: 10 | #服务端http地址,可以填nginx转发过的地址 11 | serverUrl: "ws://localhost:20871" 12 | 13 | forwards: 14 | # 把192.168.0.2的22端口代理到本机的10022端口 15 | - localPort: 10022 16 | remoteHost: "wsl" 17 | remotePort: 22 18 | # 同理也可以代理数据库等任意TCP端口,只要服务端的hppt所在服务器能访问到的端口都行 19 | - localPort: 10023 20 | remoteHost: "192.168.0.3" 21 | remotePort: 3306 22 | 23 | -------------------------------------------------------------------------------- /run/src/test/java/t1/ServerPort.java: -------------------------------------------------------------------------------- 1 | package t1; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | /** 6 | * @author liuyu 7 | * @date 2023/11/17 8 | */ 9 | @Slf4j 10 | public class ServerPort { 11 | private final String host; 12 | private final int port; 13 | 14 | public ServerPort(String host, int port) { 15 | this.host = host; 16 | this.port = port; 17 | } 18 | 19 | 20 | public String getHost() { 21 | return host; 22 | } 23 | 24 | public int getPort() { 25 | return port; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/run/ss/post/PostCtx.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.run.ss.post; 2 | 3 | import org.wowtools.hppt.common.util.BufferPool; 4 | 5 | import java.util.concurrent.BlockingQueue; 6 | import java.util.concurrent.LinkedBlockingQueue; 7 | 8 | /** 9 | * @author liuyu 10 | * @date 2024/3/20 11 | */ 12 | public class PostCtx { 13 | final String cookie; 14 | final BufferPool sendQueue = new BufferPool<>(">PostCtx-sendQueue"); 15 | 16 | public PostCtx(String cookie) { 17 | this.cookie = cookie; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /run/src/test/resources/demo/hppt/sc.yml: -------------------------------------------------------------------------------- 1 | # 和服务端的type保持一致 2 | type: hppt 3 | # 客户端用户名,每个sc进程用一个,不要重复 4 | clientUser: user1 5 | # 客户端密码 6 | clientPassword: 12345 7 | 8 | 9 | hppt: 10 | #服务端地址 11 | host: "localhost" 12 | #服务端端口 13 | port: 20871 14 | 15 | forwards: 16 | # 把192.168.0.2的22端口代理到本机的10022端口 17 | - localPort: 10022 18 | remoteHost: "wsl" 19 | remotePort: 22 20 | # 同理也可以代理数据库等任意TCP端口,只要服务端的hppt所在服务器能访问到的端口都行 21 | - localPort: 10023 22 | remoteHost: "192.168.0.3" 23 | remotePort: 3306 24 | 25 | # 心跳包发送周期 26 | heartbeatPeriod: 30000 27 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/run/ss/common/Receiver.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.run.ss.common; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | /** 7 | * @author liuyu 8 | * @date 2024/9/26 9 | */ 10 | @Getter 11 | @Setter 12 | sealed abstract class Receiver permits PortReceiver, SsReceiver { 13 | public abstract void receiveClientBytes(CTX ctx, byte[] bytes) throws Exception; 14 | 15 | public abstract void removeCtx(CTX ctx); 16 | 17 | public abstract void exit(); 18 | 19 | public abstract long getLastHeartbeatTime(); 20 | } 21 | -------------------------------------------------------------------------------- /run/src/test/resources/demo/post/sc.yml: -------------------------------------------------------------------------------- 1 | # 和服务端的type保持一致 2 | type: post 3 | # 客户端用户名,每个sc进程用一个,不要重复 4 | clientUser: user1 5 | # 客户端密码 6 | clientPassword: 12345 7 | 8 | # 向服务端发数据包包体的最大字节数,默认10M.http post请求,在网络状态不佳引起阻塞时,有时会出现413 Request Entity Too Large问题,此时可适当调小这个值, 9 | maxSendBodySize: 1024000 10 | 11 | post: 12 | serverUrl: "http://localhost:20871" 13 | 14 | forwards: 15 | # 把192.168.0.2的22端口代理到本机的10022端口 16 | - localPort: 10022 17 | remoteHost: "wsl" 18 | remotePort: 22 19 | # 同理也可以代理数据库等任意TCP端口,只要服务端的hppt所在服务器能访问到的端口都行 20 | - localPort: 10023 21 | remoteHost: "192.168.0.3" 22 | remotePort: 3306 23 | 24 | -------------------------------------------------------------------------------- /run/src/test/resources/demo/ssrpost/sc.yml: -------------------------------------------------------------------------------- 1 | type: post 2 | workerGroupNum: 128 3 | 4 | # 客户端用户名,每个sc进程用一个,不要重复 5 | clientUser: user1 6 | # 客户端密码 7 | clientPassword: 123456 8 | #向服务端发数据请求体的字节数最大值 nginx代理的话,如果没办法修改配置,会出现413 Request Entity Too Large问题,没办法改nginx的话就用这个值限制 9 | maxSendBodySize: 1000000 10 | #是否启用压缩,默认启用 需和服务端保持一致 11 | enableCompress: true 12 | #是否启用内容加密,默认启用 需和服务端保持一致 13 | enableEncrypt: true 14 | post: 15 | #服务端http地址,可以填nginx转发过的地址 16 | serverUrl: "http://localhost:20871" 17 | #人为添加一个发送等待时间(毫秒),若网络质量不佳或发送请求过于频繁,可设置一个大于0的值来等待若干毫秒后一起发送 18 | sendSleepTime: 1 19 | 20 | forwards: 21 | 22 | - localPort: 31001 23 | remoteHost: "127.0.0.1" 24 | remotePort: 22 25 | -------------------------------------------------------------------------------- /_doc/ProtoMessage.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package pojo; 3 | option java_package = "org.wowtools.hppt.common.protobuf"; 4 | option java_outer_classname = "ProtoMessage"; 5 | 6 | //消息字节 7 | message BytesPb{ 8 | //真实字节 9 | bytes bytes = 1; 10 | //会话id 通过此id确认字节该与哪个端口/哪个用户端交互 11 | int32 sessionId = 2; 12 | //流水号 用于调试消息后发先至等问题,非调试时流水号为空 13 | int32 serialNumber = 3; 14 | 15 | } 16 | 17 | message MessagePb{ 18 | //消息字节list 19 | repeated BytesPb bytesPbList = 1; 20 | //客户端/服务端需要执行的命令 21 | repeated string commandList = 2; 22 | //流水号 用于调试消息后发先至等问题,非调试时流水号为空 23 | int32 serialNumber = 3; 24 | } 25 | 26 | message BytesListPb{ 27 | //bytes list 28 | repeated bytes bytesList = 1; 29 | //流水号 用于调试消息后发先至等问题,非调试时流水号为空 30 | int32 serialNumber = 2; 31 | } 32 | -------------------------------------------------------------------------------- /run/src/main/resources/META-INF/native-image/jni-config.json: -------------------------------------------------------------------------------- 1 | [ { 2 | "name" : "java.lang.Boolean", 3 | "methods" : [ { 4 | "name" : "getBoolean", 5 | "parameterTypes" : [ "java.lang.String" ] 6 | } ], 7 | "fields" : [ ] 8 | }, { 9 | "name" : "sun.management.VMManagementImpl", 10 | "fields" : [ { 11 | "name" : "compTimeMonitoringSupport" 12 | }, { 13 | "name" : "currentThreadCpuTimeSupport" 14 | }, { 15 | "name" : "objectMonitorUsageSupport" 16 | }, { 17 | "name" : "otherThreadCpuTimeSupport" 18 | }, { 19 | "name" : "remoteDiagnosticCommandsSupport" 20 | }, { 21 | "name" : "synchronizerUsageSupport" 22 | }, { 23 | "name" : "threadAllocatedMemorySupport" 24 | }, { 25 | "name" : "threadContentionMonitoringSupport" 26 | } ] 27 | } ] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/**/target/ 4 | !**/src/test/**/target/ 5 | 6 | ### IntelliJ IDEA ### 7 | .idea/ 8 | *.iws 9 | *.iml 10 | *.ipr 11 | 12 | ### Eclipse ### 13 | .apt_generated 14 | .classpath 15 | .factorypath 16 | .project 17 | .settings 18 | .springBeans 19 | .sts4-cache 20 | 21 | ### NetBeans ### 22 | /nbproject/private/ 23 | /nbbuild/ 24 | /dist/ 25 | /nbdist/ 26 | /.nb-gradle/ 27 | build/ 28 | !**/src/main/**/build/ 29 | !**/src/test/**/build/ 30 | 31 | ### VS Code ### 32 | .vscode/ 33 | 34 | ### Mac OS ### 35 | .DS_Store 36 | 37 | logs 38 | /arthas-output/ 39 | 40 | 41 | /run/src/main/resources/*.yml 42 | /run/src/main/resources/addons/ 43 | /kafkademo/src/main/resources/*.yml 44 | 45 | dependency-reduced-pom.xml 46 | 47 | /_localtest/src/ 48 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/common/util/RoughTimeUtil.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.common.util; 2 | 3 | /** 4 | * 粗粒度的时间戳获取工具,由于系统频繁地System.currentTimeMillis(),做一个定时器统一获取时间减少性能开销 5 | * 5秒更新一次时间 6 | * 7 | * @author liuyu 8 | * @date 2023/3/7 9 | */ 10 | public class RoughTimeUtil { 11 | private static long timestamp = System.currentTimeMillis(); 12 | 13 | static { 14 | Thread.startVirtualThread(() -> { 15 | while (true) { 16 | try { 17 | Thread.sleep(5000); 18 | } catch (InterruptedException e) { 19 | continue; 20 | } 21 | timestamp = System.currentTimeMillis(); 22 | } 23 | }); 24 | } 25 | 26 | public static long getTimestamp() { 27 | return timestamp; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /run/src/test/java/org/wowtools/hppt/common/util/GridAesCipherUtilTest.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.common.util; 2 | 3 | 4 | import java.nio.charset.StandardCharsets; 5 | import java.util.Base64; 6 | 7 | public class GridAesCipherUtilTest { 8 | 9 | public static void main(String[] args) { 10 | String msg = "1234567890qwertyuiop"; 11 | byte[] byte1 = GridAesCipherUtil.encrypt(msg.getBytes(StandardCharsets.UTF_8)); 12 | byte[] byte2 = GridAesCipherUtil.encrypt(msg.getBytes(StandardCharsets.UTF_8)); 13 | System.out.println(Base64.getEncoder().encodeToString(byte1)); 14 | System.out.println(Base64.getEncoder().encodeToString(byte2)); 15 | System.out.println(new String(GridAesCipherUtil.decrypt(byte1))); 16 | System.out.println(new String(GridAesCipherUtil.decrypt(byte2))); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /usbdemo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.wowtools.hppt 8 | hppt 9 | 1.0-SNAPSHOT 10 | 11 | 12 | usbdemo 13 | 14 | 利用USB HID, 在两台无网络连接的设备上通信 15 | 16 | 17 | 18 | org.usb4java 19 | usb4java 20 | 1.2.0 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /run/src/test/java/org/wowtools/hppt/common/client/ClientSessionManagerTest.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.common.client; 2 | 3 | 4 | import java.nio.charset.StandardCharsets; 5 | 6 | public class ClientSessionManagerTest { 7 | private static ClientSessionManager clientSessionManager; 8 | 9 | public static void main(String[] args) { 10 | clientSessionManager = new ClientSessionManagerBuilder().setLifecycle(new ClientSessionLifecycle() { 11 | 12 | @Override 13 | public void created(ClientSession clientSession) { 14 | clientSession.sendToUser("hello".getBytes(StandardCharsets.UTF_8)); 15 | System.out.println("created"); 16 | } 17 | 18 | 19 | @Override 20 | public void closed(ClientSession clientSession) { 21 | System.out.println("closed"); 22 | } 23 | }).build(); 24 | clientSessionManager.bindPort(null,10001); 25 | clientSessionManager.bindPort(null,10002); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /run/src/test/java/test/ReadYmlConfig.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; 5 | import org.wowtools.hppt.common.util.ResourcesReader; 6 | 7 | /** 8 | * @author liuyu 9 | * @date 2023/12/18 10 | */ 11 | public class ReadYmlConfig { 12 | 13 | public static final class SsConfig { 14 | public int port; 15 | 16 | /** 17 | * 超过sessionTimeout,给客户端发送存活确认命令,若下一个sessionTimeout内未收到确认,则强制关闭服务 18 | */ 19 | public long sessionTimeout = 120000; 20 | 21 | /** 22 | * 接收到客户端/真实端口的数据时,数据被暂存在一个队列里,队列满后强制关闭会话 23 | */ 24 | public int messageQueueSize = 2048; 25 | 26 | /** 27 | * 上传/下载文件用的目录 28 | */ 29 | public String fileDir; 30 | } 31 | 32 | public static void main(String[] args) throws Exception { 33 | String s = ResourcesReader.readStr(ReadYmlConfig.class, "ss.yml"); 34 | ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); 35 | SsConfig obj = mapper.readValue(s, SsConfig.class); 36 | System.out.println(obj); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /run/src/test/java/t1/ClientSession.java: -------------------------------------------------------------------------------- 1 | package t1; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.Unpooled; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | /** 9 | * @author liuyu 10 | * @date 2023/11/17 11 | */ 12 | @Slf4j 13 | public class ClientSession { 14 | private final int sessionId; 15 | private final ChannelHandlerContext channelHandlerContext; 16 | 17 | public ClientSession(int sessionId, ChannelHandlerContext channelHandlerContext) { 18 | this.sessionId = sessionId; 19 | this.channelHandlerContext = channelHandlerContext; 20 | } 21 | 22 | public void putBytes(byte[] bytes) { 23 | log.debug("ClientSession {} 收到服务端发来的字节数 {}", sessionId, bytes.length); 24 | ByteBuf msg = Unpooled.copiedBuffer(bytes); 25 | channelHandlerContext.writeAndFlush(msg); 26 | } 27 | 28 | public int getSessionId() { 29 | return sessionId; 30 | } 31 | 32 | public ChannelHandlerContext getChannelHandlerContext() { 33 | return channelHandlerContext; 34 | } 35 | 36 | public void close() { 37 | channelHandlerContext.close(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/run/sc/common/SsReceiver.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.run.sc.common; 2 | 3 | import org.wowtools.hppt.common.client.ClientSession; 4 | import org.wowtools.hppt.common.util.BufferPool; 5 | import org.wowtools.hppt.common.util.RoughTimeUtil; 6 | import org.wowtools.hppt.run.sc.pojo.ScConfig; 7 | 8 | 9 | /** 10 | * @author liuyu 11 | * @date 2024/9/27 12 | */ 13 | public final class SsReceiver implements Receiver { 14 | public final BufferPool serverBytesQueue = new BufferPool<>(">SsReceiver-serverBytesQueue"); 15 | 16 | private long lastUsedTime = RoughTimeUtil.getTimestamp(); 17 | 18 | public SsReceiver(ScConfig config, ClientSessionService clientSessionService) throws Exception { 19 | } 20 | 21 | @Override 22 | public void receiveServerBytes(byte[] bytes) throws Exception { 23 | lastUsedTime = RoughTimeUtil.getTimestamp(); 24 | serverBytesQueue.add(bytes); 25 | } 26 | 27 | @Override 28 | public void closeClientSession(ClientSession clientSession) { 29 | 30 | } 31 | 32 | @Override 33 | public void exit() { 34 | 35 | } 36 | 37 | @Override 38 | public boolean notUsed() { 39 | return RoughTimeUtil.getTimestamp() - lastUsedTime > 60_000L; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/run/ss/util/SsUtil.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.run.ss.util; 2 | 3 | import io.netty.util.internal.StringUtil; 4 | import org.wowtools.hppt.common.server.ServerSessionLifecycle; 5 | import org.wowtools.hppt.common.server.ServerSessionManagerBuilder; 6 | import org.wowtools.hppt.run.ss.pojo.SsConfig; 7 | 8 | /** 9 | * @author liuyu 10 | * @date 2024/3/12 11 | */ 12 | public class SsUtil { 13 | 14 | public static ServerSessionManagerBuilder createServerSessionManagerBuilder(SsConfig ssConfig) { 15 | return new ServerSessionManagerBuilder() 16 | .setLifecycle(buildServerSessionLifecycle(ssConfig)); 17 | } 18 | 19 | private static ServerSessionLifecycle buildServerSessionLifecycle(SsConfig ssConfig) { 20 | if (StringUtil.isNullOrEmpty(ssConfig.lifecycle)) { 21 | return new ServerSessionLifecycle() { 22 | }; 23 | } else { 24 | try { 25 | Class clazz = (Class) Class.forName(ssConfig.lifecycle); 26 | return clazz.getDeclaredConstructor().newInstance(); 27 | } catch (Exception e) { 28 | throw new RuntimeException(e); 29 | } 30 | 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/common/util/GridAesCipherUtil.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.common.util; 2 | 3 | import java.util.Random; 4 | 5 | /** 6 | * 以栅格方式在奇数位加盐,再aes加密的工具 7 | * 8 | * @author liuyu 9 | * @date 2024/4/16 10 | */ 11 | public class GridAesCipherUtil { 12 | private static final AesCipherUtil aesCipherUtil = new AesCipherUtil("hppt"); 13 | private static final Random random = new Random(); 14 | 15 | /** 16 | * 加密 17 | * 18 | * @param bytes bytes 19 | * @return bytes 20 | */ 21 | public static byte[] encrypt(byte[] bytes) { 22 | byte[] bytes1 = new byte[bytes.length * 2]; 23 | for (int i = 0; i < bytes1.length; i += 2) { 24 | bytes1[i] = bytes[i / 2]; 25 | bytes1[i + 1] = (byte) random.nextInt(255); 26 | } 27 | return aesCipherUtil.encryptor.encrypt(bytes1); 28 | } 29 | 30 | /** 31 | * 解密 32 | * 33 | * @param bytes bytes 34 | * @return bytes 35 | */ 36 | public static byte[] decrypt(byte[] bytes) { 37 | bytes = aesCipherUtil.descriptor.decrypt(bytes); 38 | byte[] bytes1 = new byte[bytes.length / 2]; 39 | for (int i = 0; i < bytes1.length; i++) { 40 | bytes1[i] = bytes[i * 2]; 41 | } 42 | return bytes1; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/common/server/ServerSessionManagerBuilder.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.common.server; 2 | 3 | import io.netty.channel.EventLoopGroup; 4 | import org.wowtools.hppt.common.util.NettyObjectBuilder; 5 | 6 | /** 7 | * @author liuyu 8 | * @date 2024/1/4 9 | */ 10 | public class ServerSessionManagerBuilder { 11 | protected EventLoopGroup group; 12 | protected ServerSessionLifecycle lifecycle; 13 | protected long sessionTimeout = 60000; 14 | 15 | public ServerSessionManagerBuilder setSessionTimeout(long sessionTimeout) { 16 | this.sessionTimeout = sessionTimeout; 17 | return this; 18 | } 19 | 20 | public ServerSessionManagerBuilder setLifecycle(ServerSessionLifecycle lifecycle) { 21 | this.lifecycle = lifecycle; 22 | return this; 23 | } 24 | 25 | public ServerSessionManagerBuilder setGroup(EventLoopGroup group) { 26 | this.group = group; 27 | return this; 28 | } 29 | 30 | public ServerSessionManager build() { 31 | if (group == null) { 32 | group = NettyObjectBuilder.buildEventLoopGroup(); 33 | } 34 | 35 | if (lifecycle == null) { 36 | throw new RuntimeException("lifecycle不能为空"); 37 | } 38 | 39 | return new ServerSessionManager(this); 40 | } 41 | 42 | 43 | } 44 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/common/util/FileProducerBuffer.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.common.util; 2 | 3 | import java.io.File; 4 | import java.io.FileNotFoundException; 5 | import java.io.RandomAccessFile; 6 | import java.nio.ByteBuffer; 7 | import java.nio.channels.FileChannel; 8 | import java.nio.channels.FileLock; 9 | /** 10 | * 以文件做为缓冲池的生产者-消费者模式中的生产者 11 | */ 12 | public class FileProducerBuffer implements AutoCloseable{ 13 | 14 | private final RandomAccessFile raf; 15 | private final FileChannel channel; 16 | 17 | /** 18 | * @param file 缓冲池文件 19 | */ 20 | public FileProducerBuffer(File file) { 21 | try { 22 | raf = new RandomAccessFile(file, "rw"); 23 | channel = raf.getChannel(); 24 | } catch (FileNotFoundException e) { 25 | throw new RuntimeException(e); 26 | } 27 | } 28 | 29 | public void write(byte[] data) { 30 | try (FileLock fileLock = channel.lock()) { 31 | //FileLock 确保多个进程在读写文件时互斥访问 32 | raf.seek(raf.length()); 33 | channel.write(ByteBuffer.wrap(data)); 34 | channel.force(true); 35 | } catch (Exception e) { 36 | throw new RuntimeException(e); 37 | } 38 | } 39 | 40 | @Override 41 | public void close() throws Exception { 42 | channel.close(); 43 | raf.close(); 44 | } 45 | } 46 | 47 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/common/client/ClientBytesSender.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.common.client; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import org.wowtools.hppt.common.pojo.SessionBytes; 6 | import org.wowtools.hppt.common.util.RoughTimeUtil; 7 | 8 | /** 9 | * @author liuyu 10 | * @date 2024/2/1 11 | */ 12 | public interface ClientBytesSender { 13 | 14 | /** 15 | * sessionId回调函数 16 | */ 17 | public static abstract class SessionIdCallBack { 18 | public final long createTime = RoughTimeUtil.getTimestamp(); 19 | public final ChannelHandlerContext channelHandlerContext; 20 | 21 | public SessionIdCallBack(ChannelHandlerContext channelHandlerContext) { 22 | this.channelHandlerContext = channelHandlerContext; 23 | } 24 | 25 | public abstract void cb(int sessionId); 26 | } 27 | 28 | /** 29 | * 用户建立连接后,准备新建一个ClientSession前触发 30 | * 31 | * @param port 本地端口 32 | * @param ctx 建立连接对应的netty ctx 33 | * @param cb 生成一个唯一的sessionId返回,一般需要借助服务端接口来分配id 34 | */ 35 | void connected(int port, ChannelHandlerContext ctx, SessionIdCallBack cb); 36 | 37 | /** 38 | * 向目标发送字节的具体方式,如post请求,websocket等 39 | * 40 | * @param clientSession clientSession 41 | * @param sessionBytes bytes 42 | */ 43 | void sendToTarget(ClientSession clientSession, SessionBytes sessionBytes); 44 | } 45 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/run/sc/RunSc.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.run.sc; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.wowtools.hppt.common.util.Constant; 5 | import org.wowtools.hppt.common.util.ResourcesReader; 6 | import org.wowtools.hppt.run.sc.common.ClientSessionService; 7 | import org.wowtools.hppt.run.sc.pojo.ScConfig; 8 | 9 | /** 10 | * @author liuyu 11 | * @date 2024/1/30 12 | */ 13 | @Slf4j 14 | public class RunSc { 15 | 16 | public static void main(String[] args) { 17 | String configPath; 18 | if (args.length <= 1) { 19 | configPath = "sc.yml"; 20 | } else { 21 | configPath = args[1]; 22 | } 23 | ScConfig config; 24 | try { 25 | config = Constant.ymlMapper.readValue(ResourcesReader.readStr(RunSc.class, configPath), ScConfig.class); 26 | } catch (Exception e) { 27 | throw new RuntimeException("读取配置文件异常", e); 28 | } 29 | while (true) { 30 | try (ClientSessionService clientSessionService = ClientSessionServiceBuilder.build(config)){ 31 | clientSessionService.sync(); 32 | } catch (Exception e) { 33 | log.warn("服务异常", e); 34 | } 35 | log.warn("----------------------销毁当前Service,10秒后重启"); 36 | try { 37 | Thread.sleep(10000); 38 | } catch (InterruptedException e) { 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/run/ss/post/PostServerSessionService.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.run.ss.post; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.wowtools.common.utils.LruCache; 5 | import org.wowtools.hppt.run.ss.common.ServerSessionService; 6 | import org.wowtools.hppt.run.ss.pojo.SsConfig; 7 | 8 | import java.util.Map; 9 | 10 | /** 11 | * @author liuyu 12 | * @date 2024/1/24 13 | */ 14 | @Slf4j 15 | public class PostServerSessionService extends ServerSessionService { 16 | 17 | private NettyHttpServer server; 18 | 19 | public PostServerSessionService(SsConfig ssConfig) throws Exception { 20 | super(ssConfig); 21 | int size = null == ssConfig.clients ? 8 : ssConfig.clients.size() * 2; 22 | ctxMap = LruCache.buildCache(size, size); 23 | } 24 | 25 | @Override 26 | protected void init(SsConfig ssConfig) throws Exception { 27 | log.info("*********"); 28 | server = new NettyHttpServer(ssConfig.port, this, ssConfig); 29 | server.start(); 30 | } 31 | 32 | protected final Map ctxMap; 33 | 34 | @Override 35 | protected void sendBytesToClient(PostCtx ctx, byte[] bytes) { 36 | ctx.sendQueue.add(bytes); 37 | } 38 | 39 | @Override 40 | protected void closeCtx(PostCtx ctx) { 41 | ctxMap.remove(ctx.cookie); 42 | } 43 | 44 | @Override 45 | public void onExit() throws Exception { 46 | server.stop(); 47 | } 48 | 49 | 50 | } 51 | -------------------------------------------------------------------------------- /run/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | logs/${LOG_FILE_NAME}.log 11 | 12 | 13 | logs/${LOG_FILE_NAME}.%d{yyyy-MM-dd}.log 14 | 15 | 30 16 | 17 | 18 | ${LOG_PATTERN} 19 | 20 | 21 | 22 | 23 | 24 | 25 | ${LOG_PATTERN} 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /kafkademo/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | logs/${LOG_FILE_NAME}.log 11 | 12 | 13 | logs/${LOG_FILE_NAME}.%d{yyyy-MM-dd}.log 14 | 15 | 30 16 | 17 | 18 | ${LOG_PATTERN} 19 | 20 | 21 | 22 | 23 | 24 | 25 | ${LOG_PATTERN} 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/common/util/AddonsLoader.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.common.util; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.net.URL; 8 | import java.net.URLClassLoader; 9 | 10 | @Slf4j 11 | public class AddonsLoader { 12 | 13 | private URLClassLoader urlClassLoader; 14 | 15 | public AddonsLoader(String jarsDirPath) throws IOException { 16 | File dir = new File(jarsDirPath); 17 | if (!dir.exists() || !dir.isDirectory()) { 18 | log.warn(("插件目录路径不存在或不是文件夹,跳过: " + jarsDirPath)); 19 | return; 20 | } 21 | 22 | File[] jarFiles = dir.listFiles((d, name) -> name.endsWith(".jar")); 23 | if (jarFiles == null || jarFiles.length == 0) { 24 | log.warn(("插件目录下没有找到jar文件,跳过: " + jarsDirPath)); 25 | return; 26 | } 27 | 28 | URL[] urls = new URL[jarFiles.length]; 29 | for (int i = 0; i < jarFiles.length; i++) { 30 | urls[i] = jarFiles[i].toURI().toURL(); 31 | } 32 | 33 | // 创建类加载器 34 | this.urlClassLoader = new URLClassLoader(urls, Thread.currentThread().getContextClassLoader()); 35 | 36 | // 设置为当前线程类加载器 37 | Thread.currentThread().setContextClassLoader(this.urlClassLoader); 38 | } 39 | 40 | /** 41 | * 加载某个类 42 | */ 43 | public Class loadClass(String className) throws ClassNotFoundException { 44 | return urlClassLoader.loadClass(className); 45 | } 46 | 47 | public URLClassLoader getClassLoader() { 48 | return urlClassLoader; 49 | } 50 | } 51 | 52 | -------------------------------------------------------------------------------- /_doc/demo/hppt.md: -------------------------------------------------------------------------------- 1 | ## 示例1 通过http端口,hppt协议代理访问内部服务器SSH端口 2 | 3 | 假设你有一个服务器集群,防火墙放行了端口20871(111.222.33.44:20871),你想要访问集群中的应用服务器(192.168.0.2)的22端口,则可以按如下结构部署 4 | 5 | ![示例1](../img/hpptdemo.jpg) 6 | 7 | 1、在集群中任一服务器上新建一个hppt目录,并上传hppt.jar、ss.yml、log4j2.xml文件到此目录下: 8 | 9 | ``` 10 | hppt 11 | - hppt.jar 12 | - ss.yml 13 | - log4j2.xml 14 | ``` 15 | 16 | 并调整ss.yml的配置信息: 17 | 18 | ```yaml 19 | type: hppt 20 | #服务端口 21 | port: 20871 22 | # 允许的客户端账号和密码 23 | clients: 24 | - user: user1 25 | password: 12345 26 | - user: user2 27 | password: 112233 28 | 29 | ``` 30 | (注:实际应用中,为了确保安全,建议把密码设置得更复杂一些) 31 | 32 | 33 | 执行如下命令运行服务端的hppt 34 | 35 | jar包运行 36 | ```shell 37 | cd hppt 38 | /bin/java -jar hppt.jar ss ss.yml 39 | ``` 40 | 41 | 2、自己笔记本上,新建一个hppt目录,拷贝hppt.jar、sc.yml、log4j2.xml文件到此目录下: 42 | 43 | ``` 44 | hppt 45 | - hppt.jar 46 | - sc.yml 47 | - log4j2.xml 48 | ``` 49 | 50 | 并调整sc.yml的配置信息: 51 | 52 | ```yaml 53 | # 和服务端的type保持一致 54 | type: hppt 55 | # 客户端用户名,每个sc进程用一个,不要重复 56 | clientUser: user1 57 | # 客户端密码 58 | clientPassword: 12345 59 | 60 | hppt: 61 | #服务端地址 62 | host: "111.222.33.44" 63 | #服务端端口 64 | port: 20871 65 | forwards: 66 | # 把192.168.0.2的22端口代理到本机的10022端口 67 | - localPort: 10022 68 | remoteHost: "192.168.0.2" 69 | remotePort: 22 70 | # 同理也可以代理数据库等任意TCP端口,只要服务端的hppt所在服务器能访问到的端口都行 71 | - localPort: 10023 72 | remoteHost: "192.168.0.3" 73 | remotePort: 3306 74 | 75 | 76 | ``` 77 | 78 | 执行如下命令启动客户端的hppt 79 | 80 | jar包运行 81 | ```shell 82 | cd hppt 83 | /bin/java -jar hppt.jar sc sc.yml 84 | ``` 85 | 86 | 87 | 随后,你就可以在公司用linux连接工具访问localhost的10022端口,来登录应用服务器了 88 | -------------------------------------------------------------------------------- /run/src/main/resources/META-INF/native-image/resource-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "resources" : { 3 | "bundles" : [ ], 4 | "includes" : [ { 5 | "pattern" : "\\QMETA-INF/services/ch.qos.logback.classic.spi.Configurator\\E" 6 | }, { 7 | "pattern" : "\\QMETA-INF/services/java.lang.System$LoggerFinder\\E" 8 | }, { 9 | "pattern" : "\\QMETA-INF/services/java.net.spi.InetAddressResolverProvider\\E" 10 | }, { 11 | "pattern" : "\\QMETA-INF/services/java.net.spi.URLStreamHandlerProvider\\E" 12 | }, { 13 | "pattern" : "\\QMETA-INF/services/java.nio.channels.spi.SelectorProvider\\E" 14 | }, { 15 | "pattern" : "\\QMETA-INF/services/java.time.zone.ZoneRulesProvider\\E" 16 | }, { 17 | "pattern" : "\\QMETA-INF/services/javax.xml.parsers.SAXParserFactory\\E" 18 | }, { 19 | "pattern" : "\\QMETA-INF/services/org.slf4j.spi.SLF4JServiceProvider\\E" 20 | }, { 21 | "pattern" : "\\Qlogback-test.scmo\\E" 22 | }, { 23 | "pattern" : "\\Qlogback-test.xml\\E" 24 | }, { 25 | "pattern" : "\\Qlogback.scmo\\E" 26 | }, { 27 | "pattern" : "\\Qlogback.xml\\E" 28 | }, { 29 | "pattern" : "java.base:\\Qjdk/internal/icu/impl/data/icudt72b/uprops.icu\\E" 30 | }, { 31 | "pattern" : "java.base:\\Qsun/net/idn/uidna.spp\\E" 32 | }, { 33 | "pattern" : "\\QMETA-INF/services/java.util.spi.ResourceBundleControlProvider\\E" 34 | }, { 35 | "pattern" : "\\QMETA-INF/services/org.eclipse.jetty.http.HttpFieldPreEncoder\\E" 36 | }, { 37 | "pattern" : "\\Qorg/eclipse/jetty/http/encoding.properties\\E" 38 | }, { 39 | "pattern" : "\\Qorg/eclipse/jetty/http/mime.properties\\E" 40 | }, { 41 | "pattern" : "\\Qorg/eclipse/jetty/version/build.properties\\E" 42 | } ] 43 | } 44 | } -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/run/Run.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.run; 2 | 3 | import ch.qos.logback.classic.LoggerContext; 4 | import ch.qos.logback.classic.joran.JoranConfigurator; 5 | import ch.qos.logback.core.util.StatusPrinter; 6 | import io.netty.util.ResourceLeakDetector; 7 | import org.slf4j.LoggerFactory; 8 | import org.wowtools.hppt.common.util.ResourcesReader; 9 | import org.wowtools.hppt.run.sc.RunSc; 10 | import org.wowtools.hppt.run.ss.RunSs; 11 | 12 | import java.io.File; 13 | 14 | /** 15 | * @author liuyu 16 | * @date 2024/1/5 17 | */ 18 | public class Run { 19 | 20 | public static void main(String[] args) throws Exception { 21 | System.setProperty("logFileName", args.length > 0 ? String.join("-", args).replace(".", "_") : "hppt"); 22 | try { 23 | LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); 24 | File externalConfigFile = new File(ResourcesReader.getRootPath(Run.class) + "/logback.xml"); 25 | JoranConfigurator configurator = new JoranConfigurator(); 26 | configurator.setContext(context); 27 | context.reset(); 28 | configurator.doConfigure(externalConfigFile); 29 | StatusPrinter.printInCaseOfErrorsOrWarnings(context); 30 | } catch (Exception e) { 31 | System.out.println("未加载到根目录下logback.xml文件,使用默认配置 " + e.getMessage()); 32 | } 33 | String type = args[0]; 34 | switch (type) { 35 | case "ss": 36 | RunSs.main(args); 37 | break; 38 | case "sc": 39 | RunSc.main(args); 40 | break; 41 | default: 42 | throw new IllegalStateException("Unexpected type: " + type); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/common/client/ClientSessionLifecycle.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.common.client; 2 | 3 | /** 4 | * ClientSession的生命周期,包含ClientSession从创建、交互、销毁各过程的触发事件 5 | * 6 | * @author liuyu 7 | * @date 2024/1/4 8 | */ 9 | public interface ClientSessionLifecycle { 10 | 11 | /** 12 | * ClientSession新建完成后触发 13 | * 14 | * @param clientSession ClientSession 15 | */ 16 | default void created(ClientSession clientSession) { 17 | 18 | } 19 | 20 | /** 21 | * 发送字节给用户前触发 22 | * 23 | * @param clientSession ClientSession 24 | * @param bytes 发送的字节 25 | * @return 修改后的字节,若不需要修改直接返回bytes,返回null则表示忽略掉此字节 26 | */ 27 | default byte[] beforeSendToUser(ClientSession clientSession, byte[] bytes) { 28 | return bytes; 29 | } 30 | 31 | /** 32 | * 发送字节给用户后触发 33 | * 34 | * @param clientSession ClientSession 35 | * @param bytes 发送的字节 36 | */ 37 | default void afterSendToUser(ClientSession clientSession, byte[] bytes) { 38 | } 39 | 40 | /** 41 | * 发送字节给目标端口前触发 42 | * 43 | * @param clientSession ClientSession 44 | * @param bytes 发送的字节 45 | * return 修改后的字节,若不需要修改直接返回bytes,返回null则表示忽略掉此字节 46 | */ 47 | default byte[] beforeSendToTarget(ClientSession clientSession, byte[] bytes) { 48 | return bytes; 49 | } 50 | 51 | /** 52 | * 发送字节给目标端口后触发 53 | * 54 | * @param clientSession ClientSession 55 | * @param bytes 发送的字节 56 | */ 57 | default void afterSendToTarget(ClientSession clientSession, byte[] bytes) { 58 | } 59 | 60 | /** 61 | * 关闭后触发 62 | * 63 | * @param clientSession ClientSession 64 | */ 65 | default void closed(ClientSession clientSession) { 66 | 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /kafkademo/src/main/java/org/wowtools/hppt/kafkademo/ClientDemo.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.kafkademo; 2 | 3 | import org.wowtools.hppt.run.sc.common.ClientSessionService; 4 | import org.wowtools.hppt.run.sc.pojo.ScConfig; 5 | 6 | import java.util.ArrayList; 7 | 8 | /** 9 | * 客户端,部署在电脑A上 10 | * @author liuyu 11 | * @date 2024/6/15 12 | */ 13 | public class ClientDemo extends ClientSessionService { 14 | //TODO 传输文件等大字节数传播的情况下,需处理kafka字节顺序消费问题 15 | public ClientDemo(ScConfig config) throws Exception { 16 | super(config); 17 | } 18 | 19 | private KafkaUtil.BytesFunction sendToServer; 20 | private KafkaUtil.BytesFunction clientConsumer; 21 | @Override 22 | public void connectToServer(ScConfig config, Cb cb) throws Exception { 23 | //初始化时构造好向kafka生产和消费数据的工具 24 | sendToServer = KafkaUtil.buildProducer(KafkaUtil.config.clientSendTopic); 25 | 26 | clientConsumer = (bytes) -> { 27 | //消费到客户端的数据,调用receiveServerBytes方法来接收 28 | try { 29 | receiveServerBytes(bytes); 30 | } catch (Exception e) { 31 | throw new RuntimeException(e); 32 | } 33 | }; 34 | KafkaUtil.buildConsumer("client", KafkaUtil.config.serverSendTopic, clientConsumer); 35 | cb.end(null);//调用end方法,通知框架连接完成 36 | } 37 | 38 | @Override 39 | public void sendBytesToServer(byte[] bytes) { 40 | sendToServer.f(bytes); 41 | } 42 | 43 | public static void main(String[] args) throws Exception{ 44 | ScConfig cfg = new ScConfig(); 45 | cfg.clientUser = "user1"; 46 | cfg.clientPassword = "12345"; 47 | ScConfig.Forward forward = new ScConfig.Forward(); 48 | forward.localPort = 22022; 49 | forward.remoteHost = "127.0.0.1"; 50 | forward.remotePort = 22; 51 | cfg.forwards = new ArrayList<>(); 52 | cfg.forwards.add(forward); 53 | new ClientDemo(cfg).sync(); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/common/server/ServerSessionLifecycle.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.common.server; 2 | 3 | 4 | import org.wowtools.hppt.common.pojo.SendAbleSessionBytes; 5 | import org.wowtools.hppt.common.pojo.SessionBytes; 6 | 7 | /** 8 | * ServerSession的生命周期,包含ServerSession从创建、交互、销毁各过程的触发事件 9 | * 10 | * @author liuyu 11 | * @date 2024/1/4 12 | */ 13 | public interface ServerSessionLifecycle { 14 | 15 | /** 16 | * ServerSession新建完成后触发 17 | * 18 | * @param serverSession ServerSession 19 | */ 20 | default void created(ServerSession serverSession) { 21 | 22 | } 23 | 24 | /** 25 | * 发送字节给目标端口前触发 26 | * 27 | * @param serverSession ServerSession 28 | * @param bytes 发送的字节 29 | * @return 修改后的字节,若不需要修改直接返回bytes,返回null则表示忽略掉此字节 30 | */ 31 | default byte[] beforeSendToTarget(ServerSession serverSession, byte[] bytes) { 32 | return bytes; 33 | } 34 | 35 | /** 36 | * 发送字节给目标端口后触发 37 | * 38 | * @param serverSession ServerSession 39 | * @param bytes 发送的字节 40 | */ 41 | default void afterSendToTarget(ServerSession serverSession, byte[] bytes) { 42 | } 43 | 44 | /** 45 | * 发送字节给客户端缓冲区 46 | * 47 | * @param sessionBytes 发送的字节 48 | * @param callBack 回调,not null 49 | */ 50 | default void sendToClientBuffer(SessionBytes sessionBytes, LoginClientService.Client client, SendAbleSessionBytes.CallBack callBack) { 51 | client.addBytes(sessionBytes, callBack); 52 | } 53 | 54 | /** 55 | * 发送字节给用户后触发 56 | * 57 | * @param serverSession ServerSession 58 | * @param bytes 发送的字节 59 | */ 60 | default void afterSendToUser(ServerSession serverSession, byte[] bytes) { 61 | } 62 | 63 | 64 | /** 65 | * 关闭后触发 66 | * 67 | * @param serverSession ServerSession 68 | */ 69 | default void closed(ServerSession serverSession) { 70 | 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /_localtest/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.wowtools.hppt 8 | hppt 9 | 1.0-SNAPSHOT 10 | 11 | 12 | _localtest 13 | 14 | 15 | 16 | 17 | org.wowtools.hppt 18 | run 19 | 1.0-SNAPSHOT 20 | 21 | 22 | com.squareup.okhttp3 23 | okhttp 24 | 25 | 26 | io.netty 27 | netty-transport 28 | 4.1.91.Final 29 | 30 | 31 | io.netty 32 | netty-codec-http 33 | 4.1.91.Final 34 | 35 | 36 | org.wowtools.hppt 37 | common 38 | 1.0-SNAPSHOT 39 | 40 | 41 | org.eclipse.jetty.toolchain 42 | jetty-jakarta-servlet-api 43 | 5.0.2 44 | compile 45 | 46 | 47 | org.eclipse.jetty 48 | jetty-servlet 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /addons-kafka/src/main/java/org/wowtools/hppt/addons/kafka/KafkaClientSessionService.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.addons.kafka; 2 | 3 | import org.wowtools.hppt.run.sc.common.ClientSessionService; 4 | import org.wowtools.hppt.run.sc.pojo.ScConfig; 5 | 6 | import java.util.ArrayList; 7 | 8 | /** 9 | * 客户端,部署在电脑A上 10 | * 11 | * @author liuyu 12 | * @date 2024/6/15 13 | */ 14 | public class KafkaClientSessionService extends ClientSessionService { 15 | public KafkaClientSessionService(ScConfig config) throws Exception { 16 | super(config); 17 | } 18 | 19 | private KafkaUtil.BytesFunction sendToServer; 20 | private KafkaUtil.BytesFunction clientConsumer; 21 | 22 | @Override 23 | public void connectToServer(ScConfig config, Cb cb) throws Exception { 24 | //初始化时构造好向kafka生产和消费数据的工具 25 | sendToServer = KafkaUtil.buildProducer(KafkaUtil.config.clientSendTopic, false); 26 | 27 | clientConsumer = (bytes) -> { 28 | //消费到客户端的数据,调用receiveServerBytes方法来接收 29 | try { 30 | receiveServerBytes(bytes); 31 | } catch (Exception e) { 32 | throw new RuntimeException(e); 33 | } 34 | }; 35 | KafkaUtil.buildConsumer("client", KafkaUtil.config.serverSendTopic, clientConsumer, false); 36 | cb.end(null);//调用end方法,通知框架连接完成 37 | } 38 | 39 | @Override 40 | public void sendBytesToServer(byte[] bytes) { 41 | sendToServer.f(bytes); 42 | } 43 | 44 | public static void main(String[] args) throws Exception { 45 | ScConfig cfg = new ScConfig(); 46 | cfg.clientUser = "user1"; 47 | cfg.clientPassword = "12345"; 48 | ScConfig.Forward forward = new ScConfig.Forward(); 49 | forward.localPort = 22022; 50 | forward.remoteHost = "127.0.0.1"; 51 | forward.remotePort = 22; 52 | cfg.forwards = new ArrayList<>(); 53 | cfg.forwards.add(forward); 54 | new KafkaClientSessionService(cfg).sync(); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/common/client/ClientSessionManagerBuilder.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.common.client; 2 | 3 | import io.netty.channel.EventLoopGroup; 4 | import org.wowtools.hppt.common.util.NettyObjectBuilder; 5 | 6 | /** 7 | * @author liuyu 8 | * @date 2024/1/4 9 | */ 10 | public class ClientSessionManagerBuilder { 11 | protected int bufferSize; 12 | protected EventLoopGroup bossGroup; 13 | protected EventLoopGroup workerGroup; 14 | 15 | protected ClientSessionLifecycle lifecycle; 16 | protected ClientBytesSender clientBytesSender; 17 | 18 | public ClientSessionManagerBuilder setBufferSize(int bufferSize) { 19 | this.bufferSize = bufferSize; 20 | return this; 21 | } 22 | 23 | public ClientSessionManagerBuilder setBossGroup(EventLoopGroup bossGroup) { 24 | this.bossGroup = bossGroup; 25 | return this; 26 | } 27 | 28 | public ClientSessionManagerBuilder setWorkerGroup(EventLoopGroup workerGroup) { 29 | this.workerGroup = workerGroup; 30 | return this; 31 | } 32 | 33 | public ClientSessionManagerBuilder setLifecycle(ClientSessionLifecycle lifecycle) { 34 | this.lifecycle = lifecycle; 35 | return this; 36 | } 37 | 38 | public ClientSessionManagerBuilder setClientBytesSender(ClientBytesSender clientBytesSender) { 39 | this.clientBytesSender = clientBytesSender; 40 | return this; 41 | } 42 | 43 | public ClientSessionManager build() { 44 | if (bufferSize <= 0) { 45 | bufferSize = 10240; 46 | } 47 | if (bossGroup == null) { 48 | bossGroup = NettyObjectBuilder.buildVirtualThreadEventLoopGroup(1); 49 | } 50 | if (workerGroup == null) { 51 | workerGroup = NettyObjectBuilder.buildVirtualThreadEventLoopGroup(); 52 | } 53 | if (lifecycle == null) { 54 | throw new RuntimeException("lifecycle不能为空"); 55 | } 56 | 57 | return new ClientSessionManager(this); 58 | } 59 | 60 | 61 | } 62 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/run/sc/util/ScUtil.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.run.sc.util; 2 | 3 | import io.netty.util.internal.StringUtil; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.wowtools.hppt.common.client.ClientBytesSender; 6 | import org.wowtools.hppt.common.client.ClientSessionLifecycle; 7 | import org.wowtools.hppt.common.client.ClientSessionManager; 8 | import org.wowtools.hppt.common.client.ClientSessionManagerBuilder; 9 | import org.wowtools.hppt.common.util.NettyObjectBuilder; 10 | import org.wowtools.hppt.run.sc.pojo.ScConfig; 11 | 12 | /** 13 | * @author liuyu 14 | * @date 2024/3/12 15 | */ 16 | @Slf4j 17 | public class ScUtil { 18 | 19 | //建立ClientSessionManager,并绑上配置的端口 20 | public static ClientSessionManager createClientSessionManager(ScConfig config, ClientSessionLifecycle lifecycle, ClientBytesSender clientBytesSender) { 21 | 22 | ClientSessionManager clientSessionManager = new ClientSessionManagerBuilder() 23 | .setBufferSize(config.maxSendBodySize * 2) 24 | .setLifecycle(lifecycle) 25 | .setWorkerGroup(NettyObjectBuilder.buildEventLoopGroup(config.workerGroupNum)) 26 | .setClientBytesSender(clientBytesSender) 27 | .build(); 28 | if (null != config.forwards) { 29 | for (ScConfig.Forward forward : config.forwards) { 30 | String localHost = forward.localHost; 31 | if (StringUtil.isNullOrEmpty(localHost)) { 32 | localHost = config.localHost; 33 | } 34 | if (StringUtil.isNullOrEmpty(localHost)) { 35 | localHost = null; 36 | } 37 | 38 | boolean res = clientSessionManager.bindPort(localHost, forward.localPort); 39 | log.info("bind port {} {} -> {}:{}", res ? "success" : "fail", 40 | forward.localPort, forward.remoteHost, forward.remotePort); 41 | } 42 | } 43 | return clientSessionManager; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/run/sc/ClientSessionServiceBuilder.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.run.sc; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.wowtools.hppt.common.util.AddonsLoader; 5 | import org.wowtools.hppt.common.util.ResourcesReader; 6 | import org.wowtools.hppt.run.sc.common.ClientSessionService; 7 | import org.wowtools.hppt.run.sc.file.FileClientSessionService; 8 | import org.wowtools.hppt.run.sc.hppt.HpptClientSessionService; 9 | import org.wowtools.hppt.run.sc.pojo.ScConfig; 10 | import org.wowtools.hppt.run.sc.post.PostClientSessionService; 11 | import org.wowtools.hppt.run.sc.rhppt.RHpptClientSessionService; 12 | import org.wowtools.hppt.run.sc.rpost.RPostClientSessionService; 13 | import org.wowtools.hppt.run.sc.websocket.WebSocketClientSessionService; 14 | 15 | /** 16 | * @author liuyu 17 | * @date 2024/10/8 18 | */ 19 | @Slf4j 20 | public class ClientSessionServiceBuilder { 21 | 22 | public static ClientSessionService build(ScConfig config) throws Exception { 23 | log.info("type {}", config.type); 24 | return switch (config.type) { 25 | case "post" -> new PostClientSessionService(config); 26 | case "websocket" -> new WebSocketClientSessionService(config); 27 | case "hppt" -> new HpptClientSessionService(config); 28 | case "rhppt" -> new RHpptClientSessionService(config); 29 | case "rpost" -> new RPostClientSessionService(config); 30 | case "file" -> new FileClientSessionService(config); 31 | default -> { 32 | String addonsPath = config.addonsPath; 33 | if (null == addonsPath) { 34 | addonsPath = ResourcesReader.getRootPath(RunSc.class) + "/addons"; 35 | } 36 | AddonsLoader addonsLoader = new AddonsLoader(addonsPath); 37 | Class clazz = addonsLoader.loadClass(config.type); 38 | yield (ClientSessionService) clazz.getConstructor(ScConfig.class).newInstance(config); 39 | } 40 | }; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /run/src/test/java/t1/ClientSessionManager.java: -------------------------------------------------------------------------------- 1 | package t1; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import lombok.extern.slf4j.Slf4j; 5 | 6 | import java.util.LinkedList; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.concurrent.ArrayBlockingQueue; 10 | import java.util.concurrent.BlockingQueue; 11 | import java.util.concurrent.ConcurrentHashMap; 12 | 13 | /** 14 | * @author liuyu 15 | * @date 2023/11/18 16 | */ 17 | @Slf4j 18 | public class ClientSessionManager { 19 | public static final Map clientSessionMap = new ConcurrentHashMap<>(); 20 | public static final BlockingQueue clientSessionSendQueue = new ArrayBlockingQueue<>(100000); 21 | 22 | private static List closedClientSessions = new LinkedList<>(); 23 | 24 | private static final Object closeLock = new Object(); 25 | 26 | public static ClientSession createClientSession(int sessionId, ClientPort clientPort, ChannelHandlerContext channelHandlerContext) { 27 | ClientSession clientSession = clientPort.createClientSession(sessionId, channelHandlerContext); 28 | clientSessionMap.put(sessionId, clientSession); 29 | return clientSession; 30 | } 31 | 32 | public static void disposeClientSession(ClientSession clientSession, String type) { 33 | clientSession.close(); 34 | log.info("ClientSession {} close,type [{}]", clientSession.getSessionId(), type); 35 | clientSessionMap.remove(clientSession.getSessionId()); 36 | synchronized (closeLock) { 37 | closedClientSessions.add(clientSession.getSessionId()); 38 | } 39 | } 40 | 41 | public static List fetchClosedClientSessions() { 42 | if (closedClientSessions.isEmpty()) { 43 | return null; 44 | } 45 | List res; 46 | synchronized (closeLock) { 47 | res = List.copyOf(closedClientSessions); 48 | closedClientSessions = new LinkedList<>(); 49 | } 50 | return res; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/common/util/ResourcesReader.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.common.util; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.io.InputStream; 6 | import java.net.URISyntaxException; 7 | import java.nio.charset.StandardCharsets; 8 | import java.nio.file.Files; 9 | import java.nio.file.Paths; 10 | 11 | /** 12 | * @author liuyu 13 | * @date 2024/8/7 14 | */ 15 | public class ResourcesReader { 16 | /** 17 | * 获得项目根路径 18 | * 19 | * @param clazz 定位用的类 20 | * @return 21 | */ 22 | public static String getRootPath(Class clazz){ 23 | File currentFile; 24 | try { 25 | currentFile = new File(clazz.getProtectionDomain().getCodeSource().getLocation().toURI()); 26 | } catch (URISyntaxException e) { 27 | throw new RuntimeException(e); 28 | } 29 | String currentDir; 30 | if (currentFile.getName().endsWith("classes")) { 31 | //开发环境下,目录在target\classes下 32 | currentDir = currentFile.getPath(); 33 | }else { 34 | currentDir = currentFile.getParent(); 35 | } 36 | return currentDir; 37 | } 38 | 39 | /** 40 | * 读取类所处根目录下的文件路径加上path的文件内容为String 41 | * 42 | * @param clazz 定位用的类 43 | * @param path 类根路径下的相对路径 44 | * @return 45 | */ 46 | public static String readStr(Class clazz, String path) { 47 | try { 48 | String basePath = getRootPath(clazz); 49 | return readStr(basePath + "/" + path); 50 | } catch (Exception e) { 51 | throw new RuntimeException("读取配置文件异常", e); 52 | } 53 | } 54 | 55 | /** 56 | * 读取绝对路径下的文件的文件内容为String 57 | * 58 | * @param path 绝对路径 59 | * @return 60 | */ 61 | public static String readStr(String path) { 62 | // 读取文件内容为字符串 63 | try { 64 | return Files.readString(Paths.get(path)); 65 | } catch (IOException e) { 66 | throw new RuntimeException(e); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /run/src/test/java/t1/ServerSessionManager.java: -------------------------------------------------------------------------------- 1 | package t1; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import java.util.LinkedList; 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.concurrent.ArrayBlockingQueue; 9 | import java.util.concurrent.BlockingQueue; 10 | import java.util.concurrent.ConcurrentHashMap; 11 | 12 | /** 13 | * @author liuyu 14 | * @date 2023/11/18 15 | */ 16 | @Slf4j 17 | public class ServerSessionManager { 18 | 19 | public static final Map serverSessionMap = new ConcurrentHashMap<>(); 20 | public static final BlockingQueue serverSessionSendQueue = new ArrayBlockingQueue<>(100000); 21 | private static List closedServerSessions = new LinkedList<>(); 22 | private static final Object closeLock = new Object(); 23 | 24 | public static ServerSession createServerSession(int sessionId, ServerPort serverPort) { 25 | log.info("new ServerSession {}:{} {}", serverPort.getHost(), serverPort.getPort(), sessionId); 26 | ServerSession serverSession = new ServerSession(serverPort.getHost(), serverPort.getPort(), sessionId); 27 | serverSessionMap.put(sessionId, serverSession); 28 | return serverSession; 29 | } 30 | 31 | public static void disposeServerSession(ServerSession serverSession, String type) { 32 | serverSession.close(); 33 | log.info("serverSession {} close,type [{}]", serverSession.getSessionId(), type); 34 | serverSessionMap.remove(serverSession.getSessionId()); 35 | synchronized (closeLock) { 36 | closedServerSessions.add(serverSession.getSessionId()); 37 | } 38 | } 39 | 40 | public static List fetchClosedServerSessions() { 41 | if (closedServerSessions.isEmpty()) { 42 | return null; 43 | } 44 | List res; 45 | synchronized (closeLock) { 46 | res = List.copyOf(closedServerSessions); 47 | closedServerSessions = new LinkedList<>(); 48 | } 49 | return res; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /run/src/test/java/test/ByteArrayCompressionExample.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import java.io.ByteArrayInputStream; 4 | import java.io.ByteArrayOutputStream; 5 | import java.io.IOException; 6 | import java.util.zip.GZIPOutputStream; 7 | 8 | public class ByteArrayCompressionExample { 9 | 10 | public static void main(String[] args) { 11 | String inputString = "123."; 12 | byte[] inputBytes = inputString.getBytes(); 13 | 14 | try { 15 | // 压缩字节数组 16 | byte[] compressedBytes = compress(inputBytes); 17 | 18 | // 打印压缩前后的大小 19 | System.out.println("Original Size: " + inputBytes.length + " bytes"); 20 | System.out.println("Compressed Size: " + compressedBytes.length + " bytes"); 21 | 22 | // 解压缩字节数组 23 | byte[] decompressedBytes = decompress(compressedBytes); 24 | 25 | // 将解压缩后的字节数组转换为字符串并打印 26 | String decompressedString = new String(decompressedBytes); 27 | System.out.println("Decompressed String: " + decompressedString); 28 | } catch (IOException e) { 29 | e.printStackTrace(); 30 | } 31 | } 32 | 33 | // 使用GZIP压缩字节数组 34 | private static byte[] compress(byte[] input) throws IOException { 35 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 36 | try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream(baos)) { 37 | gzipOutputStream.write(input); 38 | } 39 | return baos.toByteArray(); 40 | } 41 | 42 | // 使用GZIP解压缩字节数组 43 | private static byte[] decompress(byte[] compressed) throws IOException { 44 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 45 | ByteArrayInputStream bais = new ByteArrayInputStream(compressed); 46 | try (java.util.zip.GZIPInputStream gzipInputStream = new java.util.zip.GZIPInputStream(bais)) { 47 | byte[] buffer = new byte[1024]; 48 | int len; 49 | while ((len = gzipInputStream.read(buffer)) > 0) { 50 | baos.write(buffer, 0, len); 51 | } 52 | } 53 | return baos.toByteArray(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /kafkademo/src/main/java/org/wowtools/hppt/kafkademo/ServerDemo.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.kafkademo; 2 | 3 | import org.wowtools.hppt.run.ss.common.ServerSessionService; 4 | import org.wowtools.hppt.run.ss.pojo.SsConfig; 5 | 6 | import java.util.ArrayList; 7 | 8 | /** 9 | * 服务端,部署在电脑B上 10 | * 11 | * @author liuyu 12 | * @date 2024/6/15 13 | */ 14 | public class ServerDemo extends ServerSessionService { 15 | //TODO 传输文件等大字节数传播的情况下,需处理kafka字节顺序消费问题 16 | /* 17 | * 注:Server类的泛型CTX用以识别客户端的唯一性,所以如果需要支持多个客户端同时访问,考虑从KafkaCtx上下手改造 18 | * 这里简单演示单个客户端的情况 19 | * */ 20 | private final KafkaCtx singleCtx = new KafkaCtx(); 21 | 22 | public ServerDemo(SsConfig ssConfig) { 23 | super(ssConfig); 24 | } 25 | 26 | 27 | private KafkaUtil.BytesFunction sendToClient; 28 | private KafkaUtil.BytesFunction clientConsumer; 29 | 30 | @Override 31 | protected void init(SsConfig ssConfig) throws Exception { 32 | //初始化时构造好向kafka生产和消费数据的工具 33 | sendToClient = KafkaUtil.buildProducer(KafkaUtil.config.serverSendTopic); 34 | 35 | clientConsumer = (bytes) -> { 36 | //消费到客户端的数据,调用receiveClientBytes方法来接收 37 | receiveClientBytes(singleCtx, bytes); 38 | }; 39 | KafkaUtil.buildConsumer("server", KafkaUtil.config.clientSendTopic, clientConsumer); 40 | } 41 | 42 | @Override 43 | protected void sendBytesToClient(KafkaCtx kafkaCtx, byte[] bytes) { 44 | sendToClient.f(bytes); 45 | } 46 | 47 | @Override 48 | protected void closeCtx(KafkaCtx kafkaCtx) throws Exception { 49 | //单个客户端的话这里没什么需要做的,多个的话可能要释放KafkaCtx里的相关资源 50 | } 51 | 52 | @Override 53 | protected void onExit() throws Exception { 54 | //TODO 关闭kafka生产者和消费者 55 | } 56 | 57 | public static void main(String[] args) throws Exception{ 58 | SsConfig cfg = new SsConfig(); 59 | SsConfig.Client client = new SsConfig.Client(); 60 | client.user = "user1"; 61 | client.password = "12345"; 62 | cfg.clients = new ArrayList<>(1); 63 | cfg.clients.add(client); 64 | ServerDemo server = new ServerDemo(cfg); 65 | server.syncStart(cfg); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /run/src/test/java/org/wowtools/hppt/common/server/ServerSessionManagerTest.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.common.server;//package org.wowtools.hppt.common.server; 2 | // 3 | // 4 | //import java.nio.charset.StandardCharsets; 5 | // 6 | //public class ServerSessionManagerTest { 7 | // 8 | // public static void main(String[] args) throws Exception { 9 | // ServerSessionManager serverSessionManager = new ServerSessionManagerBuilder().setLifecycle(new ServerSessionLifecycle() { 10 | // @Override 11 | // public void created(ServerSession serverSession) { 12 | // serverSession.sendToTarget("123".getBytes(StandardCharsets.UTF_8)); 13 | // } 14 | // 15 | // @Override 16 | // public void sendToClientBuffer(ServerSession serverSession, byte[] bytes, LoginClientService.Client client) { 17 | // System.out.println("sendToUser " + new String(bytes, StandardCharsets.UTF_8)); 18 | // } 19 | // 20 | // @Override 21 | // public void closed(ServerSession serverSession) { 22 | // System.out.println("closed"); 23 | // } 24 | // }).build(); 25 | // 26 | // new Thread(() -> { 27 | // ServerSession session = serverSessionManager.createServerSession("c1", "localhost", 10001); 28 | // try { 29 | // Thread.sleep(5000); 30 | // } catch (InterruptedException e) { 31 | // throw new RuntimeException(e); 32 | // } 33 | // session.sendToTarget("close".getBytes(StandardCharsets.UTF_8)); 34 | // }).start(); 35 | // 36 | // new Thread(() -> { 37 | // ServerSession session = serverSessionManager.createServerSession("c1", "localhost", 10001); 38 | // try { 39 | // Thread.sleep(5000); 40 | // } catch (InterruptedException e) { 41 | // throw new RuntimeException(e); 42 | // } 43 | // session.sendToTarget("close".getBytes(StandardCharsets.UTF_8)); 44 | // }).start(); 45 | // 46 | // new Thread(() -> { 47 | // ServerSession session = serverSessionManager.createServerSession("c1", "localhost", 10002); 48 | // }).start(); 49 | // } 50 | //} 51 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/common/util/FileConsumerBuffer.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.common.util; 2 | 3 | import java.io.File; 4 | import java.io.FileNotFoundException; 5 | import java.io.IOException; 6 | import java.io.RandomAccessFile; 7 | import java.nio.channels.FileChannel; 8 | import java.nio.channels.FileLock; 9 | 10 | /** 11 | * 以文件做为缓冲池的生产者-消费者模式中的消费者 12 | */ 13 | public class FileConsumerBuffer implements AutoCloseable { 14 | private final RandomAccessFile raf; 15 | private final FileChannel channel; 16 | 17 | /** 18 | * @param file 缓冲池文件 19 | */ 20 | public FileConsumerBuffer(File file) { 21 | try { 22 | raf = new RandomAccessFile(file, "rw"); 23 | channel = raf.getChannel(); 24 | } catch (FileNotFoundException e) { 25 | throw new RuntimeException(e); 26 | } 27 | } 28 | 29 | /** 30 | * 取出缓冲池中的数据 31 | * 32 | * @return bytes 33 | */ 34 | public byte[] poll() { 35 | try (FileLock fileLock = channel.lock()) { 36 | if (raf.length() == 0) { 37 | return null; 38 | } 39 | //TODO 写入的文件win-linux是空白,分析一下 40 | byte[] data; 41 | while (true) { 42 | try { 43 | raf.seek(0); 44 | int length = (int) raf.length(); 45 | if (length == 0) { 46 | return null; 47 | } 48 | data = new byte[length]; 49 | raf.readFully(data); 50 | break; 51 | } catch (Exception e) { 52 | } 53 | } 54 | 55 | 56 | long pointer = raf.getFilePointer(); 57 | byte[] remainingBytes = new byte[(int) (raf.length() - pointer)]; 58 | raf.readFully(remainingBytes); 59 | raf.setLength(0); 60 | raf.seek(0); 61 | raf.write(remainingBytes); 62 | return data; 63 | } catch (Exception e) { 64 | throw new RuntimeException(e); 65 | } 66 | } 67 | 68 | 69 | @Override 70 | public void close() throws Exception { 71 | channel.close(); 72 | raf.close(); 73 | } 74 | } 75 | 76 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/common/util/Constant.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.common.util; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; 5 | 6 | /** 7 | * @author liuyu 8 | * @date 2023/11/5 9 | */ 10 | public class Constant { 11 | public static final ObjectMapper jsonObjectMapper = new ObjectMapper(); 12 | 13 | public static final ObjectMapper ymlMapper = new ObjectMapper(new YAMLFactory()); 14 | 15 | public static final String sessionIdJoinFlag = ","; 16 | 17 | public static final String commandParamJoinFlag = "\n"; 18 | 19 | //ss端执行的命令代码 20 | public static final class SsCommands { 21 | 22 | //新建session host,port,initFlag 23 | public static final char CreateSession = '0'; 24 | 25 | //关闭Session SessionId 26 | public static final char CloseSession = '1'; 27 | 28 | //保持Session活跃 SessionId 29 | public static final char ActiveSession = '2'; 30 | 31 | //心跳消息 后面跟任意字符串 32 | public static final char Heartbeat = '3'; 33 | } 34 | 35 | //Sc端执行的命令代码 36 | public static final class ScCommands { 37 | //ServerSession就绪,Sc端需执行初始化对应ClientSession操作 sessionId,initFlag 38 | public static final char InitSession = '0'; 39 | 40 | //检查客户端的Session是否还活跃 sessionId 41 | public static final char CheckSessionActive = '1'; 42 | 43 | //关闭客户端连接 sessionId 44 | public static final char CloseSession = '2'; 45 | 46 | //心跳消息 后面跟任意字符串 47 | public static final char Heartbeat = '3'; 48 | } 49 | 50 | //Cs端执行的命令代码 51 | public static final class CsCommands { 52 | //检查客户端的Session是否还活跃 sessionId 53 | public static final char CheckSessionActive = '0'; 54 | 55 | //关闭客户端连接 sessionId 56 | public static final char CloseSession = '1'; 57 | } 58 | 59 | //cc端执行的命令代码 60 | public static final class CcCommands { 61 | //关闭Session 0逗号连接需要的SessionId 62 | public static final char CloseSession = '0'; 63 | 64 | //保持Session活跃 1逗号连接需要的SessionId 65 | public static final char ActiveSession = '1'; 66 | 67 | //新建会话 2sessionId host port 68 | public static final char CreateSession = '2'; 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /addons-kafka/src/main/java/org/wowtools/hppt/addons/kafka/KafkaServerSessionService.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.addons.kafka; 2 | 3 | import org.wowtools.hppt.run.ss.common.ServerSessionService; 4 | import org.wowtools.hppt.run.ss.pojo.SsConfig; 5 | 6 | import java.util.ArrayList; 7 | 8 | /** 9 | * 服务端,部署在电脑B上 10 | * 11 | * @author liuyu 12 | * @date 2024/6/15 13 | */ 14 | public class KafkaServerSessionService extends ServerSessionService { 15 | //TODO 传输文件等大字节数传播的情况下,需处理kafka字节顺序消费问题 16 | /* 17 | * 注:Server类的泛型CTX用以识别客户端的唯一性,所以如果需要支持多个客户端同时访问,考虑从KafkaCtx上下手改造 18 | * 这里简单演示单个客户端的情况 19 | * */ 20 | private final KafkaCtx singleCtx = new KafkaCtx(); 21 | 22 | public KafkaServerSessionService(SsConfig ssConfig) { 23 | super(ssConfig); 24 | } 25 | 26 | 27 | private KafkaUtil.BytesFunction sendToClient; 28 | private KafkaUtil.BytesFunction clientConsumer; 29 | 30 | @Override 31 | protected void init(SsConfig ssConfig) throws Exception { 32 | //初始化时构造好向kafka生产和消费数据的工具 33 | sendToClient = KafkaUtil.buildProducer(KafkaUtil.config.serverSendTopic, true); 34 | 35 | clientConsumer = (bytes) -> { 36 | //消费到客户端的数据,调用receiveClientBytes方法来接收 37 | receiveClientBytes(singleCtx, bytes); 38 | }; 39 | KafkaUtil.buildConsumer("server", KafkaUtil.config.clientSendTopic, clientConsumer, true); 40 | } 41 | 42 | @Override 43 | protected void sendBytesToClient(KafkaCtx kafkaCtx, byte[] bytes) { 44 | sendToClient.f(bytes); 45 | } 46 | 47 | @Override 48 | protected void closeCtx(KafkaCtx kafkaCtx) throws Exception { 49 | //单个客户端的话这里没什么需要做的,多个的话可能要释放KafkaCtx里的相关资源 50 | } 51 | 52 | @Override 53 | protected void onExit() throws Exception { 54 | //TODO 关闭kafka生产者和消费者 55 | } 56 | 57 | public static void main(String[] args) throws Exception { 58 | SsConfig cfg = new SsConfig(); 59 | SsConfig.Client client = new SsConfig.Client(); 60 | client.user = "user1"; 61 | client.password = "12345"; 62 | cfg.clients = new ArrayList<>(1); 63 | cfg.clients.add(client); 64 | KafkaServerSessionService server = new KafkaServerSessionService(cfg); 65 | server.syncStart(cfg); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /_doc/demo/websocket.md: -------------------------------------------------------------------------------- 1 | ## 示例1 通过http端口,websocket反向代理访问内部服务器SSH端口 2 | 3 | 假设你有一个服务器集群,仅有一个nginx提供了80/443端口对外访问(111.222.33.44:80),你想要访问集群中的应用服务器(192.168.0.2)的22端口,则可以按如下结构部署 4 | 5 | ![示例1](../img/3.jpg) 6 | 7 | 1、在集群中任一服务器上新建一个hppt目录,并上传hppt.jar、ss.yml、log4j2.xml文件到此目录下: 8 | 9 | ``` 10 | hppt 11 | - hppt.jar 12 | - ss.yml 13 | - log4j2.xml 14 | ``` 15 | 16 | 并调整ss.yml的配置信息: 17 | 18 | ```yaml 19 | type: websocket 20 | #服务端口 21 | port: 20871 22 | # 允许的客户端账号和密码 23 | clients: 24 | - user: user1 25 | password: 12345 26 | - user: user2 27 | password: 112233 28 | 29 | ``` 30 | (注:实际应用中,为了确保安全,建议把密码设置得更复杂一些) 31 | 32 | 执行如下命令运行服务端的hppt 33 | 34 | jar包运行 35 | ```shell 36 | cd hppt 37 | /bin/java -jar hppt.jar ss ss.yml 38 | ``` 39 | 40 | 41 | 在nginx上增加一段配置指向hppt 42 | 43 | ``` 44 | server { 45 | # 用https也ok的,对应修改nginx https配置即可 46 | listen 80; 47 | ... 48 | location /aaa/ { 49 | proxy_redirect off; 50 | proxy_pass http://192.168.0.1:20871/; 51 | proxy_http_version 1.1; 52 | proxy_set_header Upgrade $http_upgrade; 53 | proxy_set_header Connection "upgrade"; 54 | proxy_set_header Host $http_host; 55 | } 56 | 57 | ... 58 | ``` 59 | 60 | 随后,访问`http://111.222.33.44:80/aaa/` 能看到“not a WebSocket handshake request”字样即证明服务端部署成功。 61 | 62 | 2、自己笔记本上,新建一个hppt目录,拷贝hppt.jar、sc.yml、log4j2.xml文件到此目录下: 63 | 64 | ``` 65 | hppt 66 | - hppt.jar 67 | - sc.yml 68 | - log4j2.xml 69 | ``` 70 | 71 | 并调整sc.yml的配置信息: 72 | 73 | ```yaml 74 | # 和服务端的type保持一致 75 | type: websocket 76 | # 客户端用户名,每个sc进程用一个,不要重复 77 | clientUser: user1 78 | # 客户端密码 79 | clientPassword: 12345 80 | 81 | 82 | websocket: 83 | #服务端http地址,可以填nginx转发过的地址 84 | serverUrl: "ws://111.222.33.44:80/aaa" 85 | # 服务端http地址,不用nginx的话直接配原始的服务端端口 86 | #serverUrl: "ws://111.222.33.44:20871" 87 | forwards: 88 | # 把192.168.0.2的22端口代理到本机的10022端口 89 | - localPort: 10022 90 | remoteHost: "192.168.0.2" 91 | remotePort: 22 92 | # 同理也可以代理数据库等任意TCP端口,只要服务端的hppt所在服务器能访问到的端口都行 93 | - localPort: 10023 94 | remoteHost: "192.168.0.3" 95 | remotePort: 3306 96 | 97 | 98 | ``` 99 | 100 | 执行如下命令启动客户端的hppt 101 | 102 | jar包运行 103 | ```shell 104 | cd hppt 105 | /bin/java -jar hppt.jar sc sc.yml 106 | ``` 107 | 108 | 随后,你就可以在公司用linux连接工具访问localhost的10022端口,来登录应用服务器了 109 | -------------------------------------------------------------------------------- /run/src/test/java/test/LZ4CompressionExample.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | 4 | import net.jpountz.lz4.LZ4Compressor; 5 | import net.jpountz.lz4.LZ4Factory; 6 | import net.jpountz.lz4.LZ4FastDecompressor; 7 | 8 | import java.nio.charset.StandardCharsets; 9 | import java.util.Arrays; 10 | import java.util.Random; 11 | 12 | public class LZ4CompressionExample { 13 | 14 | public static void main(String[] args) { 15 | Random r = new Random(233); 16 | byte[] bt = new byte[1000_0000]; 17 | for (int i = 0; i < bt.length; i++) { 18 | bt[i] = (byte) (r.nextInt(255) - 128); 19 | } 20 | // 将字符串转换为字节数组 21 | byte[] inputBytes = bt; 22 | int orgLen = inputBytes.length; 23 | 24 | // 压缩字节数组 25 | byte[] compressedBytes = compress(inputBytes); 26 | 27 | // 打印压缩前后的大小 28 | System.out.println("Original Size: " + inputBytes.length + " bytes"); 29 | System.out.println("Compressed Size: " + compressedBytes.length + " bytes"); 30 | 31 | // 解压缩字节数组 32 | byte[] decompressedBytes = decompress(compressedBytes, orgLen); 33 | 34 | // 将解压缩后的字节数组转换为字符串并打印 35 | String decompressedString = new String(decompressedBytes, StandardCharsets.UTF_8); 36 | System.out.println("Decompressed String: " + decompressedString.length()); 37 | } 38 | 39 | // 使用LZ4压缩字节数组 40 | private static byte[] compress(byte[] input) { 41 | LZ4Factory lz4Factory = LZ4Factory.fastestInstance(); 42 | LZ4Compressor compressor = lz4Factory.fastCompressor(); 43 | 44 | int maxCompressedLength = compressor.maxCompressedLength(input.length); 45 | byte[] compressedOutput = new byte[maxCompressedLength]; 46 | 47 | int compressedLength = compressor.compress(input, 0, input.length, compressedOutput, 0, maxCompressedLength); 48 | 49 | // 如果需要确保压缩后的数组大小,可以使用Arrays.copyOfRange 50 | return Arrays.copyOfRange(compressedOutput, 0, compressedLength); 51 | } 52 | 53 | // 使用LZ4解压缩字节数组 54 | private static byte[] decompress(byte[] compressed, int orgLen) { 55 | LZ4Factory lz4Factory = LZ4Factory.fastestInstance(); 56 | LZ4FastDecompressor decompressor = lz4Factory.fastDecompressor(); 57 | 58 | byte[] decompressedOutput = new byte[orgLen]; 59 | 60 | decompressor.decompress(compressed, 0, decompressedOutput, 0, orgLen); 61 | return decompressedOutput; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /run/src/test/java/test/SimpleNettyServer.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import io.netty.bootstrap.ServerBootstrap; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.ChannelInitializer; 7 | import io.netty.channel.ChannelOption; 8 | import io.netty.channel.EventLoopGroup; 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.ByteToMessageDecoder; 13 | import io.netty.handler.logging.LogLevel; 14 | import io.netty.handler.logging.LoggingHandler; 15 | 16 | import java.nio.charset.StandardCharsets; 17 | import java.util.List; 18 | 19 | public class SimpleNettyServer { 20 | 21 | public static void main(String[] args) { 22 | EventLoopGroup bossGroup = new NioEventLoopGroup(1); 23 | EventLoopGroup workerGroup = new NioEventLoopGroup(); 24 | 25 | try { 26 | ServerBootstrap b = new ServerBootstrap(); 27 | b.group(bossGroup, workerGroup) 28 | .channel(NioServerSocketChannel.class) 29 | .option(ChannelOption.SO_BACKLOG, 128) 30 | .handler(new LoggingHandler(LogLevel.INFO)) 31 | .childHandler(new ChannelInitializer() { 32 | @Override 33 | protected void initChannel(SocketChannel ch) { 34 | ch.pipeline().addLast(new LoggingHandler(LogLevel.INFO)); 35 | ch.pipeline().addLast(new SimpleHandler()); 36 | } 37 | }); 38 | 39 | // 绑定端口,启动服务器 40 | b.bind(11001).sync().channel().closeFuture().sync(); 41 | } catch (InterruptedException e) { 42 | e.printStackTrace(); 43 | } finally { 44 | bossGroup.shutdownGracefully(); 45 | workerGroup.shutdownGracefully(); 46 | } 47 | } 48 | 49 | static class SimpleHandler extends ByteToMessageDecoder { 50 | 51 | @Override 52 | protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) throws Exception { 53 | int n = byteBuf.readableBytes(); 54 | byte[] bytes = new byte[n]; 55 | byteBuf.readBytes(bytes); 56 | System.out.println("~" + new String(bytes, StandardCharsets.UTF_8) + "~"); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /run/src/test/java/org/wowtools/hppt/common/util/AesCipherUtilTest.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.common.util; 2 | 3 | 4 | import javax.crypto.Cipher; 5 | import javax.crypto.SecretKey; 6 | import javax.crypto.spec.SecretKeySpec; 7 | import java.nio.charset.StandardCharsets; 8 | import java.util.Random; 9 | 10 | public class AesCipherUtilTest { 11 | 12 | public static void main(String[] args) throws Exception { 13 | System.out.println("---------------"); 14 | t2(); 15 | } 16 | 17 | private static void t1() throws Exception { 18 | 19 | SecretKey key1; 20 | { 21 | Random r = new Random(); 22 | byte[] se = new byte[16]; 23 | for (int i = 0; i < se.length; i++) { 24 | se[i] = (byte) (r.nextInt(255) - 128); 25 | } 26 | key1 = new SecretKeySpec(se, 0, se.length, "AES"); 27 | } 28 | 29 | byte[] bts = "hello".getBytes(StandardCharsets.UTF_8); 30 | byte[] r; 31 | { 32 | Cipher cipher = Cipher.getInstance("AES"); 33 | cipher.init(Cipher.ENCRYPT_MODE, key1); 34 | r = cipher.doFinal(bts); 35 | System.out.println(BytesUtil.bytes2base64(r)); 36 | } 37 | { 38 | Cipher cipher = Cipher.getInstance("AES"); 39 | cipher.init(Cipher.DECRYPT_MODE, key1); 40 | byte[] r1 = cipher.doFinal(r); 41 | System.out.println(new String(r1, StandardCharsets.UTF_8)); 42 | } 43 | } 44 | 45 | private static void t2() throws Exception { 46 | Random random = new Random(233); 47 | byte[] bts = new byte[111]; 48 | for (int i = 0; i < bts.length; i++) { 49 | bts[i] = (byte) (random.nextInt(255) - 128); 50 | } 51 | System.out.println(bts.length); 52 | AesCipherUtil aesCipherUtil = new AesCipherUtil("ioshdwi3u4y8ujs", System.currentTimeMillis()); 53 | byte[] r = aesCipherUtil.encryptor.encrypt(bts); 54 | System.out.println(r.length); 55 | // System.out.println(BytesUtil.bytes2base64(r)); 56 | 57 | r = aesCipherUtil.descriptor.decrypt(r); 58 | // System.out.println(new String(r,StandardCharsets.UTF_8)); 59 | 60 | if (bts.length != r.length) { 61 | throw new RuntimeException(); 62 | } 63 | for (int i = 0; i < bts.length; i++) { 64 | if (r[i] != bts[i]) { 65 | throw new RuntimeException(); 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/common/client/ClientSession.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.common.client; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.wowtools.hppt.common.util.BufferPool; 6 | import org.wowtools.hppt.common.util.BytesUtil; 7 | 8 | import java.util.concurrent.TimeUnit; 9 | 10 | /** 11 | * 客户端会话 12 | * 13 | * @author liuyu 14 | * @date 2023/11/17 15 | */ 16 | @Slf4j 17 | public class ClientSession { 18 | private final int sessionId; 19 | private final ChannelHandlerContext channelHandlerContext; 20 | 21 | private final BufferPool sendToUserBytesQueue = new BufferPool<>(" { 28 | while (running) { 29 | byte[] bytes = sendToUserBytesQueue.poll(10, TimeUnit.SECONDS); 30 | if (null == bytes) { 31 | continue; 32 | } 33 | bytes = lifecycle.beforeSendToUser(this, bytes); 34 | if (null != bytes) { 35 | log.debug("ClientSession {} 向用户发送字节 {}", sessionId, bytes.length); 36 | Throwable e = BytesUtil.writeToChannelHandlerContext(channelHandlerContext, bytes); 37 | if (null != e) { 38 | log.warn("向用户发送字节异常", e); 39 | close(); 40 | } else if (log.isDebugEnabled()) { 41 | log.debug("ClientSession {} 向用户发送字节完成 {}", sessionId, bytes.length); 42 | } 43 | lifecycle.afterSendToUser(this, bytes); 44 | } 45 | } 46 | log.debug("ClientSession {} 接收线程结束", sessionId); 47 | }); 48 | } 49 | 50 | /** 51 | * 发bytes到用户 52 | * 53 | * @param bytes bytes 54 | */ 55 | public void sendToUser(byte[] bytes) { 56 | sendToUserBytesQueue.add(bytes); 57 | } 58 | 59 | 60 | public int getSessionId() { 61 | return sessionId; 62 | } 63 | 64 | public ChannelHandlerContext getChannelHandlerContext() { 65 | return channelHandlerContext; 66 | } 67 | 68 | void close() { 69 | running = false; 70 | channelHandlerContext.close(); 71 | } 72 | 73 | 74 | } 75 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/common/util/NettyObjectBuilder.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.common.util; 2 | 3 | import io.netty.channel.EventLoopGroup; 4 | import io.netty.channel.ServerChannel; 5 | import io.netty.channel.nio.NioEventLoopGroup; 6 | import io.netty.channel.socket.nio.NioServerSocketChannel; 7 | import io.netty.channel.socket.nio.NioSocketChannel; 8 | import io.netty.util.NettyRuntime; 9 | import io.netty.util.ResourceLeakDetector; 10 | import io.netty.util.internal.SystemPropertyUtil; 11 | import lombok.extern.slf4j.Slf4j; 12 | 13 | import java.util.concurrent.ThreadFactory; 14 | 15 | /** 16 | * @author liuyu 17 | * @date 2024/6/26 18 | */ 19 | @Slf4j 20 | public class NettyObjectBuilder { 21 | private static final int DEFAULT_EVENT_LOOP_VIRTUAL_THREADS; 22 | 23 | static { 24 | //本项目用了虚拟线程,且写数据用了阻塞等待,所以把线程数调高一些 25 | DEFAULT_EVENT_LOOP_VIRTUAL_THREADS = Math.max(32, SystemPropertyUtil.getInt( 26 | "io.netty.eventLoopVirtualThreads", NettyRuntime.availableProcessors() * 16)); 27 | log.debug("-Dio.netty.eventLoopVirtualThreads: {}", DEFAULT_EVENT_LOOP_VIRTUAL_THREADS); 28 | 29 | ResourceLeakDetector.setLevel(DebugConfig.NettyResourceLeakDetectorLevel); 30 | log.info("NettyResourceLeakDetectorLevel :{}", DebugConfig.NettyResourceLeakDetectorLevel); 31 | } 32 | 33 | 34 | //TODO epoll+虚拟线程+高线程数 会导致最多只有CPU核心数的线程工作,原因暂不明确,先全部使用NioEventLoopGroup 35 | public static Class getServerSocketChannelClass() { 36 | return NioServerSocketChannel.class; 37 | } 38 | 39 | public static Class getSocketChannelClass() { 40 | return NioSocketChannel.class; 41 | } 42 | 43 | 44 | public static EventLoopGroup buildEventLoopGroup(int nThread) { 45 | if (nThread > 0) { 46 | return new NioEventLoopGroup(nThread); 47 | } else { 48 | return new NioEventLoopGroup(); 49 | } 50 | } 51 | 52 | public static EventLoopGroup buildEventLoopGroup() { 53 | return buildEventLoopGroup(0); 54 | } 55 | 56 | public static EventLoopGroup buildVirtualThreadEventLoopGroup(int nThread) { 57 | return new NioEventLoopGroup(nThread, new VirtualThreadFactory()); 58 | } 59 | 60 | public static EventLoopGroup buildVirtualThreadEventLoopGroup() { 61 | return buildVirtualThreadEventLoopGroup(DEFAULT_EVENT_LOOP_VIRTUAL_THREADS); 62 | } 63 | 64 | private static final class VirtualThreadFactory implements ThreadFactory { 65 | @Override 66 | public Thread newThread(Runnable r) { 67 | return Thread.ofVirtual().unstarted(r); 68 | } 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/common/pojo/BytesList.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.common.pojo; 2 | 3 | import com.google.protobuf.ByteString; 4 | import com.google.protobuf.InvalidProtocolBufferException; 5 | import lombok.Getter; 6 | import org.wowtools.hppt.common.protobuf.ProtoMessage; 7 | import org.wowtools.hppt.common.util.DebugConfig; 8 | 9 | import java.util.ArrayList; 10 | import java.util.Collection; 11 | import java.util.List; 12 | import java.util.concurrent.atomic.AtomicInteger; 13 | 14 | /** 15 | * @author liuyu 16 | * @date 2024/10/23 17 | */ 18 | public class BytesList { 19 | private static final AtomicInteger serialNumberBuilder; 20 | 21 | static { 22 | if (DebugConfig.OpenSerialNumber) { 23 | serialNumberBuilder = new AtomicInteger(); 24 | } else { 25 | serialNumberBuilder = null; 26 | } 27 | } 28 | 29 | private final Collection bytesCollection; 30 | private final int serialNumber; 31 | 32 | public BytesList(Collection bytesCollection) { 33 | this.bytesCollection = bytesCollection; 34 | if (!DebugConfig.OpenSerialNumber) { 35 | serialNumber = 0; 36 | } else { 37 | serialNumber = serialNumberBuilder.incrementAndGet(); 38 | } 39 | } 40 | 41 | public BytesList(byte[] pbBytes) { 42 | ProtoMessage.BytesListPb pb; 43 | try { 44 | pb = ProtoMessage.BytesListPb.parseFrom(pbBytes); 45 | } catch (InvalidProtocolBufferException e) { 46 | throw new RuntimeException(e); 47 | } 48 | List byteStringList = pb.getBytesListList(); 49 | ArrayList res = new ArrayList<>(byteStringList.size()); 50 | for (ByteString s : byteStringList) { 51 | res.add(s.toByteArray()); 52 | } 53 | this.bytesCollection = res; 54 | 55 | if (!DebugConfig.OpenSerialNumber) { 56 | serialNumber = 0; 57 | } else { 58 | serialNumber = pb.getSerialNumber(); 59 | } 60 | } 61 | 62 | public Collection getBytes(){ 63 | return bytesCollection; 64 | } 65 | 66 | public int getSerialNumber() { 67 | return serialNumber; 68 | } 69 | 70 | public ProtoMessage.BytesListPb.Builder toProto() { 71 | List byteStringList = new ArrayList<>(bytesCollection.size()); 72 | for (byte[] bytes : bytesCollection) { 73 | byteStringList.add(ByteString.copyFrom(bytes)); 74 | } 75 | ProtoMessage.BytesListPb.Builder builder = ProtoMessage.BytesListPb.newBuilder(); 76 | builder.addAllBytesList(byteStringList); 77 | return builder; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/common/pojo/SessionBytes.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.common.pojo; 2 | 3 | import com.google.protobuf.ByteString; 4 | import com.google.protobuf.InvalidProtocolBufferException; 5 | import lombok.Getter; 6 | import org.wowtools.hppt.common.protobuf.ProtoMessage; 7 | import org.wowtools.hppt.common.util.DebugConfig; 8 | 9 | import java.util.concurrent.atomic.AtomicInteger; 10 | 11 | /** 12 | * session发/收的bytes,包含sessionId和具体bytes 13 | * 14 | * @author liuyu 15 | * @date 2023/11/17 16 | */ 17 | @Getter 18 | public class SessionBytes { 19 | 20 | private static final AtomicInteger serialNumberBuilder; 21 | 22 | static { 23 | if (DebugConfig.OpenSerialNumber) { 24 | serialNumberBuilder = new AtomicInteger(); 25 | } else { 26 | serialNumberBuilder = null; 27 | } 28 | } 29 | 30 | private final int sessionId; 31 | private final byte[] bytes; 32 | private final int serialNumber; 33 | 34 | public SessionBytes(int sessionId, byte[] bytes) { 35 | this.sessionId = sessionId; 36 | this.bytes = bytes; 37 | if (!DebugConfig.OpenSerialNumber) { 38 | serialNumber = 0; 39 | } else { 40 | serialNumber = serialNumberBuilder.incrementAndGet(); 41 | } 42 | } 43 | 44 | public SessionBytes(byte[] pbBytes) { 45 | ProtoMessage.BytesPb pb; 46 | try { 47 | pb = ProtoMessage.BytesPb.parseFrom(pbBytes); 48 | } catch (InvalidProtocolBufferException e) { 49 | throw new RuntimeException(e); 50 | } 51 | sessionId = pb.getSessionId(); 52 | bytes = pb.getBytes().toByteArray(); 53 | if (!DebugConfig.OpenSerialNumber) { 54 | serialNumber = 0; 55 | } else { 56 | serialNumber = pb.getSerialNumber(); 57 | } 58 | } 59 | 60 | protected SessionBytes(ProtoMessage.BytesPb pb) { 61 | sessionId = pb.getSessionId(); 62 | bytes = pb.getBytes().toByteArray(); 63 | if (!DebugConfig.OpenSerialNumber) { 64 | serialNumber = 0; 65 | } else { 66 | serialNumber = pb.getSerialNumber(); 67 | } 68 | } 69 | 70 | public int getSerialNumber() { 71 | return serialNumber; 72 | } 73 | 74 | public ProtoMessage.BytesPb.Builder toProto() { 75 | ProtoMessage.BytesPb.Builder builder = ProtoMessage.BytesPb.newBuilder() 76 | .setBytes(ByteString.copyFrom(bytes)) 77 | .setSessionId(sessionId); 78 | if (DebugConfig.OpenSerialNumber) { 79 | builder.setSerialNumber(serialNumber); 80 | } 81 | return builder; 82 | } 83 | 84 | 85 | } 86 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/common/util/DebugConfig.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.common.util; 2 | 3 | import io.netty.util.ResourceLeakDetector; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.wowtools.hppt.run.Run; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | /** 11 | * 调试配置信息 12 | * 13 | * @author liuyu 14 | * @date 2024/10/23 15 | */ 16 | @Slf4j 17 | public class DebugConfig { 18 | //netty内存泄露检查级别 19 | public static final ResourceLeakDetector.Level NettyResourceLeakDetectorLevel; 20 | //是否开启消息流水号 21 | public static final boolean OpenSerialNumber; 22 | 23 | //是否开启缓冲池监控 24 | public static final boolean OpenBufferPoolDetector; 25 | 26 | //缓冲池高水位线,缓冲池中元素个数超过此值且继续向其中添加要素则触发日志 27 | public static final int BufferPoolWaterline; 28 | 29 | static { 30 | ResourceLeakDetector.Level _NettyResourceLeakDetectorLevel = ResourceLeakDetector.Level.DISABLED; 31 | boolean _OpenSerialNumber = false; 32 | boolean _OpenBufferPoolDetector = false; 33 | int _BufferPoolWaterline = 1000; 34 | try { 35 | String str = ResourcesReader.readStr(Run.class, "debug.ini"); 36 | Map configs = new HashMap<>(); 37 | for (String line : str.split("\n")) { 38 | line = line.trim(); 39 | if (line.isEmpty() || line.charAt(0) == '#') { 40 | continue; 41 | } 42 | log.debug(line); 43 | String[] kv = line.split("=", 2); 44 | if (kv.length == 2) { 45 | configs.put(kv[0].trim(), kv[1].trim()); 46 | } 47 | } 48 | 49 | _NettyResourceLeakDetectorLevel = switch (configs.get("NettyResourceLeakDetectorLevel")) { 50 | case "0" -> ResourceLeakDetector.Level.DISABLED; 51 | case "1" -> ResourceLeakDetector.Level.SIMPLE; 52 | case "2" -> ResourceLeakDetector.Level.ADVANCED; 53 | case "3" -> ResourceLeakDetector.Level.PARANOID; 54 | default -> ResourceLeakDetector.Level.DISABLED; 55 | }; 56 | 57 | _OpenSerialNumber = "1".equals(configs.get("OpenSerialNumber")); 58 | 59 | _OpenBufferPoolDetector = "1".equals(configs.get("OpenBufferPoolDetector")); 60 | _BufferPoolWaterline = Integer.parseInt(configs.getOrDefault("BufferPoolWaterline", "1000")); 61 | 62 | } catch (Exception e) { 63 | log.debug("不开启调试模式 ", e); 64 | } 65 | 66 | NettyResourceLeakDetectorLevel = _NettyResourceLeakDetectorLevel; 67 | OpenSerialNumber = _OpenSerialNumber; 68 | 69 | OpenBufferPoolDetector = _OpenBufferPoolDetector; 70 | BufferPoolWaterline = _BufferPoolWaterline; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/run/sc/file/FileClientSessionService.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.run.sc.file; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.wowtools.hppt.common.util.DirChangeWatcher; 5 | import org.wowtools.hppt.common.util.FileConsumerBuffer; 6 | import org.wowtools.hppt.common.util.FileProducerBuffer; 7 | import org.wowtools.hppt.run.sc.common.ClientSessionService; 8 | import org.wowtools.hppt.run.sc.pojo.ScConfig; 9 | 10 | import java.io.File; 11 | import java.nio.file.Path; 12 | 13 | /** 14 | * 基于文件的客户端 15 | * 16 | * @author liuyu 17 | * @date 2024/7/5 18 | */ 19 | @Slf4j 20 | public class FileClientSessionService extends ClientSessionService { 21 | 22 | private final DirChangeWatcher dirChangeWatcher; 23 | private final FileProducerBuffer fileProducerBuffer; 24 | 25 | 26 | public FileClientSessionService(ScConfig config) throws Exception { 27 | super(config); 28 | File clientSendFile = new File(config.file.fileDir + "/c.bin"); 29 | File serverSendFile = new File(config.file.fileDir + "/s.bin"); 30 | FileConsumerBuffer fileConsumerBuffer = new FileConsumerBuffer(serverSendFile); 31 | fileProducerBuffer = new FileProducerBuffer(clientSendFile); 32 | 33 | Path path = serverSendFile.toPath().getFileName(); 34 | dirChangeWatcher = new DirChangeWatcher(new File(config.file.fileDir).toPath(), (f) -> { 35 | if (path.equals(f.getFileName())) { 36 | synchronized (path) { 37 | byte[] bytes; 38 | while (true) { 39 | bytes = fileConsumerBuffer.poll(); 40 | if (null == bytes) { 41 | break; 42 | } 43 | try { 44 | receiveServerBytes(bytes); 45 | } catch (Exception e) { 46 | log.info("receiveServerBytes err {}", bytes, e); 47 | } 48 | } 49 | } 50 | } 51 | }); 52 | } 53 | 54 | @Override 55 | public void connectToServer(ScConfig config, Cb cb) throws Exception { 56 | cb.end(null); 57 | } 58 | 59 | @Override 60 | public void sendBytesToServer(byte[] bytes) { 61 | while (null == fileProducerBuffer) { 62 | try { 63 | Thread.sleep(10); 64 | } catch (InterruptedException e) { 65 | throw new RuntimeException(e); 66 | } 67 | } 68 | log.debug("sendBytesToServer start {}",bytes.length); 69 | fileProducerBuffer.write(bytes); 70 | log.debug("sendBytesToServer end {}",bytes.length); 71 | } 72 | 73 | @Override 74 | protected void doClose() throws Exception { 75 | dirChangeWatcher.close(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /usbdemo/src/main/java/org/wowtools/hppt/usb/UsbExample.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.usb; 2 | 3 | import org.usb4java.*; 4 | 5 | import java.nio.ByteBuffer; 6 | 7 | public class UsbExample { 8 | public static void main(String[] args) { 9 | // 初始化 USB 上下文 10 | Context context = new Context(); 11 | int result = LibUsb.init(context); 12 | if (result != LibUsb.SUCCESS) { 13 | throw new LibUsbException("无法初始化 LibUsb", result); 14 | } 15 | 16 | // 获取设备列表 17 | DeviceList deviceList = new DeviceList(); 18 | result = LibUsb.getDeviceList(context, deviceList); 19 | if (result < 0) { 20 | throw new LibUsbException("获取设备列表失败", result); 21 | } 22 | 23 | try { 24 | // 遍历设备列表 25 | for (Device device : deviceList) { 26 | DeviceDescriptor descriptor = new DeviceDescriptor(); 27 | result = LibUsb.getDeviceDescriptor(device, descriptor); 28 | if (result != LibUsb.SUCCESS) { 29 | throw new LibUsbException("获取设备描述失败", result); 30 | } 31 | if (descriptor.idVendor() == 0X1A86) { 32 | DeviceHandle handle = new DeviceHandle(); 33 | result = LibUsb.open(device, handle); 34 | if (result != LibUsb.SUCCESS) { 35 | new LibUsbException("Unable to open device", result).printStackTrace(); 36 | continue; 37 | } 38 | System.out.println("-----------------------------------"); 39 | System.out.printf("设备 %04x:%04x (供应商ID:产品ID)%n", 40 | descriptor.idVendor(), 41 | descriptor.idProduct()); 42 | // 假设设备需要 4 字节数据 43 | ByteBuffer buffer = ByteBuffer.allocateDirect(4); 44 | buffer.putInt(0, 0x12345678); // 填充数据 45 | 46 | // 调用控制传输 47 | result = LibUsb.controlTransfer( 48 | handle, 49 | (byte) (LibUsb.REQUEST_TYPE_VENDOR | LibUsb.ENDPOINT_OUT), // 请求类型和方向 50 | (byte) 0x01, // 请求码 51 | (short) 0, // wValue 52 | (short) 0, // wIndex 53 | buffer, // 数据缓冲区 54 | 1000); // 超时时间(毫秒) 55 | if (result != LibUsb.SUCCESS) { 56 | new LibUsbException("Unable to open write", result).printStackTrace(); 57 | continue; 58 | } 59 | } 60 | 61 | 62 | } 63 | } finally { 64 | // 释放设备列表 65 | LibUsb.freeDeviceList(deviceList, true); 66 | } 67 | 68 | // 关闭 USB 上下文 69 | LibUsb.exit(context); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/common/pojo/TalkMessage.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.common.pojo; 2 | 3 | import com.google.protobuf.InvalidProtocolBufferException; 4 | import lombok.Getter; 5 | import org.wowtools.hppt.common.protobuf.ProtoMessage; 6 | import org.wowtools.hppt.common.util.DebugConfig; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.concurrent.atomic.AtomicInteger; 11 | 12 | /** 13 | * 服务端和客户端交互的消息,包含交互的SessionBytes和命令 14 | * 15 | * @author liuyu 16 | * @date 2024/10/23 17 | */ 18 | @Getter 19 | public class TalkMessage { 20 | private static final AtomicInteger serialNumberBuilder; 21 | 22 | static { 23 | if (DebugConfig.OpenSerialNumber) { 24 | serialNumberBuilder = new AtomicInteger(); 25 | } else { 26 | serialNumberBuilder = null; 27 | } 28 | } 29 | 30 | private final List sessionBytes; 31 | private final List commands; 32 | private final int serialNumber; 33 | 34 | public TalkMessage(List sessionBytes, List commands) { 35 | this.sessionBytes = sessionBytes; 36 | this.commands = commands; 37 | if (!DebugConfig.OpenSerialNumber) { 38 | serialNumber = 0; 39 | } else { 40 | serialNumber = serialNumberBuilder.incrementAndGet(); 41 | } 42 | } 43 | 44 | public TalkMessage(byte[] pbBytes) { 45 | ProtoMessage.MessagePb pb; 46 | try { 47 | pb = ProtoMessage.MessagePb.parseFrom(pbBytes); 48 | } catch (InvalidProtocolBufferException e) { 49 | throw new RuntimeException(e); 50 | } 51 | commands = pb.getCommandListList(); 52 | 53 | List bytesPbs = pb.getBytesPbListList(); 54 | sessionBytes = new ArrayList<>(bytesPbs.size()); 55 | for (ProtoMessage.BytesPb bytesPb : bytesPbs) { 56 | sessionBytes.add(new SessionBytes(bytesPb)); 57 | } 58 | 59 | 60 | if (!DebugConfig.OpenSerialNumber) { 61 | serialNumber = 0; 62 | } else { 63 | serialNumber = pb.getSerialNumber(); 64 | } 65 | } 66 | 67 | public int getSerialNumber() { 68 | return serialNumber; 69 | } 70 | 71 | public ProtoMessage.MessagePb.Builder toProto() { 72 | ProtoMessage.MessagePb.Builder builder = ProtoMessage.MessagePb.newBuilder(); 73 | if (null != commands && !commands.isEmpty()) { 74 | builder.addAllCommandList(commands); 75 | } 76 | if (null != sessionBytes && !sessionBytes.isEmpty()) { 77 | List pbs = new ArrayList<>(sessionBytes.size()); 78 | for (SessionBytes sessionByte : sessionBytes) { 79 | pbs.add(sessionByte.toProto().build()); 80 | } 81 | builder.addAllBytesPbList(pbs); 82 | } 83 | if (DebugConfig.OpenSerialNumber) { 84 | builder.setSerialNumber(serialNumber); 85 | } 86 | return builder; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/run/ss/RunSs.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.run.ss; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.wowtools.hppt.common.util.AddonsLoader; 5 | import org.wowtools.hppt.common.util.Constant; 6 | import org.wowtools.hppt.common.util.ResourcesReader; 7 | import org.wowtools.hppt.run.ss.common.ServerSessionService; 8 | import org.wowtools.hppt.run.ss.file.FileServerSessionService; 9 | import org.wowtools.hppt.run.ss.hppt.HpptServerSessionService; 10 | import org.wowtools.hppt.run.ss.pojo.SsConfig; 11 | import org.wowtools.hppt.run.ss.post.PostServerSessionService; 12 | import org.wowtools.hppt.run.ss.rhppt.RHpptServerSessionService; 13 | import org.wowtools.hppt.run.ss.rpost.RPostServerSessionService; 14 | import org.wowtools.hppt.run.ss.websocket.WebsocketServerSessionService; 15 | 16 | /** 17 | * @author liuyu 18 | * @date 2024/1/24 19 | */ 20 | @Slf4j 21 | public class RunSs { 22 | 23 | public static void main(String[] args) throws Exception { 24 | 25 | String configPath; 26 | if (args.length <= 1) { 27 | configPath = "ss.yml"; 28 | } else { 29 | configPath = args[1]; 30 | } 31 | SsConfig config; 32 | try { 33 | config = Constant.ymlMapper.readValue(ResourcesReader.readStr(RunSs.class, configPath), SsConfig.class); 34 | } catch (Exception e) { 35 | throw new RuntimeException("读取配置文件异常", e); 36 | } 37 | while (true) { 38 | ServerSessionService sessionService; 39 | log.info("type {}", config.type); 40 | sessionService = switch (config.type) { 41 | case "post" -> new PostServerSessionService(config); 42 | case "websocket" -> new WebsocketServerSessionService(config); 43 | case "hppt" -> new HpptServerSessionService(config); 44 | case "rhppt" -> new RHpptServerSessionService(config); 45 | case "rpost" -> new RPostServerSessionService(config); 46 | case "file" -> new FileServerSessionService(config); 47 | default -> { 48 | String addonsPath = config.addonsPath; 49 | if (null == addonsPath) { 50 | addonsPath = ResourcesReader.getRootPath(RunSs.class) + "/addons"; 51 | } 52 | AddonsLoader addonsLoader = new AddonsLoader(addonsPath); 53 | Class clazz = addonsLoader.loadClass(config.type); 54 | ServerSessionService in = (ServerSessionService) clazz.getConstructor(SsConfig.class).newInstance(config); 55 | log.info("自定义服务启动成功 {}", clazz); 56 | yield in; 57 | } 58 | }; 59 | sessionService.syncStart(config); 60 | 61 | log.warn("ServerSessionService exit {}", sessionService); 62 | try { 63 | Thread.sleep(10000); 64 | } catch (InterruptedException e1) { 65 | } 66 | } 67 | 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /run/src/test/java/nettytest/NettyServer.java: -------------------------------------------------------------------------------- 1 | package nettytest; 2 | 3 | import io.netty.bootstrap.ServerBootstrap; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.channel.*; 6 | import io.netty.channel.socket.SocketChannel; 7 | import io.netty.channel.socket.nio.NioServerSocketChannel; 8 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder; 9 | import io.netty.handler.codec.LengthFieldPrepender; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.wowtools.hppt.common.util.BytesUtil; 12 | import org.wowtools.hppt.common.util.NettyObjectBuilder; 13 | 14 | import java.nio.charset.StandardCharsets; 15 | 16 | @Slf4j 17 | public class NettyServer { 18 | 19 | public static void main(String[] args) throws InterruptedException { 20 | EventLoopGroup bossGroup = NettyObjectBuilder.buildEventLoopGroup(); 21 | EventLoopGroup workerGroup = NettyObjectBuilder.buildEventLoopGroup(); 22 | 23 | try { 24 | ServerBootstrap serverBootstrap = new ServerBootstrap(); 25 | serverBootstrap.group(bossGroup, workerGroup) 26 | .channel(NioServerSocketChannel.class) 27 | .childHandler(new ChannelInitializer() { 28 | @Override 29 | protected void initChannel(SocketChannel ch) { 30 | int len = 4; 31 | int maxFrameLength = (int) (Math.pow(256, len) - 1); 32 | ChannelPipeline pipeline = ch.pipeline(); 33 | pipeline.addLast(new LengthFieldBasedFrameDecoder(maxFrameLength, 0, len, 0, len)); 34 | pipeline.addLast(new LengthFieldPrepender(len)); 35 | pipeline.addLast(new MessageHandler()); 36 | } 37 | }); 38 | 39 | serverBootstrap.bind(20871).sync().channel().closeFuture().sync(); 40 | } finally { 41 | bossGroup.shutdownGracefully(); 42 | workerGroup.shutdownGracefully(); 43 | } 44 | } 45 | 46 | public static ByteBuf bytes2byteBuf(ChannelHandlerContext ctx, byte[] bytes) { 47 | ByteBuf byteBuf = ctx.alloc().buffer(bytes.length, bytes.length); 48 | byteBuf.writeBytes(bytes); 49 | return byteBuf; 50 | } 51 | 52 | private static class MessageHandler extends SimpleChannelInboundHandler { 53 | 54 | 55 | @Override 56 | protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) { 57 | // 处理接收到的消息 58 | byte[] bytes = BytesUtil.byteBuf2bytes(msg); 59 | Thread.startVirtualThread(()->{ 60 | String s = new String(bytes, StandardCharsets.UTF_8); 61 | log.info(s.split(" ")[0]); 62 | BytesUtil.writeToChannelHandlerContext(ctx, s.getBytes(StandardCharsets.UTF_8)); 63 | }); 64 | 65 | } 66 | 67 | @Override 68 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 69 | cause.printStackTrace(); 70 | ctx.close(); 71 | } 72 | } 73 | } 74 | 75 | -------------------------------------------------------------------------------- /run/src/test/java/test/TCPForwardingServer.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import io.netty.bootstrap.Bootstrap; 4 | import io.netty.bootstrap.ServerBootstrap; 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.channel.*; 7 | import io.netty.channel.nio.NioEventLoopGroup; 8 | import io.netty.channel.socket.SocketChannel; 9 | import io.netty.channel.socket.nio.NioServerSocketChannel; 10 | import io.netty.channel.socket.nio.NioSocketChannel; 11 | 12 | public class TCPForwardingServer { 13 | 14 | Bootstrap bootstrap; 15 | ServerBootstrap server; 16 | 17 | NioEventLoopGroup bossGroup; 18 | NioEventLoopGroup workGroup; 19 | 20 | public static void main(String[] args) { 21 | TCPForwardingServer TCPForwardingServer = new TCPForwardingServer(); 22 | TCPForwardingServer.init(); 23 | } 24 | 25 | class DataHandler extends ChannelInboundHandlerAdapter { 26 | 27 | private Channel channel; 28 | 29 | public DataHandler(Channel channel) { 30 | this.channel = channel; 31 | } 32 | 33 | @Override 34 | public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 35 | ByteBuf readBuffer = (ByteBuf) msg; 36 | readBuffer.retain(); 37 | channel.writeAndFlush(readBuffer); 38 | } 39 | 40 | } 41 | 42 | void init() { 43 | this.bossGroup = new NioEventLoopGroup(); 44 | this.workGroup = new NioEventLoopGroup(); 45 | this.server = new ServerBootstrap(); 46 | this.bootstrap = new Bootstrap(); 47 | bootstrap.channel(NioSocketChannel.class); 48 | bootstrap.group(bossGroup); 49 | this.server.group(bossGroup, workGroup); 50 | 51 | 52 | server.channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer( 53 | ) { 54 | @Override 55 | protected void initChannel(SocketChannel socketChannel) throws Exception { 56 | socketChannel.pipeline().addLast("serverHandler", new DataHandler(getClientChannel(socketChannel))); 57 | } 58 | }).option(ChannelOption.SO_BACKLOG, 1024) 59 | .option(ChannelOption.SO_RCVBUF, 16 * 1024); 60 | 61 | server.bind(11001).syncUninterruptibly().addListener((ChannelFutureListener) channelFuture -> { 62 | if (channelFuture.isSuccess()) { 63 | System.out.println("forward server start success"); 64 | } else { 65 | System.out.println("forward server start failed"); 66 | } 67 | }); 68 | } 69 | 70 | private Channel getClientChannel(SocketChannel ch) throws InterruptedException { 71 | this.bootstrap.handler(new ChannelInitializer() { 72 | @Override 73 | protected void initChannel(SocketChannel socketChannel) throws Exception { 74 | socketChannel.pipeline().addLast("clientHandler", new DataHandler(ch)); 75 | } 76 | }); 77 | // 目标地址 78 | ChannelFuture sync = bootstrap.connect("localhost", 7779).sync(); 79 | return sync.channel(); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/common/util/AesCipherUtil.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.common.util; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import javax.crypto.Cipher; 6 | import javax.crypto.KeyGenerator; 7 | import javax.crypto.SecretKey; 8 | import java.security.NoSuchAlgorithmException; 9 | import java.security.SecureRandom; 10 | 11 | /** 12 | * aes加解密工具 13 | * 14 | * @author liuyu 15 | * @date 2023/12/18 16 | */ 17 | @Slf4j 18 | public class AesCipherUtil { 19 | 20 | public final Encryptor encryptor; 21 | 22 | public final Descriptor descriptor; 23 | 24 | 25 | public AesCipherUtil(String strKey, long ts) { 26 | strKey = strKey + (ts / (30 * 60 * 1000)); 27 | SecretKey key = generateKey(strKey); 28 | this.encryptor = new Encryptor(key); 29 | this.descriptor = new Descriptor(key); 30 | } 31 | 32 | public AesCipherUtil(String strKey) { 33 | SecretKey key = generateKey(strKey); 34 | this.encryptor = new Encryptor(key); 35 | this.descriptor = new Descriptor(key); 36 | } 37 | 38 | /** 39 | * 加密器 40 | */ 41 | public static final class Encryptor { 42 | private final Cipher cipher; 43 | 44 | private Encryptor(SecretKey key) { 45 | try { 46 | cipher = Cipher.getInstance("AES"); 47 | cipher.init(Cipher.ENCRYPT_MODE, key); 48 | } catch (Exception e) { 49 | throw new RuntimeException(e); 50 | } 51 | } 52 | 53 | public byte[] encrypt(byte[] bytes) { 54 | try { 55 | return cipher.doFinal(bytes); 56 | } catch (Exception e) { 57 | throw new RuntimeException(e); 58 | } 59 | } 60 | 61 | } 62 | 63 | /** 64 | * 解密器 65 | */ 66 | public static final class Descriptor { 67 | private final Cipher cipher; 68 | 69 | private Descriptor(SecretKey key) { 70 | try { 71 | cipher = Cipher.getInstance("AES"); 72 | cipher.init(Cipher.DECRYPT_MODE, key); 73 | } catch (Exception e) { 74 | throw new RuntimeException(e); 75 | } 76 | } 77 | 78 | public byte[] decrypt(byte[] bytes) { 79 | try { 80 | return cipher.doFinal(bytes); 81 | } catch (Exception e) { 82 | throw new RuntimeException(e); 83 | } 84 | } 85 | 86 | } 87 | 88 | 89 | private static SecretKey generateKey(String input) { 90 | try { 91 | KeyGenerator kgen = KeyGenerator.getInstance("AES");// 创建AES的Key生产者 92 | SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG"); 93 | secureRandom.setSeed(input.getBytes()); 94 | kgen.init(128, secureRandom); 95 | 96 | SecretKey secretKey = kgen.generateKey();// 根据用户密码,生成一个密钥 97 | return secretKey; 98 | } catch (NoSuchAlgorithmException e) { 99 | throw new RuntimeException(e); 100 | } 101 | } 102 | 103 | 104 | } 105 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/run/ss/file/FileServerSessionService.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.run.ss.file; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.wowtools.hppt.common.util.DirChangeWatcher; 5 | import org.wowtools.hppt.common.util.FileConsumerBuffer; 6 | import org.wowtools.hppt.common.util.FileProducerBuffer; 7 | import org.wowtools.hppt.run.ss.common.ServerSessionService; 8 | import org.wowtools.hppt.run.ss.pojo.SsConfig; 9 | 10 | import java.io.File; 11 | import java.io.IOException; 12 | import java.nio.file.Path; 13 | 14 | /** 15 | * @author liuyu 16 | * @date 2024/7/5 17 | */ 18 | @Slf4j 19 | public class FileServerSessionService extends ServerSessionService { 20 | 21 | private final DirChangeWatcher dirChangeWatcher; 22 | private final FileProducerBuffer fileProducerBuffer; 23 | 24 | public FileServerSessionService(SsConfig ssConfig) { 25 | super(ssConfig); 26 | FileCtx fileCtx = new FileCtx();//TODO 暂时只支持一个客户端连接过来 27 | File clientSendFile = new File(ssConfig.file.fileDir + "/c.bin"); 28 | clientSendFile.delete(); 29 | try { 30 | clientSendFile.createNewFile(); 31 | } catch (IOException e) { 32 | throw new RuntimeException(e); 33 | } 34 | File serverSendFile = new File(ssConfig.file.fileDir + "/s.bin"); 35 | serverSendFile.delete(); 36 | try { 37 | serverSendFile.createNewFile(); 38 | } catch (IOException e) { 39 | throw new RuntimeException(e); 40 | } 41 | FileConsumerBuffer fileConsumerBuffer = new FileConsumerBuffer(clientSendFile); 42 | fileProducerBuffer = new FileProducerBuffer(serverSendFile); 43 | 44 | Path path = clientSendFile.toPath().getFileName(); 45 | dirChangeWatcher = new DirChangeWatcher(new File(ssConfig.file.fileDir).toPath(), (f) -> { 46 | if (path.equals(f.getFileName())) { 47 | synchronized (path) { 48 | byte[] bytes; 49 | while (true) { 50 | bytes = fileConsumerBuffer.poll(); 51 | if (null == bytes) { 52 | break; 53 | } 54 | try { 55 | receiveClientBytes(fileCtx,bytes); 56 | } catch (Exception e) { 57 | log.info("receiveClientBytes err {}", bytes, e); 58 | } 59 | } 60 | } 61 | 62 | } 63 | }); 64 | } 65 | 66 | @Override 67 | protected void init(SsConfig ssConfig) throws Exception { 68 | 69 | } 70 | 71 | @Override 72 | protected void sendBytesToClient(FileCtx fileCtx, byte[] bytes) { 73 | log.debug("sendBytesToClient start {}",bytes.length); 74 | fileProducerBuffer.write(bytes); 75 | log.debug("sendBytesToClient end {}",bytes.length); 76 | } 77 | 78 | @Override 79 | protected void closeCtx(FileCtx fileCtx) throws Exception { 80 | 81 | } 82 | 83 | @Override 84 | protected void onExit() throws Exception { 85 | dirChangeWatcher.close(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/common/util/BufferPool.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.common.util; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import java.util.LinkedList; 6 | import java.util.List; 7 | import java.util.concurrent.LinkedBlockingQueue; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | /** 11 | * 缓冲池,内置一个LinkedBlockingQueue,用以解耦生产者和消费者、缓冲数据并做监控 12 | * 13 | * @author liuyu 14 | * @date 2024/10/27 15 | */ 16 | @Slf4j 17 | public class BufferPool { 18 | private final LinkedBlockingQueue queue = new LinkedBlockingQueue<>(); 19 | 20 | private final String name; 21 | 22 | /** 23 | * @param name 缓冲池名字,为便于排查,请保证名称在业务层面的准确清晰 24 | */ 25 | public BufferPool(String name) { 26 | this.name = name; 27 | } 28 | 29 | /** 30 | * 添加 31 | * 32 | * @param t t 33 | */ 34 | public void add(T t) { 35 | if (!DebugConfig.OpenBufferPoolDetector) { 36 | queue.add(t); 37 | } else { 38 | int n = queue.size(); 39 | queue.add(t); 40 | int n1 = queue.size(); 41 | if (n < DebugConfig.BufferPoolWaterline && n1 >= DebugConfig.BufferPoolWaterline) { 42 | log.debug("{} 缓冲池高水位线: {} -> {}", name, n, n1); 43 | } 44 | } 45 | 46 | } 47 | 48 | /** 49 | * 获取,队列为空则一直阻塞等待 50 | * 51 | * @return t 52 | */ 53 | public T take() { 54 | try { 55 | return queue.take(); 56 | } catch (InterruptedException e) { 57 | throw new RuntimeException(e); 58 | } 59 | } 60 | 61 | /** 62 | * 获取,队列为空则返回null 63 | * 64 | * @return t or null 65 | */ 66 | public T poll() { 67 | return queue.poll(); 68 | } 69 | 70 | /** 71 | * 获取,队列为空则阻塞等待一段时间,超时则返回null 72 | * 73 | * @param timeout timeout 74 | * @param unit TimeUnit 75 | * @return t or null 76 | */ 77 | public T poll(long timeout, TimeUnit unit) { 78 | T t; 79 | try { 80 | t = queue.poll(timeout, unit); 81 | } catch (InterruptedException e) { 82 | return null; 83 | } 84 | return t; 85 | } 86 | 87 | /** 88 | * 获取队列中当前可用的所有元素,队列为空则阻塞等待,所以list至少会有一个元素 89 | * 90 | * @return list 91 | */ 92 | public List takeAndDrainToList() { 93 | List list = new LinkedList<>(); 94 | T t0 = take(); 95 | list.add(t0); 96 | queue.drainTo(list); 97 | return list; 98 | } 99 | 100 | /** 101 | * 获取队列中当前可用的所有元素,队列为空则返回null 102 | * 103 | * @return list 104 | */ 105 | public List drainToList() { 106 | if (queue.isEmpty()) { 107 | return null; 108 | } 109 | List list = new LinkedList<>(); 110 | queue.drainTo(list); 111 | return list; 112 | } 113 | 114 | /** 115 | * 获取队列中当前可用的所有元素添加到list中 116 | * 117 | * @param list list 118 | */ 119 | public void drainToList(List list) { 120 | queue.drainTo(list); 121 | } 122 | 123 | public boolean isEmpty() { 124 | return queue.isEmpty(); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/common/util/JsonConfig.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.common.util; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.Map; 6 | 7 | /** 8 | * 从json型配置信息中读取需要的配置 9 | * 10 | * @author liuyu 11 | * @date 2024/4/8 12 | */ 13 | public abstract class JsonConfig { 14 | 15 | public static final class MapConfig extends JsonConfig { 16 | 17 | private final Map map; 18 | 19 | public MapConfig(Map map) { 20 | HashMap _map = new HashMap<>(map.size()); 21 | map.forEach((k, v) -> { 22 | if (v instanceof Map) { 23 | _map.put(k, new MapConfig((Map) v)); 24 | } else if (v instanceof ArrayList) { 25 | _map.put(k, new ListConfig((ArrayList) v)); 26 | } else { 27 | _map.put(k, v); 28 | } 29 | }); 30 | this.map = _map; 31 | } 32 | 33 | public T value(String key) { 34 | return (T) map.get(key); 35 | } 36 | 37 | public T value(String key, T defaultValue) { 38 | return (T) map.getOrDefault(key, defaultValue); 39 | } 40 | 41 | public Map mapValue() { 42 | return map; 43 | } 44 | 45 | @Override 46 | public MapConfig map(String key) { 47 | return (MapConfig) map.get(key); 48 | } 49 | 50 | @Override 51 | public ListConfig list(String key) { 52 | return (ListConfig) map.get(key); 53 | } 54 | } 55 | 56 | public static final class ListConfig extends JsonConfig { 57 | 58 | private final ArrayList list; 59 | 60 | public ListConfig(ArrayList list) { 61 | ArrayList _list = new ArrayList<>(list); 62 | list.forEach(v -> { 63 | if (v instanceof Map) { 64 | _list.add(new MapConfig((Map) v)); 65 | } else if (v instanceof ArrayList) { 66 | _list.add(new ListConfig((ArrayList) v)); 67 | } else { 68 | _list.add(v); 69 | } 70 | }); 71 | this.list = _list; 72 | } 73 | 74 | public T valueAt(int i) { 75 | return (T) list.get(i); 76 | } 77 | 78 | public ArrayList listValue() { 79 | return list; 80 | } 81 | 82 | @Override 83 | public MapConfig map(String key) { 84 | return null; 85 | } 86 | 87 | @Override 88 | public ListConfig list(String key) { 89 | return null; 90 | } 91 | } 92 | 93 | public static MapConfig newInstance(Map config) { 94 | return new MapConfig(config); 95 | } 96 | 97 | public static ListConfig newInstance(ArrayList config) { 98 | return new ListConfig(config); 99 | } 100 | 101 | 102 | public abstract MapConfig map(String key); 103 | 104 | public abstract ListConfig list(String key); 105 | } 106 | -------------------------------------------------------------------------------- /kafkademo/src/main/java/org/wowtools/hppt/kafkademo/KafkaUtil.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.kafkademo; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.apache.kafka.clients.consumer.ConsumerRecord; 8 | import org.apache.kafka.clients.consumer.ConsumerRecords; 9 | import org.apache.kafka.clients.consumer.KafkaConsumer; 10 | import org.apache.kafka.clients.producer.KafkaProducer; 11 | import org.apache.kafka.clients.producer.Producer; 12 | import org.apache.kafka.clients.producer.ProducerRecord; 13 | import org.wowtools.hppt.common.util.ResourcesReader; 14 | 15 | import java.time.Duration; 16 | import java.util.Collections; 17 | import java.util.Properties; 18 | 19 | /** 20 | * kafka工具类 21 | * 22 | * @author liuyu 23 | * @date 2024/6/15 24 | */ 25 | @Slf4j 26 | public class KafkaUtil { 27 | public static final Config config; 28 | 29 | static { 30 | try { 31 | config = new ObjectMapper(new YAMLFactory()) 32 | .readValue(ResourcesReader.readStr(Config.class, "config.yml"), Config.class); 33 | } catch (JsonProcessingException e) { 34 | throw new RuntimeException(e); 35 | } 36 | } 37 | 38 | 39 | 40 | //基本的kafka连接配置 41 | private static Properties buildProperties() { 42 | Properties props = new Properties(); 43 | props.putAll(config.properties); 44 | return props; 45 | } 46 | 47 | @FunctionalInterface 48 | public interface BytesFunction { 49 | void f(byte[] bytes); 50 | } 51 | 52 | /** 53 | * 构造一个向指定topic发送bytes数据的工具 54 | * 55 | * @param topic 主题 56 | * @return BytesFunction 调用其f(byte[] bytes)方法发送数据 57 | */ 58 | public static BytesFunction buildProducer(String topic) { 59 | Producer producer = new KafkaProducer<>(buildProperties()); 60 | return (bytes -> { 61 | ProducerRecord record = new ProducerRecord<>(topic, bytes); 62 | producer.send(record); 63 | }); 64 | } 65 | 66 | /** 67 | * 消费kafka数据 68 | * 69 | * @param groupId 消费者组 70 | * @param topic 主题 71 | * @param cb 消费到字节时回调 72 | */ 73 | public static void buildConsumer(String groupId, String topic, BytesFunction cb) { 74 | Properties props = buildProperties(); 75 | props.put("group.id", groupId); 76 | props.put("auto.offset.reset", "latest"); 77 | KafkaConsumer consumer = new KafkaConsumer<>(props); 78 | // 订阅主题 79 | consumer.subscribe(Collections.singletonList(topic)); 80 | { 81 | ConsumerRecords records = consumer.poll(Duration.ofMillis(100)); 82 | for (ConsumerRecord record : records) { 83 | log.info("测试消费 {}", record.toString()); 84 | byte[] value = record.value(); 85 | cb.f(value); 86 | } 87 | } 88 | // 消费消息 89 | Thread.startVirtualThread(() -> { 90 | while (true) { 91 | ConsumerRecords records = consumer.poll(Duration.ofMillis(100)); 92 | for (ConsumerRecord record : records) { 93 | byte[] value = record.value(); 94 | cb.f(value); 95 | } 96 | } 97 | }); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/run/ss/rhppt/RHpptServerSessionService.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.run.ss.rhppt; 2 | 3 | import io.netty.bootstrap.Bootstrap; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.channel.*; 6 | import io.netty.channel.socket.SocketChannel; 7 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder; 8 | import io.netty.handler.codec.LengthFieldPrepender; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.wowtools.hppt.common.util.BytesUtil; 11 | import org.wowtools.hppt.common.util.NettyObjectBuilder; 12 | import org.wowtools.hppt.run.ss.common.ServerSessionService; 13 | import org.wowtools.hppt.run.ss.pojo.SsConfig; 14 | 15 | /** 16 | * @author liuyu 17 | * @date 2024/4/15 18 | */ 19 | @Slf4j 20 | public class RHpptServerSessionService extends ServerSessionService { 21 | 22 | private EventLoopGroup group; 23 | 24 | public RHpptServerSessionService(SsConfig ssConfig) { 25 | super(ssConfig); 26 | } 27 | 28 | @Override 29 | protected void init(SsConfig ssConfig) throws Exception { 30 | group = NettyObjectBuilder.buildEventLoopGroup(); 31 | try { 32 | Bootstrap bootstrap = new Bootstrap(); 33 | bootstrap.group(group) 34 | .channel(NettyObjectBuilder.getSocketChannelClass()) 35 | .handler(new ChannelInitializer() { 36 | @Override 37 | protected void initChannel(SocketChannel ch) { 38 | int len = ssConfig.rhppt.lengthFieldLength; 39 | int maxFrameLength = (int) (Math.pow(256, len) - 1); 40 | if (maxFrameLength <= 0) { 41 | maxFrameLength = Integer.MAX_VALUE; 42 | } 43 | ChannelPipeline pipeline = ch.pipeline(); 44 | pipeline.addLast(new LengthFieldBasedFrameDecoder(maxFrameLength, 0, len, 0, len)); 45 | pipeline.addLast(new LengthFieldPrepender(len)); 46 | pipeline.addLast(new MessageHandler()); 47 | } 48 | }); 49 | 50 | bootstrap.connect(ssConfig.rhppt.host, ssConfig.rhppt.port).sync(); 51 | } catch (Exception e) { 52 | group.shutdownGracefully(); 53 | throw new RuntimeException(e); 54 | } 55 | } 56 | 57 | private class MessageHandler extends SimpleChannelInboundHandler { 58 | 59 | @Override 60 | protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { 61 | // 处理接收到的消息 62 | byte[] bytes = BytesUtil.byteBuf2bytes(msg); 63 | receiveClientBytes(ctx, bytes); 64 | } 65 | 66 | @Override 67 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 68 | super.exceptionCaught(ctx, cause); 69 | exit("netty err:" + cause.getMessage()); 70 | } 71 | } 72 | 73 | @Override 74 | protected void sendBytesToClient(ChannelHandlerContext ctx, byte[] bytes) { 75 | Throwable e = BytesUtil.writeToChannelHandlerContext(ctx, bytes); 76 | if (null != e) { 77 | log.warn("sendBytesToClient err", e); 78 | ctx.close(); 79 | } 80 | } 81 | 82 | @Override 83 | protected void closeCtx(ChannelHandlerContext ctx) throws Exception { 84 | ctx.close(); 85 | exit("netty closeCtx"); 86 | } 87 | 88 | @Override 89 | public void onExit() throws Exception { 90 | Object c = group.shutdownGracefully().sync().get(); 91 | log.info("onExit end {}", c); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/common/util/DirChangeWatcher.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.common.util; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.nio.file.*; 8 | import java.util.ArrayList; 9 | import java.util.LinkedList; 10 | import java.util.List; 11 | 12 | /** 13 | * 监听某个文件夹中的文件被修改 14 | * 15 | * @author liuyu 16 | * @date 2024/6/17 17 | */ 18 | @Slf4j 19 | public class DirChangeWatcher implements AutoCloseable { 20 | 21 | @FunctionalInterface 22 | public interface Cb { 23 | void cb(Path file); 24 | } 25 | 26 | private volatile boolean running = true; 27 | private final WatchService watchService; 28 | 29 | public void close() throws Exception { 30 | running = false; 31 | watchService.close(); 32 | } 33 | 34 | /** 35 | * 监听某个文件夹中的文件被修改 36 | * 37 | * @param dir 文件夹 38 | * @param cb 文件被修改时,触发cb(file) 39 | */ 40 | public DirChangeWatcher(Path dir, Cb cb) { 41 | try { 42 | watchService = FileSystems.getDefault().newWatchService(); 43 | } catch (IOException e) { 44 | throw new RuntimeException(e); 45 | } 46 | boolean success; 47 | try { 48 | dir.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY); 49 | success = true; 50 | } catch (Exception e) { 51 | log.warn("系统或文件夹不支持文件监听,降级为每10ms轮询一次文件的方式", e); 52 | success = false; 53 | } 54 | if (success) { 55 | Thread.startVirtualThread(() -> { 56 | while (running) { 57 | WatchKey key; 58 | try { 59 | key = watchService.take(); 60 | } catch (InterruptedException e) { 61 | continue; 62 | } 63 | for (WatchEvent event : key.pollEvents()) { 64 | WatchEvent.Kind kind = event.kind(); 65 | if (kind == StandardWatchEventKinds.ENTRY_MODIFY) { 66 | Path changed = (Path) event.context(); 67 | log.debug("file change: {}", changed); 68 | cb.cb(changed); 69 | } 70 | } 71 | boolean valid = key.reset(); 72 | if (!valid) { 73 | log.debug("WatchKey has been unregistered: {}", dir); 74 | break; 75 | } 76 | } 77 | }); 78 | } else { 79 | Thread.startVirtualThread(() -> { 80 | //TODO 性能起见,这里只会监听当前已存在的文件,后面新建的文件得不到监听,当前场景下可以满足,后面有需要的话需要调整动态加入到files中 81 | ArrayList list = new ArrayList<>(); 82 | for (File file : dir.toFile().listFiles()) { 83 | if (file.isFile()) { 84 | list.add(file); 85 | } 86 | } 87 | Path[] files = new Path[list.size()]; 88 | for (int i = 0; i < list.size(); i++) { 89 | files[i] = list.get(i).toPath(); 90 | } 91 | while (running) { 92 | try { 93 | Thread.sleep(10); 94 | for (Path file : files) { 95 | cb.cb(file); 96 | } 97 | } catch (Exception e) { 98 | log.warn("监听线程异常", e); 99 | } 100 | } 101 | }); 102 | } 103 | 104 | 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /usbdemo/src/main/java/org/wowtools/hppt/usb/UsbHidCommunication.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.usb; 2 | 3 | import org.usb4java.*; 4 | 5 | import java.nio.ByteBuffer; 6 | 7 | public class UsbHidCommunication { 8 | public static void main(String[] args) { 9 | // Vendor ID 和 Product ID (根据你的设备修改) 10 | final short VENDOR_ID = (short) 0x1a86; // 替换为实际的 Vendor ID 11 | final short PRODUCT_ID = (short) 0x7523; // 替换为实际的 Product ID 12 | 13 | // 初始化 USB 上下文 14 | Context context = new Context(); 15 | int result = LibUsb.init(context); 16 | if (result != LibUsb.SUCCESS) { 17 | throw new LibUsbException("无法初始化 LibUsb", result); 18 | } 19 | 20 | try { 21 | // 查找目标设备 22 | Device device = findDevice(context, VENDOR_ID, PRODUCT_ID); 23 | if (device == null) { 24 | System.out.println("未找到目标设备"); 25 | return; 26 | } 27 | 28 | // 打开设备 29 | DeviceHandle handle = new DeviceHandle(); 30 | result = LibUsb.open(device, handle); 31 | if (result != LibUsb.SUCCESS) { 32 | throw new LibUsbException("无法打开设备", result); 33 | } 34 | 35 | try { 36 | // 声明接口编号 (通常为 0,对于 HID 设备) 37 | int interfaceNumber = 0; 38 | 39 | // 绑定设备接口 40 | result = LibUsb.claimInterface(handle, interfaceNumber); 41 | if (result != LibUsb.SUCCESS) { 42 | throw new LibUsbException("无法绑定接口", result); 43 | } 44 | 45 | try { 46 | // 发送数据到设备 (假设使用端点 0x01,长度为 8 字节) 47 | byte[] sendData = new byte[8]; 48 | sendData[0] = 0x01; // 示例数据 49 | sendData[1] = 0x02; // 示例数据 50 | ByteBuffer bb = ByteBuffer.wrap(sendData); 51 | int transferred = LibUsb.controlTransfer( 52 | handle, 53 | (byte) (LibUsb.REQUEST_TYPE_CLASS | LibUsb.RECIPIENT_INTERFACE | LibUsb.ENDPOINT_OUT), 54 | (byte) 0x09, // HID Set_Report 请求 55 | (short) 0x0200, // Report Type 和 Report ID (根据你的设备修改) 56 | (short) interfaceNumber, // 接口号 57 | bb, 58 | 1000); 59 | if (transferred < 0) { 60 | throw new LibUsbException("发送数据失败", transferred); 61 | } 62 | } finally { 63 | // 释放设备接口 64 | LibUsb.releaseInterface(handle, interfaceNumber); 65 | } 66 | } finally { 67 | // 关闭设备 68 | LibUsb.close(handle); 69 | } 70 | } finally { 71 | // 释放 USB 上下文 72 | LibUsb.exit(context); 73 | } 74 | } 75 | 76 | /** 77 | * 根据 Vendor ID 和 Product ID 查找设备 78 | */ 79 | private static Device findDevice(Context context, short vendorId, short productId) { 80 | DeviceList deviceList = new DeviceList(); 81 | int result = LibUsb.getDeviceList(context, deviceList); 82 | if (result < 0) { 83 | throw new LibUsbException("获取设备列表失败", result); 84 | } 85 | 86 | try { 87 | for (Device device : deviceList) { 88 | DeviceDescriptor descriptor = new DeviceDescriptor(); 89 | result = LibUsb.getDeviceDescriptor(device, descriptor); 90 | if (result != LibUsb.SUCCESS) { 91 | throw new LibUsbException("获取设备描述失败", result); 92 | } 93 | if (descriptor.idVendor() == vendorId && descriptor.idProduct() == productId) { 94 | return device; 95 | } 96 | } 97 | } finally { 98 | LibUsb.freeDeviceList(deviceList, true); 99 | } 100 | return null; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /kafkademo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.wowtools.hppt 8 | hppt 9 | 1.0-SNAPSHOT 10 | 11 | 12 | kafkademo 13 | 14 | 15 | 16 | org.projectlombok 17 | lombok 18 | 1.18.30 19 | 20 | 21 | org.slf4j 22 | slf4j-api 23 | 2.0.12 24 | 25 | 26 | ch.qos.logback 27 | logback-classic 28 | 1.4.11 29 | 30 | 31 | 32 | 33 | org.wowtools.hppt 34 | run 35 | 1.0-SNAPSHOT 36 | 37 | 38 | org.apache.kafka 39 | kafka-clients 40 | 3.4.0 41 | 42 | 43 | 44 | 45 | 46 | src/main/resources 47 | 48 | 49 | 50 | 51 | org.apache.maven.plugins 52 | maven-compiler-plugin 53 | 54 | ${java.version} 55 | ${java.version} 56 | UTF-8 57 | -Xlint:unchecked 58 | 59 | 60 | 61 | org.apache.maven.plugins 62 | maven-shade-plugin 63 | 64 | 65 | package 66 | 67 | shade 68 | 69 | 70 | kafkademo 71 | 72 | 73 | *:* 74 | 75 | *.yml 76 | META-INF/*.SF 77 | META-INF/*.DSA 78 | META-INF/*.RSA 79 | 80 | 81 | 82 | 83 | 85 | org.wowtools.hppt.kafkademo.ServerDemo 86 | 87 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/run/ss/rpost/RPostServerSessionService.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.run.ss.rpost; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import okhttp3.Response; 5 | import okhttp3.ResponseBody; 6 | import org.wowtools.hppt.common.util.BytesUtil; 7 | import org.wowtools.hppt.common.util.HttpUtil; 8 | import org.wowtools.hppt.run.ss.common.ServerSessionService; 9 | import org.wowtools.hppt.run.ss.pojo.SsConfig; 10 | 11 | import java.util.Collection; 12 | import java.util.LinkedList; 13 | import java.util.List; 14 | import java.util.concurrent.BlockingQueue; 15 | import java.util.concurrent.LinkedBlockingQueue; 16 | import java.util.concurrent.TimeUnit; 17 | 18 | /** 19 | * @author liuyu 20 | * @date 2024/4/16 21 | */ 22 | @Slf4j 23 | public class RPostServerSessionService extends ServerSessionService { 24 | 25 | private final BlockingQueue sendQueue = new LinkedBlockingQueue<>(); 26 | private final String sendUrl; 27 | private final String receiveUrl; 28 | 29 | 30 | private volatile boolean actived = true; 31 | 32 | 33 | public RPostServerSessionService(SsConfig ssConfig) { 34 | super(ssConfig); 35 | sendUrl = ssConfig.rpost.serverUrl + "/s"; 36 | receiveUrl = ssConfig.rpost.serverUrl + "/r"; 37 | } 38 | 39 | @Override 40 | protected void init(SsConfig ssConfig) throws Exception { 41 | startSendThread(); 42 | startReceiveThread(); 43 | } 44 | 45 | private void startSendThread() { 46 | Thread.startVirtualThread(() -> { 47 | while (actived) { 48 | try { 49 | byte[] sendBytes = sendQueue.poll(10000, TimeUnit.MINUTES); 50 | if (null == sendBytes || !actived) { 51 | continue; 52 | } 53 | List bytesList = new LinkedList<>(); 54 | bytesList.add(sendBytes); 55 | sendBytes = BytesUtil.bytesCollection2PbBytes(bytesList); 56 | try (Response ignored = HttpUtil.doPost(receiveUrl, sendBytes)) { 57 | } 58 | } catch (Exception e) { 59 | log.warn("发送线程执行异常,10秒后重启", e); 60 | try { 61 | Thread.sleep(10000); 62 | } catch (Exception ex) { 63 | } 64 | exit("发送线程执行异常"); 65 | } 66 | } 67 | }); 68 | } 69 | 70 | private void startReceiveThread() { 71 | Thread.startVirtualThread(() -> { 72 | RPostCtx rPostCtx = new RPostCtx(); 73 | while (actived) { 74 | try { 75 | byte[] responseBytes; 76 | try (Response response = HttpUtil.doPost(sendUrl, null)) { 77 | ResponseBody body = response.body(); 78 | responseBytes = null == body ? null : body.bytes(); 79 | } 80 | if (null != responseBytes && responseBytes.length > 0) { 81 | log.debug("收到服务端响应字节数 {}", responseBytes.length); 82 | Collection bytesList = BytesUtil.pbBytes2BytesList(responseBytes).getBytes(); 83 | for (byte[] bytes : bytesList) { 84 | receiveClientBytes(rPostCtx, bytes); 85 | } 86 | } 87 | } catch (Exception e) { 88 | log.warn("接收线程执行异常,10秒后重启", e); 89 | try { 90 | Thread.sleep(10000); 91 | } catch (Exception ex) { 92 | } 93 | exit("接收线程执行异常"); 94 | } 95 | } 96 | }); 97 | } 98 | 99 | @Override 100 | protected void sendBytesToClient(RPostCtx rPostCtx, byte[] bytes) { 101 | sendQueue.add(bytes); 102 | } 103 | 104 | @Override 105 | protected void closeCtx(RPostCtx rPostCtx) throws Exception { 106 | actived = false; 107 | } 108 | 109 | @Override 110 | protected void onExit() throws Exception { 111 | actived = false; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/run/ss/hppt/HpptServerSessionService.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.run.ss.hppt; 2 | 3 | import io.netty.bootstrap.ServerBootstrap; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.channel.*; 6 | import io.netty.channel.socket.SocketChannel; 7 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder; 8 | import io.netty.handler.codec.LengthFieldPrepender; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.wowtools.hppt.common.util.BytesUtil; 11 | import org.wowtools.hppt.common.util.NettyObjectBuilder; 12 | import org.wowtools.hppt.run.ss.common.ServerSessionService; 13 | import org.wowtools.hppt.run.ss.pojo.SsConfig; 14 | 15 | /** 16 | * @author liuyu 17 | * @date 2024/4/9 18 | */ 19 | @Slf4j 20 | public class HpptServerSessionService extends ServerSessionService { 21 | private EventLoopGroup bossGroup; 22 | private EventLoopGroup workerGroup; 23 | 24 | public HpptServerSessionService(SsConfig ssConfig) throws Exception { 25 | super(ssConfig); 26 | } 27 | 28 | @Override 29 | protected void init(SsConfig ssConfig) { 30 | bossGroup = NettyObjectBuilder.buildEventLoopGroup(ssConfig.hppt.bossGroupNum); 31 | workerGroup = NettyObjectBuilder.buildEventLoopGroup(ssConfig.hppt.workerGroupNum); 32 | 33 | ServerBootstrap serverBootstrap = new ServerBootstrap(); 34 | serverBootstrap.group(bossGroup, workerGroup) 35 | .channel(NettyObjectBuilder.getServerSocketChannelClass()) 36 | .childHandler(new ChannelInitializer() { 37 | @Override 38 | protected void initChannel(SocketChannel ch) { 39 | int len = ssConfig.hppt.lengthFieldLength; 40 | int maxFrameLength = (int) (Math.pow(256, len) - 1); 41 | if (maxFrameLength <= 0) { 42 | maxFrameLength = Integer.MAX_VALUE; 43 | } 44 | ChannelPipeline pipeline = ch.pipeline(); 45 | pipeline.addLast(new LengthFieldBasedFrameDecoder(maxFrameLength, 0, len, 0, len)); 46 | pipeline.addLast(new LengthFieldPrepender(len)); 47 | pipeline.addLast(new MessageHandler()); 48 | } 49 | }); 50 | 51 | serverBootstrap.bind(ssConfig.port); 52 | } 53 | 54 | @Override 55 | protected void sendBytesToClient(ChannelHandlerContext ctx, byte[] bytes) { 56 | Throwable e = BytesUtil.writeToChannelHandlerContext(ctx, bytes); 57 | if (null != e) { 58 | log.warn("sendBytesToClient err", e); 59 | ctx.close(); 60 | } 61 | } 62 | 63 | @Override 64 | protected void closeCtx(ChannelHandlerContext ctx) throws Exception { 65 | ctx.close(); 66 | } 67 | 68 | @Override 69 | public void onExit() { 70 | try { 71 | bossGroup.shutdownGracefully(); 72 | } catch (Exception e) { 73 | log.warn("bossGroup.shutdownGracefully() err", e); 74 | } 75 | try { 76 | workerGroup.shutdownGracefully(); 77 | } catch (Exception e) { 78 | log.warn("workerGroup.shutdownGracefully() err", e); 79 | } 80 | } 81 | 82 | private class MessageHandler extends SimpleChannelInboundHandler { 83 | @Override 84 | protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) { 85 | // 处理接收到的消息 86 | byte[] content = BytesUtil.byteBuf2bytes(msg); 87 | try { 88 | receiveClientBytes(ctx, content); 89 | } catch (Exception e) { 90 | log.warn("receiveClientBytes err",e); 91 | ctx.close(); 92 | } 93 | } 94 | 95 | @Override 96 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 97 | super.exceptionCaught(ctx, cause); 98 | removeCtx(ctx); 99 | } 100 | 101 | @Override 102 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 103 | super.channelInactive(ctx); 104 | removeCtx(ctx); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/common/util/HttpUtil.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.common.util; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import okhttp3.*; 5 | 6 | import javax.net.ssl.SSLContext; 7 | import javax.net.ssl.SSLSocketFactory; 8 | import javax.net.ssl.TrustManager; 9 | import javax.net.ssl.X509TrustManager; 10 | import java.io.IOException; 11 | import java.nio.charset.Charset; 12 | import java.security.KeyManagementException; 13 | import java.security.NoSuchAlgorithmException; 14 | import java.security.SecureRandom; 15 | import java.security.cert.X509Certificate; 16 | import java.util.concurrent.TimeUnit; 17 | 18 | /** 19 | * @author liuyu 20 | * @date 2023/12/22 21 | */ 22 | @Slf4j 23 | public class HttpUtil { 24 | 25 | private static final okhttp3.MediaType bytesMediaType = okhttp3.MediaType.parse("application/octet-stream"); 26 | private static final byte[] emptyBts = new byte[0]; 27 | 28 | private static final OkHttpClient okHttpClient; 29 | 30 | static { 31 | okHttpClient = new OkHttpClient.Builder() 32 | .sslSocketFactory(sslSocketFactory(), x509TrustManager()) 33 | .retryOnConnectionFailure(true) 34 | .connectionPool(pool()) 35 | .connectTimeout(60L, TimeUnit.SECONDS) 36 | .readTimeout(60L, TimeUnit.SECONDS) 37 | .writeTimeout(60L, TimeUnit.SECONDS) 38 | .hostnameVerifier((hostname, session) -> true) 39 | .build(); 40 | 41 | } 42 | 43 | 44 | public static Response doGet(String url) { 45 | Request.Builder builder = new Request.Builder(); 46 | Request request = builder.url(url).build(); 47 | return execute(request); 48 | } 49 | 50 | public static Response doPost(String url) { 51 | RequestBody body = RequestBody.create(bytesMediaType, emptyBts); 52 | Request request = new Request.Builder().url(url).post(body).build(); 53 | return execute(request); 54 | } 55 | 56 | public static Response doPost(String url, byte[] bytes) { 57 | RequestBody body = RequestBody.create(bytesMediaType, bytes == null ? emptyBts : bytes); 58 | Request request = new Request.Builder().url(url).post(body).build(); 59 | return execute(request); 60 | } 61 | 62 | 63 | public static Response execute(Request request) { 64 | java.io.InterruptedIOException interruptedIOException = null; 65 | //做一个循环防止被假唤醒打断 66 | for (int i = 0; i < 5; i++) { 67 | try { 68 | return okHttpClient.newCall(request).execute(); 69 | } catch (java.io.InterruptedIOException e) { 70 | interruptedIOException = e; 71 | try { 72 | Thread.sleep(10); 73 | } catch (Exception ex) { 74 | log.debug("发送请求sleep被打断"); 75 | } 76 | } catch (IOException e) { 77 | throw new RuntimeException(e); 78 | } 79 | } 80 | throw new RuntimeException(interruptedIOException); 81 | 82 | } 83 | 84 | 85 | private static X509TrustManager x509TrustManager() { 86 | return new X509TrustManager() { 87 | @Override 88 | public void checkClientTrusted(X509Certificate[] chain, String authType) { 89 | } 90 | 91 | @Override 92 | public void checkServerTrusted(X509Certificate[] chain, String authType) { 93 | } 94 | 95 | @Override 96 | public X509Certificate[] getAcceptedIssuers() { 97 | return new X509Certificate[0]; 98 | } 99 | }; 100 | } 101 | 102 | private static SSLSocketFactory sslSocketFactory() { 103 | try { 104 | // 信任任何链接 105 | SSLContext sslContext = SSLContext.getInstance("TLS"); 106 | sslContext.init(null, new TrustManager[]{x509TrustManager()}, new SecureRandom()); 107 | return sslContext.getSocketFactory(); 108 | } catch (NoSuchAlgorithmException | KeyManagementException e) { 109 | throw new RuntimeException(e); 110 | } 111 | } 112 | 113 | private static ConnectionPool pool() { 114 | return new ConnectionPool(5, 1L, TimeUnit.MINUTES); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /addons-kafka/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.wowtools.hppt 8 | hppt 9 | 1.0-SNAPSHOT 10 | 11 | 12 | addons-kafka 13 | 14 | 15 | 21 16 | 21 17 | UTF-8 18 | 19 | 20 | 21 | org.projectlombok 22 | lombok 23 | 1.18.30 24 | compile 25 | 26 | 27 | org.slf4j 28 | slf4j-api 29 | 2.0.12 30 | compile 31 | 32 | 33 | ch.qos.logback 34 | logback-classic 35 | 1.4.11 36 | compile 37 | 38 | 39 | 40 | 41 | org.wowtools.hppt 42 | run 43 | 1.0-SNAPSHOT 44 | compile 45 | 46 | 47 | org.apache.kafka 48 | kafka-clients 49 | 3.4.0 50 | 51 | 52 | 53 | 54 | 55 | src/main/resources 56 | 57 | 58 | 59 | 60 | org.apache.maven.plugins 61 | maven-compiler-plugin 62 | 63 | ${java.version} 64 | ${java.version} 65 | UTF-8 66 | -Xlint:unchecked 67 | 68 | 69 | 70 | org.apache.maven.plugins 71 | maven-shade-plugin 72 | 73 | 74 | package 75 | 76 | shade 77 | 78 | 79 | addons-kafka 80 | 81 | 82 | *:* 83 | 84 | *.yml 85 | addons/*.* 86 | META-INF/*.SF 87 | META-INF/*.DSA 88 | META-INF/*.RSA 89 | 90 | 91 | 92 | 93 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.wowtools.hppt 8 | hppt 9 | 1.0-SNAPSHOT 10 | pom 11 | 12 | 13 | 14 | run 15 | _localtest 16 | kafkademo 17 | usbdemo 18 | addons-kafka 19 | 20 | 21 | 22 | 21 23 | 0.10.2 24 | ${java.version} 25 | ${java.version} 26 | UTF-8 27 | 28 | 29 | 30 | 31 | 32 | com.squareup.okhttp3 33 | okhttp 34 | 3.14.9 35 | 36 | 37 | com.fasterxml.jackson.core 38 | jackson-databind 39 | 2.13.1 40 | 41 | 42 | com.fasterxml.jackson.dataformat 43 | jackson-dataformat-yaml 44 | 2.13.1 45 | 46 | 47 | org.eclipse.jetty 48 | jetty-server 49 | 11.0.15 50 | 51 | 52 | org.eclipse.jetty 53 | jetty-servlet 54 | 11.0.15 55 | 56 | 57 | org.projectlombok 58 | lombok 59 | 1.18.30 60 | 61 | 62 | org.slf4j 63 | slf4j-api 64 | 2.0.7 65 | 66 | 67 | ch.qos.logback 68 | logback-classic 69 | 1.2.3 70 | 71 | 72 | com.google.protobuf 73 | protobuf-java 74 | 3.25.1 75 | 76 | 77 | com.fasterxml.jackson.core 78 | jackson-databind 79 | 2.14.2 80 | 81 | 82 | io.netty 83 | netty-all 84 | 4.1.91.Final 85 | 86 | 87 | 88 | org.apache.logging.log4j 89 | log4j-slf4j2-impl 90 | 2.20.0 91 | 92 | 93 | 94 | org.apache.logging.log4j 95 | log4j-core 96 | 2.20.0 97 | 98 | 99 | org.wowtools 100 | catframe-common 101 | 1.4.2 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /run/src/test/java/test/SymmetricEncryptionExample.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import javax.crypto.Cipher; 4 | import javax.crypto.KeyGenerator; 5 | import javax.crypto.SecretKey; 6 | import javax.crypto.spec.SecretKeySpec; 7 | import java.io.ByteArrayInputStream; 8 | import java.io.ByteArrayOutputStream; 9 | import java.io.IOException; 10 | import java.nio.charset.StandardCharsets; 11 | import java.security.Key; 12 | import java.security.SecureRandom; 13 | import java.util.Base64; 14 | import java.util.Random; 15 | import java.util.zip.GZIPOutputStream; 16 | 17 | public class SymmetricEncryptionExample { 18 | 19 | public static void main(String[] args) { 20 | try { 21 | Random r = new Random(233); 22 | // 生成随机密钥 23 | SecretKey secretKey = generateKey("test"); 24 | String keyStr = encodeKeyToString(secretKey); 25 | System.out.println(keyStr); 26 | 27 | // 原始 28 | byte[] bt = new byte[1000_0000]; 29 | for (int i = 0; i < bt.length; i++) { 30 | bt[i] = (byte) (r.nextInt(255) - 128); 31 | } 32 | System.out.println("org len: " + bt.length); 33 | 34 | //压缩 35 | bt = compress(bt); 36 | System.out.println("gzip len: " + bt.length); 37 | 38 | // 加密 39 | bt = encrypt(bt, secretKey); 40 | System.out.println("encrypt len: " + bt.length); 41 | 42 | 43 | // 解密 44 | SecretKey secretKey1 = decodeStringToKey(keyStr); 45 | bt = decrypt(bt, secretKey1); 46 | 47 | 48 | //解压缩 49 | bt = decompress(bt); 50 | 51 | 52 | } catch (Exception e) { 53 | e.printStackTrace(); 54 | } 55 | } 56 | 57 | // 生成随机密钥 58 | private static SecretKey generateSecretKey() throws Exception { 59 | KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); 60 | keyGenerator.init(128); // 使用128位密钥 61 | return keyGenerator.generateKey(); 62 | } 63 | 64 | // 使用密钥加密数据 65 | private static byte[] encrypt(byte[] data, Key key) throws Exception { 66 | Cipher cipher = Cipher.getInstance("AES"); 67 | cipher.init(Cipher.ENCRYPT_MODE, key); 68 | return cipher.doFinal(data); 69 | } 70 | 71 | // 使用密钥解密数据 72 | private static byte[] decrypt(byte[] encryptedData, Key key) throws Exception { 73 | Cipher cipher = Cipher.getInstance("AES"); 74 | cipher.init(Cipher.DECRYPT_MODE, key); 75 | return cipher.doFinal(encryptedData); 76 | } 77 | 78 | private static SecretKey generateKey(String input) throws Exception { 79 | // input += System.currentTimeMillis()/86400000; 80 | KeyGenerator keyGenerator = KeyGenerator.getInstance("AES"); 81 | keyGenerator.init(128, new SecureRandom(input.getBytes(StandardCharsets.UTF_8))); // 使用128位密钥 82 | return keyGenerator.generateKey(); 83 | } 84 | 85 | // 将密钥转换为Base64编码的字符串 86 | private static String encodeKeyToString(Key key) { 87 | return Base64.getEncoder().encodeToString(key.getEncoded()); 88 | } 89 | 90 | // 将Base64编码的字符串转换为密钥 91 | private static SecretKey decodeStringToKey(String encodedKey) { 92 | byte[] decodedKey = Base64.getDecoder().decode(encodedKey); 93 | return new SecretKeySpec(decodedKey, 0, decodedKey.length, "AES"); 94 | } 95 | 96 | 97 | // 使用GZIP压缩字节数组 98 | private static byte[] compress(byte[] input) throws IOException { 99 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 100 | try (GZIPOutputStream gzipOutputStream = new GZIPOutputStream(baos)) { 101 | gzipOutputStream.write(input); 102 | } 103 | return baos.toByteArray(); 104 | } 105 | 106 | // 使用GZIP解压缩字节数组 107 | private static byte[] decompress(byte[] compressed) throws IOException { 108 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 109 | ByteArrayInputStream bais = new ByteArrayInputStream(compressed); 110 | try (java.util.zip.GZIPInputStream gzipInputStream = new java.util.zip.GZIPInputStream(bais)) { 111 | byte[] buffer = new byte[1024]; 112 | int len; 113 | while ((len = gzipInputStream.read(buffer)) > 0) { 114 | baos.write(buffer, 0, len); 115 | } 116 | } 117 | return baos.toByteArray(); 118 | } 119 | 120 | } 121 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/run/sc/hppt/HpptClientSessionService.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.run.sc.hppt; 2 | 3 | import io.netty.bootstrap.Bootstrap; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.channel.*; 6 | import io.netty.channel.socket.SocketChannel; 7 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder; 8 | import io.netty.handler.codec.LengthFieldPrepender; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.wowtools.hppt.common.util.BytesUtil; 11 | import org.wowtools.hppt.common.util.NettyObjectBuilder; 12 | import org.wowtools.hppt.run.sc.common.ClientSessionService; 13 | import org.wowtools.hppt.run.sc.pojo.ScConfig; 14 | 15 | /** 16 | * @author liuyu 17 | * @date 2024/3/12 18 | */ 19 | @Slf4j 20 | public class HpptClientSessionService extends ClientSessionService { 21 | 22 | 23 | private ChannelHandlerContext _ctx; 24 | 25 | public HpptClientSessionService(ScConfig config) throws Exception { 26 | super(config); 27 | } 28 | 29 | @Override 30 | public void connectToServer(ScConfig config, Cb cb) { 31 | Thread.startVirtualThread(() -> { 32 | EventLoopGroup workerGroup = NettyObjectBuilder.buildEventLoopGroup(config.hppt.workerGroupNum); 33 | try { 34 | Bootstrap bootstrap = new Bootstrap(); 35 | bootstrap.group(workerGroup) 36 | .channel(NettyObjectBuilder.getSocketChannelClass()) 37 | .handler(new ChannelInitializer() { 38 | @Override 39 | protected void initChannel(SocketChannel ch) { 40 | int len = config.hppt.lengthFieldLength; 41 | int maxFrameLength = (int) (Math.pow(256, len) - 1); 42 | if (maxFrameLength <= 0) { 43 | maxFrameLength = Integer.MAX_VALUE; 44 | } 45 | ChannelPipeline pipeline = ch.pipeline(); 46 | pipeline.addLast(new LengthFieldBasedFrameDecoder(maxFrameLength, 0, len, 0, len)); 47 | pipeline.addLast(new LengthFieldPrepender(len)); 48 | pipeline.addLast(new MessageHandler(cb)); 49 | } 50 | }); 51 | bootstrap.connect(config.hppt.host, config.hppt.port).sync().channel().closeFuture().sync(); 52 | } catch (Exception e) { 53 | log.warn("netty err", e); 54 | exit(); 55 | } finally { 56 | try { 57 | workerGroup.shutdownGracefully(); 58 | } catch (Exception e) { 59 | log.warn("workerGroup.shutdownGracefully err", e); 60 | } 61 | } 62 | }); 63 | } 64 | 65 | @Override 66 | public void sendBytesToServer(byte[] bytes) { 67 | Throwable e = BytesUtil.writeToChannelHandlerContext(_ctx, bytes); 68 | if (null != e) { 69 | log.warn("sendBytesToServer err", e); 70 | exit(); 71 | } 72 | } 73 | 74 | private class MessageHandler extends SimpleChannelInboundHandler { 75 | private final Cb cb; 76 | 77 | public MessageHandler(Cb cb) { 78 | this.cb = cb; 79 | } 80 | 81 | @Override 82 | protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { 83 | // 处理接收到的消息 84 | byte[] bytes = BytesUtil.byteBuf2bytes(msg); 85 | receiveServerBytes(bytes); 86 | } 87 | 88 | @Override 89 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 90 | super.channelActive(ctx); 91 | _ctx = ctx; 92 | cb.end(null); 93 | } 94 | 95 | @Override 96 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 97 | super.exceptionCaught(ctx, cause); 98 | exit(); 99 | } 100 | } 101 | 102 | @Override 103 | protected void doClose() throws Exception { 104 | try { 105 | _ctx.close(); 106 | } catch (Exception e) { 107 | 108 | } 109 | try { 110 | _ctx.channel().close(); 111 | } catch (Exception e) { 112 | 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/run/ss/websocket/WebsocketServerSessionService.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.run.ss.websocket; 2 | 3 | import io.netty.bootstrap.ServerBootstrap; 4 | import io.netty.channel.*; 5 | import io.netty.channel.socket.nio.NioSocketChannel; 6 | import io.netty.handler.codec.http.HttpObjectAggregator; 7 | import io.netty.handler.codec.http.HttpServerCodec; 8 | import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; 9 | import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; 10 | import io.netty.handler.stream.ChunkedWriteHandler; 11 | import lombok.extern.slf4j.Slf4j; 12 | import org.wowtools.hppt.common.util.BytesUtil; 13 | import org.wowtools.hppt.common.util.NettyObjectBuilder; 14 | import org.wowtools.hppt.run.ss.common.ServerSessionService; 15 | import org.wowtools.hppt.run.ss.pojo.SsConfig; 16 | 17 | /** 18 | * @author liuyu 19 | * @date 2024/2/7 20 | */ 21 | @Slf4j 22 | public class WebsocketServerSessionService extends ServerSessionService { 23 | private EventLoopGroup boss; 24 | private EventLoopGroup worker; 25 | 26 | public WebsocketServerSessionService(SsConfig ssConfig) throws Exception { 27 | super(ssConfig); 28 | } 29 | 30 | @Override 31 | protected void init(SsConfig ssConfig) throws Exception { 32 | log.info("*********"); 33 | boss = NettyObjectBuilder.buildEventLoopGroup(ssConfig.websocket.bossGroupNum); 34 | worker = NettyObjectBuilder.buildEventLoopGroup(ssConfig.websocket.workerGroupNum); 35 | 36 | ServerBootstrap serverBootstrap; 37 | 38 | serverBootstrap = new ServerBootstrap(); 39 | serverBootstrap 40 | .group(boss, worker) 41 | .channel(NettyObjectBuilder.getServerSocketChannelClass()) 42 | .childOption(ChannelOption.TCP_NODELAY, true) 43 | .childHandler(new ChannelInitializer() { 44 | @Override 45 | protected void initChannel(NioSocketChannel ch) throws Exception { 46 | ChannelPipeline pipeline = ch.pipeline(); 47 | pipeline.addLast(new HttpServerCodec()) 48 | .addLast(new ChunkedWriteHandler()) 49 | .addLast(new HttpObjectAggregator(1024 * 1024 * 10)) 50 | .addLast(new WebSocketServerProtocolHandler("/", null, false, 1024 * 1024 * 50, false, true, 10000L)) 51 | .addLast(new MyHandler()); 52 | } 53 | } 54 | ); 55 | serverBootstrap.bind(ssConfig.port).sync(); 56 | } 57 | 58 | 59 | @ChannelHandler.Sharable 60 | private final class MyHandler extends SimpleChannelInboundHandler { 61 | 62 | @Override 63 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 64 | removeCtx(ctx); 65 | } 66 | 67 | @Override 68 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 69 | super.channelInactive(ctx); 70 | removeCtx(ctx); 71 | } 72 | 73 | @Override 74 | protected void channelRead0(ChannelHandlerContext ctx, BinaryWebSocketFrame msg) throws Exception { 75 | byte[] bytes = new byte[msg.content().readableBytes()]; 76 | msg.content().readBytes(bytes); 77 | receiveClientBytes(ctx, bytes); 78 | } 79 | } 80 | 81 | @Override 82 | protected void sendBytesToClient(ChannelHandlerContext ctx, byte[] bytes) { 83 | BinaryWebSocketFrame f = new BinaryWebSocketFrame(BytesUtil.bytes2byteBuf(ctx, bytes)); 84 | ctx.channel().writeAndFlush(f); 85 | } 86 | 87 | @Override 88 | protected void closeCtx(ChannelHandlerContext channelHandlerContext) { 89 | channelHandlerContext.close(); 90 | } 91 | 92 | @Override 93 | public void onExit() { 94 | try { 95 | boss.shutdownGracefully(); 96 | } catch (Exception e) { 97 | log.warn("boss.shutdownGracefully() err", e); 98 | } 99 | try { 100 | worker.shutdownGracefully(); 101 | } catch (Exception e) { 102 | log.warn("worker.shutdownGracefully() err", e); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /run/src/test/java/test/SimpleNettyClient.java: -------------------------------------------------------------------------------- 1 | package test; 2 | 3 | import io.netty.bootstrap.Bootstrap; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.buffer.Unpooled; 6 | import io.netty.channel.*; 7 | import io.netty.channel.nio.NioEventLoopGroup; 8 | import io.netty.channel.socket.SocketChannel; 9 | import io.netty.channel.socket.nio.NioSocketChannel; 10 | import io.netty.util.CharsetUtil; 11 | 12 | public class SimpleNettyClient { 13 | 14 | private final String host; 15 | private final int port; 16 | 17 | public SimpleNettyClient(String host, int port) { 18 | this.host = host; 19 | this.port = port; 20 | new Thread(() -> { 21 | try { 22 | run(); 23 | } catch (Exception e) { 24 | throw new RuntimeException(e); 25 | } 26 | 27 | }).start(); 28 | } 29 | 30 | private void run() throws Exception { 31 | EventLoopGroup group = new NioEventLoopGroup(); 32 | 33 | try { 34 | Bootstrap bootstrap = new Bootstrap(); 35 | bootstrap.group(group) 36 | .channel(NioSocketChannel.class) 37 | .handler(new ChannelInitializer() { 38 | @Override 39 | protected void initChannel(SocketChannel ch) { 40 | ChannelPipeline pipeline = ch.pipeline(); 41 | pipeline.addLast(new SimpleClientHandler()); 42 | } 43 | }); 44 | 45 | ChannelFuture future = bootstrap.connect(host, port).sync(); 46 | future.channel().closeFuture().sync(); 47 | } finally { 48 | group.shutdownGracefully(); 49 | } 50 | } 51 | 52 | public static void main(String[] args) throws Exception { 53 | String host = "localhost"; // 服务器地址 54 | int port = 7779; // 服务器端口 55 | SimpleNettyClient client = new SimpleNettyClient(host, port); 56 | 57 | } 58 | 59 | private static class SimpleClientHandler extends ChannelInboundHandlerAdapter { 60 | @Override 61 | public void channelActive(ChannelHandlerContext ctx) { 62 | // 发送字节到服务器 63 | new Thread(() -> { 64 | while (true) { 65 | ByteBuf message = Unpooled.copiedBuffer(send, CharsetUtil.UTF_8); 66 | ctx.writeAndFlush(message); 67 | try { 68 | Thread.sleep(2000); 69 | } catch (InterruptedException e) { 70 | throw new RuntimeException(e); 71 | } 72 | } 73 | }).start(); 74 | } 75 | 76 | @Override 77 | public void channelRead(ChannelHandlerContext ctx, Object msg) { 78 | // 打印服务器返回的字节 79 | ByteBuf byteBuf = (ByteBuf) msg; 80 | byte[] bytes = new byte[byteBuf.readableBytes()]; 81 | byteBuf.readBytes(bytes); 82 | System.out.println("Received from server: " + new String(bytes, CharsetUtil.UTF_8)); 83 | } 84 | 85 | @Override 86 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 87 | cause.printStackTrace(); 88 | ctx.close(); 89 | } 90 | 91 | 92 | } 93 | 94 | 95 | private static final String send = "GET /sc.json HTTP/1.1\n" + 96 | "Host: localhost:8080\n" + 97 | "Connection: keep-alive\n" + 98 | "Cache-Control: max-age=0\n" + 99 | "sec-ch-ua: \"Google Chrome\";v=\"119\", \"Chromium\";v=\"119\", \"Not?A_Brand\";v=\"24\"\n" + 100 | "sec-ch-ua-mobile: ?0\n" + 101 | "sec-ch-ua-platform: \"Windows\"\n" + 102 | "Upgrade-Insecure-Requests: 1\n" + 103 | "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36\n" + 104 | "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\n" + 105 | "Sec-Fetch-Site: none\n" + 106 | "Sec-Fetch-Mode: navigate\n" + 107 | "Sec-Fetch-User: ?1\n" + 108 | "Sec-Fetch-Dest: document\n" + 109 | "Accept-Encoding: gzip, deflate, br\n" + 110 | "Accept-Language: zh-CN,zh;q=0.9,en;q=0.8\n" + 111 | "Cookie: Idea-ed3c5ae4=f3af5aec-d705-4950-a081-0f5ee99b6752; Hm_lvt_a3b27d40f206a97bb91289d5044bc63a=1696816675,1697542397\n" + 112 | "\n"; 113 | } 114 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/common/server/ServerSession.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.common.server; 2 | 3 | import io.netty.channel.Channel; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.wowtools.hppt.common.pojo.SessionBytes; 6 | import org.wowtools.hppt.common.util.BufferPool; 7 | import org.wowtools.hppt.common.util.BytesUtil; 8 | import org.wowtools.hppt.common.util.DebugConfig; 9 | import org.wowtools.hppt.common.util.RoughTimeUtil; 10 | 11 | import java.util.concurrent.BlockingQueue; 12 | import java.util.concurrent.LinkedBlockingQueue; 13 | import java.util.concurrent.TimeUnit; 14 | 15 | /** 16 | * @author liuyu 17 | * @date 2023/11/17 18 | */ 19 | @Slf4j 20 | public class ServerSession { 21 | 22 | private final LoginClientService.Client client; 23 | private final Channel channel; 24 | private final int sessionId; 25 | 26 | private final long sessionTimeout; 27 | 28 | private final ServerSessionLifecycle lifecycle; 29 | 30 | private final BufferPool sendBytesQueue = new BufferPool<>(">ServerSession-sendBytesQueue"); 31 | //上次活跃时间 32 | private long activeTime; 33 | 34 | private volatile boolean running = true; 35 | 36 | 37 | ServerSession(long sessionTimeout, int sessionId, LoginClientService.Client client, ServerSessionLifecycle lifecycle, Channel channel) { 38 | this.sessionId = sessionId; 39 | this.channel = channel; 40 | this.sessionTimeout = sessionTimeout; 41 | this.lifecycle = lifecycle; 42 | this.client = client; 43 | activeSession(); 44 | startSendThread(); 45 | client.addSession(this); 46 | } 47 | 48 | private void startSendThread() { 49 | Thread.startVirtualThread(() -> { 50 | while (running) { 51 | try { 52 | SessionBytes sessionBytes = sendBytesQueue.poll(10, TimeUnit.SECONDS); 53 | if (null == sessionBytes) { 54 | continue; 55 | } 56 | if (DebugConfig.OpenSerialNumber) { 57 | log.debug("取出session待发送缓冲区数据 >sessionBytes-SerialNumber {}", sessionBytes.getSerialNumber()); 58 | } 59 | byte[] bytes = sessionBytes.getBytes(); 60 | 61 | bytes = lifecycle.beforeSendToTarget(this, bytes); 62 | 63 | if (bytes != null) { 64 | Throwable e = BytesUtil.writeToChannel(channel, bytes); 65 | if (null != e) { 66 | log.warn("BytesUtil.writeToChannel err", e); 67 | throw e; 68 | } 69 | if (log.isDebugEnabled()) { 70 | log.debug("向目标端口发送字节 {}", bytes.length); 71 | } 72 | lifecycle.afterSendToTarget(this, bytes); 73 | } 74 | } catch (Throwable e) { 75 | log.warn("SendThread err", e); 76 | close(); 77 | } 78 | } 79 | log.info("{} sendThread stop", this); 80 | }); 81 | 82 | } 83 | 84 | /** 85 | * 向目标端口发送字节 86 | * 87 | * @param bytes bytes 88 | */ 89 | public void sendToTarget(SessionBytes bytes) { 90 | activeSession(); 91 | if (bytes != null) { 92 | sendBytesQueue.add(bytes); 93 | } 94 | } 95 | 96 | /** 97 | * 保持会话活跃 98 | */ 99 | public void activeSession() { 100 | activeTime = RoughTimeUtil.getTimestamp(); 101 | } 102 | 103 | //是否需要向用户侧确认session是否存活 104 | public boolean isNeedCheckActive() { 105 | return activeTime + sessionTimeout <= RoughTimeUtil.getTimestamp(); 106 | } 107 | 108 | //session是否超时 109 | public boolean isTimeOut() { 110 | return activeTime + (sessionTimeout * 2) <= RoughTimeUtil.getTimestamp(); 111 | } 112 | 113 | public int getSessionId() { 114 | return sessionId; 115 | } 116 | 117 | 118 | void close() { 119 | running = false; 120 | channel.close(); 121 | client.removeSession(this); 122 | } 123 | 124 | @Override 125 | public String toString() { 126 | return "(" 127 | + client.clientId + " " 128 | + sessionId + (isTimeOut() ? " timeout)" : " active" + 129 | ")"); 130 | } 131 | 132 | public Channel getChannel() { 133 | return channel; 134 | } 135 | 136 | public LoginClientService.Client getClient() { 137 | return client; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/run/ss/pojo/SsConfig.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.run.ss.pojo; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import org.wowtools.hppt.common.util.CommonConfig; 5 | import org.wowtools.hppt.run.sc.pojo.ScConfig; 6 | 7 | import java.util.ArrayList; 8 | 9 | /** 10 | * @author liuyu 11 | * @date 2023/11/25 12 | */ 13 | @JsonIgnoreProperties(ignoreUnknown = true) 14 | public class SsConfig extends CommonConfig { 15 | 16 | /** 17 | * 运行类型 支持 websocket(以websocket协议传输数据)、post(以http post协议传输数据)、hppt(以hppt自定义的协议传输数据) 18 | */ 19 | public String type; 20 | 21 | 22 | /** 23 | * 插件目录,默认在根目录的addons下 24 | */ 25 | public String addonsPath; 26 | 27 | /** 28 | * 中继模式下,配置一个ScConfig,即构造一个sc转发给下一个ss 29 | */ 30 | public ScConfig relayScConfig; 31 | 32 | /** 33 | * 服务端口 34 | */ 35 | public int port; 36 | 37 | /** 38 | * 心跳超时(ms) 若此值大于0,且当服务端超过这段时间没有收到任何客户端发来的心跳包时,会执行重启操作 39 | */ 40 | public long heartbeatTimeout = -1; 41 | 42 | /** 43 | * 发起一个新连接,连接到真实端口并构建会话超时时限(ms),超时还连不上会关闭session 44 | */ 45 | public long initSessionTimeout = 30000; 46 | 47 | /** 48 | * 超过sessionTimeout,给客户端发送存活确认命令,若下一个sessionTimeout内未收到确认,则强制关闭服务 49 | */ 50 | public long sessionTimeout = 120000; 51 | 52 | /** 53 | * 接收到客户端/真实端口的数据时,数据被暂存在一个队列里,队列满后强制关闭会话 54 | */ 55 | public int messageQueueSize = 2048; 56 | 57 | /** 58 | * 每个数据包最大返回字节数,如通信协议或nginx等限制了最大包体,适当调整此值 59 | */ 60 | public long maxReturnBodySize = 10 * 1024 * 1024; 61 | 62 | /** 63 | * 生命周期实现类path,为空则使用默认 64 | */ 65 | public String lifecycle; 66 | 67 | /** 68 | * 允许的客户端 69 | */ 70 | public ArrayList clients; 71 | 72 | /** 73 | * 密码最大尝试次数,超过次数没输入对会锁定账号直至重启 74 | */ 75 | public int passwordRetryNum = 5; 76 | 77 | public static final class Client { 78 | /** 79 | * 用户名 80 | */ 81 | public String user; 82 | 83 | /** 84 | * 密码 85 | */ 86 | public String password; 87 | } 88 | 89 | public static final class PostConfig { 90 | /** 91 | * 等待真实端口返回数据的毫秒数,一般设一个略小于http服务超时时间的值 92 | */ 93 | public long waitResponseTime = 10000; 94 | 95 | /** 96 | * 回复的servlet人为设置的延迟,避免客户端过于频繁的发请求 97 | */ 98 | public long replyDelayTime = 0; 99 | 100 | /** 101 | * 服务端netty bossGroupNum 102 | */ 103 | public int bossGroupNum = 1; 104 | 105 | /** 106 | * 服务端netty workerGroupNum 107 | */ 108 | public int workerGroupNum = 0; 109 | } 110 | 111 | public PostConfig post = new PostConfig(); 112 | 113 | 114 | public static final class WebSocketConfig { 115 | /** 116 | * 服务端netty bossGroupNum 117 | */ 118 | public int bossGroupNum = 1; 119 | 120 | /** 121 | * 服务端netty workerGroupNum 122 | */ 123 | public int workerGroupNum = 0; 124 | } 125 | 126 | public WebSocketConfig websocket = new WebSocketConfig(); 127 | 128 | public static final class HpptConfig { 129 | /** 130 | * 用几个字节来作为长度位,对应最多可发送Max(256^lengthFieldLength-1,2^31-1)长度的字节,只支持1、2、3、4,服务端与客户端必须一致,默认3 131 | */ 132 | public int lengthFieldLength = 3; 133 | 134 | /** 135 | * 服务端netty bossGroupNum 136 | */ 137 | public int bossGroupNum = 1; 138 | 139 | /** 140 | * 服务端netty workerGroupNum 默认按CPU数动态计算 141 | */ 142 | public int workerGroupNum = 0; 143 | } 144 | 145 | public HpptConfig hppt = new HpptConfig(); 146 | 147 | public static final class RHpptConfig { 148 | /** 149 | * 客户端host 150 | */ 151 | public String host; 152 | /** 153 | * 客户端端口 154 | */ 155 | public int port; 156 | /** 157 | * 用几个字节来作为长度位,对应最多可发送Max(256^lengthFieldLength-1,2^31-1)长度的字节,只支持1、2、3、4,服务端与客户端必须一致,默认3 158 | */ 159 | public int lengthFieldLength = 3; 160 | } 161 | 162 | public RHpptConfig rhppt = new RHpptConfig(); 163 | 164 | public static final class RPostConfig { 165 | /** 166 | * 服务端http地址,可以填nginx转发过的地址 167 | */ 168 | public String serverUrl; 169 | } 170 | 171 | public RPostConfig rpost = new RPostConfig(); 172 | 173 | public static final class FileConfig { 174 | /** 175 | * 共享文件夹路径 176 | */ 177 | public String fileDir; 178 | 179 | } 180 | 181 | public FileConfig file = new FileConfig(); 182 | } 183 | -------------------------------------------------------------------------------- /run/src/main/java/org/wowtools/hppt/run/sc/rhppt/RHpptClientSessionService.java: -------------------------------------------------------------------------------- 1 | package org.wowtools.hppt.run.sc.rhppt; 2 | 3 | import io.netty.bootstrap.ServerBootstrap; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.channel.*; 6 | import io.netty.channel.socket.SocketChannel; 7 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder; 8 | import io.netty.handler.codec.LengthFieldPrepender; 9 | import lombok.extern.slf4j.Slf4j; 10 | import org.wowtools.hppt.common.util.BytesUtil; 11 | import org.wowtools.hppt.common.util.NettyObjectBuilder; 12 | import org.wowtools.hppt.run.sc.common.ClientSessionService; 13 | import org.wowtools.hppt.run.sc.pojo.ScConfig; 14 | 15 | /** 16 | * @author liuyu 17 | * @date 2024/4/9 18 | */ 19 | @Slf4j 20 | public class RHpptClientSessionService extends ClientSessionService { 21 | 22 | private EventLoopGroup bossGroup; 23 | private EventLoopGroup workerGroup; 24 | 25 | private ChannelHandlerContext _ctx; 26 | 27 | public RHpptClientSessionService(ScConfig config) throws Exception { 28 | super(config); 29 | } 30 | 31 | @Override 32 | public void connectToServer(ScConfig config, Cb cb) throws Exception { 33 | startServer(config, cb); 34 | } 35 | 36 | private void startServer(ScConfig config, Cb cb) throws Exception { 37 | ServerBootstrap serverBootstrap = new ServerBootstrap(); 38 | bossGroup = NettyObjectBuilder.buildEventLoopGroup(1); 39 | workerGroup = NettyObjectBuilder.buildEventLoopGroup(); 40 | serverBootstrap.group(bossGroup, workerGroup) 41 | .channel(NettyObjectBuilder.getServerSocketChannelClass()) 42 | .childHandler(new ChannelInitializer() { 43 | @Override 44 | protected void initChannel(SocketChannel ch) { 45 | int len = config.rhppt.lengthFieldLength; 46 | int maxFrameLength = (int) (Math.pow(256, len) - 1); 47 | if (maxFrameLength <= 0) { 48 | maxFrameLength = Integer.MAX_VALUE; 49 | } 50 | ChannelPipeline pipeline = ch.pipeline(); 51 | pipeline.addLast(new LengthFieldBasedFrameDecoder(maxFrameLength, 0, len, 0, len)); 52 | pipeline.addLast(new LengthFieldPrepender(len)); 53 | pipeline.addLast(new MessageHandler(cb)); 54 | } 55 | }); 56 | serverBootstrap.bind(config.rhppt.port).sync(); 57 | } 58 | 59 | @Override 60 | protected void doClose() throws Exception { 61 | if (null != _ctx) { 62 | _ctx.close(); 63 | } 64 | try { 65 | bossGroup.shutdownGracefully(); 66 | } catch (Exception e) { 67 | log.warn("bossGroup.shutdownGracefully() err", e); 68 | } 69 | try { 70 | workerGroup.shutdownGracefully(); 71 | } catch (Exception e) { 72 | log.warn("workerGroup.shutdownGracefully() err", e); 73 | } 74 | } 75 | 76 | private class MessageHandler extends SimpleChannelInboundHandler { 77 | private final Cb cb; 78 | 79 | public MessageHandler(Cb cb) { 80 | this.cb = cb; 81 | } 82 | 83 | @Override 84 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 85 | super.channelActive(ctx); 86 | _ctx = ctx; 87 | cb.end(null); 88 | } 89 | 90 | @Override 91 | protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) { 92 | // 处理接收到的消息 93 | byte[] content = BytesUtil.byteBuf2bytes(msg); 94 | try { 95 | receiveServerBytes(content); 96 | } catch (Exception e) { 97 | log.warn("接收消息异常", e); 98 | exit(); 99 | } 100 | } 101 | 102 | @Override 103 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 104 | super.exceptionCaught(ctx, cause); 105 | exit(); 106 | } 107 | 108 | @Override 109 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 110 | super.channelInactive(ctx); 111 | exit(); 112 | } 113 | } 114 | 115 | @Override 116 | public void sendBytesToServer(byte[] bytes) { 117 | Throwable e = BytesUtil.writeToChannelHandlerContext(_ctx, bytes); 118 | if (null!=e) { 119 | log.warn("sendBytesToServer err", e); 120 | exit(); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /run/src/test/java/nettytest/NettyClient.java: -------------------------------------------------------------------------------- 1 | package nettytest; 2 | 3 | import io.netty.bootstrap.Bootstrap; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.channel.*; 6 | import io.netty.channel.socket.SocketChannel; 7 | import io.netty.channel.socket.nio.NioSocketChannel; 8 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder; 9 | import io.netty.handler.codec.LengthFieldPrepender; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.wowtools.hppt.common.util.BytesUtil; 12 | import org.wowtools.hppt.common.util.NettyObjectBuilder; 13 | 14 | import java.nio.charset.StandardCharsets; 15 | import java.util.Map; 16 | import java.util.UUID; 17 | import java.util.concurrent.*; 18 | 19 | @Slf4j 20 | public class NettyClient { 21 | 22 | private static final class Rsp{ 23 | CompletableFuture future = new CompletableFuture<>(); 24 | 25 | } 26 | private static final Map callRequestMap = new ConcurrentHashMap<>(); 27 | public static void main(String[] args) throws InterruptedException { 28 | EventLoopGroup workerGroup = NettyObjectBuilder.buildEventLoopGroup(); 29 | 30 | try { 31 | Bootstrap bootstrap = new Bootstrap(); 32 | bootstrap.group(workerGroup) 33 | .channel(NioSocketChannel.class) 34 | .handler(new ChannelInitializer() { 35 | @Override 36 | protected void initChannel(SocketChannel ch) { 37 | int len = 4; 38 | int maxFrameLength = (int) (Math.pow(256, len) - 1); 39 | ChannelPipeline pipeline = ch.pipeline(); 40 | pipeline.addLast(new LengthFieldBasedFrameDecoder(maxFrameLength, 0, len, 0, len)); 41 | pipeline.addLast(new LengthFieldPrepender(len)); 42 | pipeline.addLast(new MessageHandler()); 43 | } 44 | }); 45 | 46 | bootstrap.connect("localhost", 20871).sync().channel().closeFuture().sync(); 47 | } finally { 48 | workerGroup.shutdownGracefully(); 49 | } 50 | } 51 | 52 | public static ByteBuf bytes2byteBuf(ChannelHandlerContext ctx, byte[] bytes) { 53 | ByteBuf byteBuf = ctx.alloc().buffer(bytes.length, bytes.length); 54 | byteBuf.writeBytes(bytes); 55 | return byteBuf; 56 | } 57 | 58 | private static class MessageHandler extends SimpleChannelInboundHandler { 59 | @Override 60 | protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) { 61 | // 处理接收到的消息 62 | byte[] content = BytesUtil.byteBuf2bytes(msg); 63 | Thread.startVirtualThread(()->{ 64 | int id = Integer.parseInt(new String(content, StandardCharsets.UTF_8).split(" ")[0]); 65 | log.info("rsp {}",id); 66 | Rsp rsp = callRequestMap.remove(id); 67 | if (null != rsp) { 68 | rsp.future.complete(String.valueOf(id)); 69 | synchronized (rsp) { 70 | rsp.notify(); 71 | log.info("rsp notify {}",id); 72 | } 73 | 74 | } 75 | }); 76 | } 77 | 78 | @Override 79 | public void channelActive(ChannelHandlerContext ctx) { 80 | // 发送消息到服务器 81 | Thread.startVirtualThread(()->{ 82 | for (int i = 0; i < 1000000; i++) { 83 | String msg = i+" "+ UUID.randomUUID()+UUID.randomUUID()+UUID.randomUUID()+UUID.randomUUID(); 84 | int fi = i; 85 | 86 | log.info("write {}",fi); 87 | Rsp rsp = new Rsp(); 88 | callRequestMap.put(fi, rsp); 89 | 90 | BytesUtil.writeToChannelHandlerContext(ctx, msg.getBytes(StandardCharsets.UTF_8)); 91 | 92 | try { 93 | String rr = rsp.future.get(10000, TimeUnit.MILLISECONDS); 94 | System.out.println(rr); 95 | } catch (InterruptedException e) { 96 | throw new RuntimeException(e); 97 | } catch (ExecutionException e) { 98 | throw new RuntimeException(e); 99 | } catch (TimeoutException e) { 100 | System.out.println("等待超時"); 101 | } 102 | 103 | } 104 | }); 105 | 106 | } 107 | 108 | @Override 109 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 110 | cause.printStackTrace(); 111 | ctx.close(); 112 | } 113 | } 114 | } 115 | 116 | -------------------------------------------------------------------------------- /run/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.wowtools.hppt 8 | hppt 9 | 1.0-SNAPSHOT 10 | 11 | 12 | run 13 | 14 | 15 | com.squareup.okhttp3 16 | okhttp 17 | 18 | 19 | org.lz4 20 | lz4-java 21 | 1.8.0 22 | test 23 | 24 | 25 | com.fasterxml.jackson.dataformat 26 | jackson-dataformat-yaml 27 | 28 | 29 | io.netty 30 | netty-all 31 | 32 | 33 | org.projectlombok 34 | lombok 35 | 36 | 37 | ch.qos.logback 38 | logback-classic 39 | 1.4.11 40 | 41 | 42 | com.google.protobuf 43 | protobuf-java 44 | 45 | 46 | com.fasterxml.jackson.core 47 | jackson-databind 48 | 49 | 50 | org.wowtools 51 | catframe-common 52 | 53 | 54 | 55 | 56 | 57 | src/main/resources 58 | 59 | 60 | 61 | 62 | org.apache.maven.plugins 63 | maven-compiler-plugin 64 | 65 | ${java.version} 66 | ${java.version} 67 | UTF-8 68 | -Xlint:unchecked 69 | 70 | 71 | 72 | org.apache.maven.plugins 73 | maven-shade-plugin 74 | 75 | 76 | package 77 | 78 | shade 79 | 80 | 81 | hppt 82 | 83 | 84 | *:* 85 | 86 | *.yml 87 | addons/*.* 88 | META-INF/*.SF 89 | META-INF/*.DSA 90 | META-INF/*.RSA 91 | 92 | 93 | 94 | 95 | 97 | org.wowtools.hppt.run.Run 98 | 99 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | --------------------------------------------------------------------------------