37 Commits
Author SHA1 Message Date
KNEMC 23731a4851 添加新的README.md 2026-11-07 15:25:43 +08:00
KNEMC 47c28d1d70 删除旧的README.md 2026-11-07 15:24:38 +08:00
KNEMC 1cc34ea00a 添加README.md 2026-11-07 15:23:27 +08:00
KNEMC 4f2fb8b8fb 添加README.md 2026-11-07 15:22:33 +08:00
KNEMC 189b14a743 优化内存,提高队列和线程池性能。更新JCTOOL库 2026-11-06 12:16:25 +08:00
KNEMC 3b73e2b613 减小体积 2026-11-02 16:10:32 +08:00
KNEMC ec51e5a778 修复Kperf测速工具仪表盘显示bug,优化Kperf缓存命中率 2026-10-31 17:47:08 +08:00
KNEMC c0e73bce55 内存优化,减轻GC压力 2026-10-29 13:40:01 +08:00
KNEMC b898e4c5be 修复了SRH插入失败导致无法转发的bug 2026-10-21 14:24:32 +08:00
KNEMC 61bc066f82 添加README.md 2026-10-07 12:41:52 +08:00
KNEMC 7f46c1b8c9 修复滑动窗口出现负数的bug
修复拥塞算法RTT延迟为0的bug
2026-10-07 12:35:12 +08:00
KNEMC 156d74bbe3 修复滑动窗口出现负数的bug 2026-10-07 12:34:23 +08:00
KNEMC 862b860020 修复延迟基线bug
Timer改成线程池,修复系统时间改变导致定时任务无法执行的bug
2026-09-29 20:10:57 +08:00
KNEMC 100fad7d18 cmt 2026-09-29 10:49:51 +08:00
KNEMC 6d6a199728 把NTP从平均数改成中位数,防止极端值干扰 2026-09-01 15:36:15 +08:00
KNEMC 21b975676d 现在当目的地址是直接邻居(不需要转发)时,不插入SRv6扩展头节约带宽 2026-09-01 10:21:49 +08:00
KNEMC 2c111770b3 整理SRv6Router代码,修bug,增加SRHInsert虚链路 2026-08-31 19:07:53 +08:00
SerinaNya 52f44335e4 chore(dashboard): update dashboard submodule 2026-08-31 14:40:46 +08:00
KNEMC a06e1cb5eb 修改路由表名字为ENDSID和ENDXSID 2026-08-31 14:18:34 +08:00
KNEMC 58913254e0 区分了ENDXSID和ENDSID。移除了多余的路由表对象的isLoopback(),因为是否是环回是链路的属性,不是路由表的。 2026-08-31 10:57:08 +08:00
KNEMC 02d7bcd93b update JFreeChart 2026-08-30 21:46:01 +08:00
KNEMC 1e52e82d6d Merge branch 'master' of https://git.code.cq.cn/KNEMC/KLALB
# Conflicts:
#	.classpath
#	src/org/kne/cloud/network/klalb/KLALBControllerConfigItem.java
2026-08-30 21:42:29 +08:00
KNEMC 3023fe5b2a ntp和udp URI的GUI现在可以不写协议 2026-08-30 21:40:27 +08:00
SerinaNya 7896ee6638 🙈 add omo-slim files to .gitignore 2026-08-28 17:00:40 +08:00
SerinaNya ed94423855 🚑 recover fastcopy.dll 2026-08-28 16:53:37 +08:00
SerinaNya c3172a04b1 refactor(web)!: remove obsolete interfaces endpoint 2026-08-27 23:19:53 +08:00
SerinaNya cc803f47a7 feat(web): add /api/node-info endpoint for on-demand node detail queries
- Expose GET /api/node-info?address=<ipv6> endpoint in KLALBWebServer
- Return local deviceName and deviceDescription immediately for self node
- Perform asynchronous full node-info query (NODE_INFO_FULL_REQ) via
  KLALBRoutingProtocolAPIClient for remote nodes with 3s timeout fallback
2026-08-26 21:31:48 +08:00
KNEMC 3bb95988fc 增加enableTUN checkbox 2026-08-26 21:10:41 +08:00
KNEMC e87e3bb953 remove existed klalb-config.json 2026-08-26 20:28:31 +08:00
KNEMC 41db17f1ec remove klalb-config.json 2026-08-26 20:22:23 +08:00
SerinaNya 61868fe93e feat(config): switch webPort to URI-style webListen address
- replace integer webPort with MultiProtocolSocketAddress webListen
  (default: http://0.0.0.0:4665) in controller config
- rename Swing UI item to "Web API 监听地址" and validate as full URI
- update WebServer binding to honor configured host and port
- normalize legacy webPort integers to http://0.0.0.0:<port> on load
  and keep JSON API backward compatibility
- update i18n keys in zh_CN and en_US resource bundles
- update root klalb-config.json and AGENTS.md documentation
2026-08-26 19:42:26 +08:00
SerinaNya 9cea5ed493 feat(gui): add enable-Web-API toggle and web port settings
- new "Web API settings" section after security settings in the Swing
  options panel: checkbox bound to webUI plus a port text field bound
  to webPort (empty -> default 4665; non-numeric or out of 0-65535
  shows an invalid-port warning and aborts the save)
- load handler fills both controls from the current config item
- add i18n keys to klalb_zh_CN / klalb_en_US bundles
- include previously missing webUI/webPort fields in
  KLALBControllerConfigItem equals/hashCode
2026-08-26 00:36:24 +08:00
SerinaNya e0296e0ccd feat(srv6)!: replace openlines query with tiny/full node-info API
- routing-protocol JSON API: nodeinfotinyreq/resp returns device name
  only and is never gated; nodeinfofullreq/resp returns external
  endpoints + device name + description, where denyExternalEndpointQuery
  hides only the endpoint list (name/description always answer);
  legacy openlines* wire types removed - upgrade the whole mesh together
- rename misnamed openConnections -> externalEndpoints and
  denyConnection{Query,Broadcast} -> denyExternalEndpoint{Query,Broadcast};
  legacy config keys normalized on load because gson-2.1 lacks
  @SerializedName(alternate=...)
- remove TCP-based KLALBRemoteManagement, superseded by the HTTP API
- dashboard settings follow the renamed keys; update AGENTS.md
2026-08-26 00:23:21 +08:00
SerinaNya 9cea74943c fix(web): avoid gson-2.1 members-wrapped JSON responses; update AGENTS.md
- sendJsonResponse now serializes JsonElement via toString(), since gson-2.1's
  JSON_ELEMENT factory misses JsonObject/JsonArray subclasses and falls back to
  reflection (leaking the internal "members" field into every REST response)
- Document javac full-path requirement, pre-existing compile warnings,
  frontend page data flow, and gson quirks in AGENTS.md
2026-08-24 23:59:14 +08:00
SerinaNya 35c430114e update AGENTS.md 2026-08-24 00:13:33 +08:00
SerinaNya 6c7017bc75 ✨ feat(web): integrate embedded web server, REST/SSE APIs and modernize configuration
- Add embedded KLALBWebServer with REST API, SSE streaming (200ms) and SPA hosting
- Introduce enableTUN configuration flag with fallback and non-admin execution support
- Refactor LineTable and ConnectLineTable to openConnections and autoConnections
- Rename denyLineTableQuery/Broadcast to denyConnectionQuery/Broadcast with backwards compatibility
- Support runtime config hot-reloading and automatic persistence to klalb-config.json
- Update default Web API port to 4665 and update dashboard submodule reference
2026-08-24 00:01:24 +08:00
SerinaNya 041cc31c5f 🍱 add dashboard as submodule 2026-08-23 17:42:49 +08:00
140 changed files with 4548 additions and 2238 deletions
+3 -18
View File
@@ -2,32 +2,17 @@
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="lib" path="lib/gson-2.1.jar"/>
<classpathentry kind="lib" path="lib/javassist.jar" sourcepath="C:/Users/ADMINI~1/AppData/Local/Temp/1/.org.sf.feeling.decompiler1686963856070/source/javassist-3.29.2-GA-sources.jar"/>
<classpathentry kind="lib" path="lib/ini4j.jar"/>
<classpathentry kind="lib" path="lib/JFreeChart1.5.2.jar"/>
<classpathentry kind="lib" path="lib/toml4j-0.7.1.jar"/>
<classpathentry kind="lib" path="lib/pcap4j-core-1.8.2.jar" sourcepath="C:/Users/ADMINI~1/AppData/Local/Temp/1/.org.sf.feeling.decompiler1725520274095/source/pcap4j-core-1.8.2-sources.jar"/>
<classpathentry kind="lib" path="lib/pcap4j-packetfactory-propertiesbased-1.8.2.jar"/>
<classpathentry kind="lib" path="lib/pcap4j-packetfactory-static-1.8.2.jar"/>
<classpathentry kind="lib" path="lib/pcap4j-sample-1.8.2.jar"/>
<classpathentry kind="lib" path="lib/slf4j-api-2.0.9.jar"/>
<classpathentry kind="lib" path="lib/slf4j-api-2.0.9-javadoc.jar"/>
<classpathentry kind="lib" path="lib/slf4j-api-2.0.9-sources.jar"/>
<classpathentry kind="lib" path="lib/slf4j-api-2.0.9-tests.jar"/>
<classpathentry kind="lib" path="lib/disruptor-4.0.0.jar" sourcepath="lib/disruptor-4.0.0-sources.jar"/>
<classpathentry kind="lib" path="lib/disruptor-4.0.0-javadoc.jar"/>
<classpathentry kind="lib" path="lib/disruptor-4.0.0-sources.jar"/>
<classpathentry kind="lib" path="lib/KNElib1.0.jar"/>
<classpathentry kind="lib" path="lib/KNEOptimize.jar"/>
<classpathentry kind="lib" path="lib/JavaTUN0.3.jar"/>
<classpathentry kind="lib" path="lib/uuid-creator-6.1.1.jar"/>
<classpathentry kind="lib" path="lib/jctools-core-4.0.6.jar"/>
<classpathentry kind="lib" path="lib/jctools-core-4.0.6-javadoc.jar"/>
<classpathentry kind="lib" path="lib/jctools-core-4.0.6-sources.jar"/>
<classpathentry kind="lib" path="lib/KNEOptimize.jar"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-25">
<attributes>
<attribute name="module" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="lib" path="lib/jfreechart-1.5.3.jar"/>
<classpathentry kind="lib" path="lib/jfreechart-1.5.3-sources.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
+13
View File
@@ -3,3 +3,16 @@
/klalbs4.json
/klalbs.json
/klalbs2.json
# Dashboard / Frontend
dashboard/node_modules/
dashboard/dist/
dashboard/.pnpm-store/
.pnpm-debug.log*
dashboard/.env.local
dashboard/.env.*.local
/klalb-config.json
/klalbconfig-old.json
/.slim/
+3
View File
@@ -0,0 +1,3 @@
[submodule "dashboard"]
path = dashboard
url = https://git.code.cq.cn/SerinaNya/KLALB-dashboard.git
+11
View File
@@ -0,0 +1,11 @@
<component name="libraryTable">
<library name="jctools-core-jdk11-4.0.7">
<CLASSES>
<root url="jar://$PROJECT_DIR$/lib/jctools-core-jdk11-4.0.7.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$PROJECT_DIR$/lib/jctools-core-jdk11-4.0.7-sources.jar!/" />
</SOURCES>
</library>
</component>
+38
View File
@@ -0,0 +1,38 @@
<component name="libraryTable">
<library name="jfreechart-1.5.3">
<CLASSES>
<root url="jar://$PROJECT_DIR$/lib/jfreechart-1.5.3.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/disruptor-4.0.0.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/gson-2.1.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/ini4j.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/javassist.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/JavaTUN0.3.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/jctools-core-4.0.6.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/KNElib1.0.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/KNEOptimize.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/pcap4j-core-1.8.2.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/pcap4j-packetfactory-propertiesbased-1.8.2.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/pcap4j-packetfactory-static-1.8.2.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/pcap4j-packettest-1.8.2-tests.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/pcap4j-sample-1.8.2.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/slf4j-api-2.0.9.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/slf4j-api-2.0.9-tests.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/toml4j-0.7.1.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/uuid-creator-6.1.1.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$PROJECT_DIR$/lib/jctools-core-4.0.6-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$PROJECT_DIR$/lib/jfreechart-1.5.3-sources.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/disruptor-4.0.0-sources.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/ini4j.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/JavaTUN0.3.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/jctools-core-4.0.6-sources.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/KNElib1.0.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/slf4j-api-2.0.9-sources.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/toml4j-0.7.1.jar!/" />
<root url="jar://$PROJECT_DIR$/lib/uuid-creator-6.1.1-sources.jar!/" />
</SOURCES>
</library>
</component>
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2">
<component name="ProjectRootManager" version="2" languageLevel="JDK_26" project-jdk-name="jdk-26.0.1" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/classes" />
</component>
</project>
Generated
+1
View File
@@ -2,5 +2,6 @@
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$/dashboard" vcs="Git" />
</component>
</project>
+51 -5
View File
@@ -6,12 +6,14 @@ KLALB ("KLALB Decentralized SRv6 Network") — Java load-balancing/tunnel system
No Maven/Gradle. Plain Eclipse/IntelliJ project: dependencies are vendored jars in `lib/`, output goes to `bin/` (gitignored). When adding a jar, update **both** `.classpath` and `KLALB.iml`.
Compile (verified; `-encoding UTF-8` is mandatory — sources contain Chinese text):
Compile (`javac` is NOT on PATH — use the full JDK path; `-encoding UTF-8` is mandatory because sources contain Chinese text):
```powershell
& javac -encoding UTF-8 -cp "lib/*" -d bin (Get-ChildItem -Recurse src -Filter *.java | ForEach-Object FullName)
& "C:\Program Files\Zulu\zulu-25\bin\javac.exe" -encoding UTF-8 -cp "lib/*" -d bin (Get-ChildItem -Recurse src -Filter *.java | ForEach-Object FullName)
```
Warnings about `ThreadTool` varargs / deprecated `finalize` are pre-existing and expected — success = exit code 0. After recompiling, restart the running app (IDE-debugged JVMs keep old classes).
Run from the repo root — CWD matters:
- reads `klalb-config.json` from CWD
- loads native libs from CWD: `tuntap4j.dll/.so/.dylib`, `wintun.dll`, `fastcopy.dll` (TUN device support)
@@ -26,8 +28,8 @@ IDE metadata targets JDK 26 (`jdk-26.0.1`); the tree also compiles cleanly on JD
Runtime gotchas (all verified):
- On JDK 25, `KNEOptimize.jar`'s `FastLib` reflects into `jdk.internal.misc.Unsafe`; without the two JVM flags above it throws `InaccessibleObjectException` at startup (app still runs).
- Creating the SRv6 TUN adapter (`WintunCreateAdapter`) requires an elevated shell; without admin rights it logs "创建虚拟网卡失败" and continues with only the `inLoopBack` interface — links/bridges still work.
- To disable TUN creation completely (e.g. for non-admin UI/routing testing), set `"TUNName": null` in `klalb-config.json` or leave the TUN Name empty in GUI settings. If omitted, it defaults to `"KLALB_SRv6"`.
- Routing broadcast (`RouterInfo`) transmits `deviceName` across the network, which topology and node overview panels display. `deviceDescription` and `ExtraRoutes` remain local controller configs.
- To disable TUN creation completely (e.g. for non-admin UI/routing testing), set `"enableTUN": false` in `klalb-config.json` or toggle off "启用 TUN 虚拟网卡" in GUI/Web settings.
- Routing broadcast (`RouterInfo`) transmits `deviceName`, which topology and node overview panels display. `deviceDescription` is NOT broadcast — it only leaves the node in full node-info query responses (see srv6 API below); `ExtraRoutes` remain local controller configs.
## Verification
@@ -35,19 +37,63 @@ No test suite, no CI. Classes named `*Test*` (`nathole/`, `ntp/`) are manual `ma
## Architecture
- Entrypoint `org.kne.cloud.network.klalb.KLALBMain`: load config → build `KLALBProxySystem` → open Swing GUI (`KLALBStateGUI3`) unless `"nogui": true` → interactive console (`help`, `links-state`, `route`, `kperf`, ...).
- Entrypoint `org.kne.cloud.network.klalb.KLALBMain`: load config → build `KLALBProxySystem` → open Swing GUI (`KLALBStateGUI3`) unless `"nogui": true` → start `KLALBWebServer` (if `"webUI": true` or web server enabled, default port `4665`) → interactive console (`help`, `links-state`, `route`, `kperf`, ...).
- `org.kne.cloud.network.klalb.web.KLALBWebServer` — built-in HTTP/SSE server (JDK `HttpServer`):
- API endpoints: `/api/status`, `/api/events` (SSE stream, 200ms intervals), `/api/links`, `/api/links/action`, `/api/links/reconnect`, `/api/routing-table`, `/api/nodes` (topology graph), `/api/node-info?address=<ipv6>` (on-demand full node info), `/api/config`.
- Static file hosting / SPA fallback: serves `dashboard/dist/` assets directly.
- `org.kne.cloud.network` — generic socket framework: `VirtualSocket*` hierarchy, `SocketBridge` port-forwarding proxies, `ProtocolDetector` (multi-protocol mux on one port), `MultiProtocolSocketAddress` = URI-style addresses (`tcp://`, `udp://`, `kltp://`, `ntp://`) dispatched through the `SocketType` registry.
- `...network.klalb` — app core: `KLALBController` (the virtual SRv6 network), `KLALBRemoteLink` (WAN lines), `*Packet` wire-format classes, virtual socket implementations.
- `...network.congestion` — pluggable congestion control (BBR, Vegas2, DCTCP...), chosen via `"congestionAlgorithm"` in config.
- `...network.kltp` — custom reliable transport protocol (packets/streams).
- `...network.ipv6`, `...network.srv6` — packet codecs, route table, Dijkstra path computation.
- **Node-info query API** (`...network.srv6`, JSON datagrams on `KLALBRoutingProtocol.DEFAULT_PORT=1001`): `KLALBRoutingProtocolAPIServer/Client` speak two request types —
- `nodeinfotinyreq/resp` → device name ONLY; never gated by any flag (name is public via broadcast anyway).
- `nodeinfofullreq/resp` → externalEndpoints + deviceName + deviceDescription. `denyExternalEndpointQuery=true` hides ONLY the endpoint list (`data=null`); name/description still answer.
- GUI rule: opening `NodeInformationPanel` = Full query; use `requestNodeInfoTiny` for lightweight/background lookups. Legacy `openlines*` message types were removed — mixed-version meshes get silence, so upgrade the whole network together.
- `JsonDataPacket` stores its UTF-8 payload length in a 2-byte header field: keep every JSON message under 64 KiB.
- `...network.frpc` — frp client integration.
- `...klalb.ui` — all Swing UI code.
## Frontend (Dashboard)
Located in `dashboard/`:
- **Git layout**: `dashboard/` is a separate git repo wired in as a submodule (own origin on `git.code.cq.cn`). Commit frontend changes inside `dashboard/` first, then bump the submodule pointer in the parent repo — parent-repo commits alone do not capture them.
- **Stack**: Vite + React 19 + TypeScript + Tailwind CSS v4 + `@base-ui/react` (style: `base-nova`, icons: `lucide-react`, toasts: `@base-ui/react/toast`).
- **Routing**: Hash-based routing (`#/overview`, `#/connections`, `#/routing-table`, `#/topology`, `#/settings`) for seamless SPA hosting under Java `KLALBWebServer`.
- **Package Manager**: `pnpm` (run all commands from `dashboard/` directory).
- **Component installation**: **Must** use CLI via `pnpm dlx shadcn@latest add <component>` (e.g. `pnpm dlx shadcn@latest add alert card badge toast`). Never create or fake shadcn components manually. Non-shadcn libs (`@xyflow/react`, `d3-force`) are installed via plain `pnpm add`.
- **Commands**:
- `pnpm dev` — Start Vite dev server (proxies `/api` to backend `http://127.0.0.1:4665`).
- `pnpm build` — Typecheck and build SPA to `dashboard/dist` (which Java `KLALBWebServer` serves directly).
- `pnpm lint` / `pnpm typecheck` — Verification.
- **Pages & data flow**:
- Overview / Connections read the SSE stream (`use-klalb-sse.ts`, 200ms pushes of status + links).
- Settings loads/saves `/api/config` (`use-klalb-config.ts`); save payload must keep legacy field aliases alongside new names for compatibility.
- Routing table polls `/api/routing-table` every 1s (`use-routing-table.ts`); `cost` is delay-derived and displayed in milliseconds.
- Topology polls `/api/nodes` every 1s (`use-topology.ts`) — SSE does NOT carry topology.
- Selecting a topology node queries `/api/node-info`; remote node descriptions require a full SRv6 node-info request and can time out after 3s.
- Topology layout: `d3-force` headless simulation (recomputed only when node/edge structure changes) rendered by `@xyflow/react` with custom `device-node` / `link-edge` components in `src/components/topology/`.
- React hooks lint rule forbids `setState` synchronously inside effects — initialize form state via component `key` remount + lazy `useState(() => ...)` initializers (see `SettingsForm` pattern).
## Config
`klalb-config.json` is an array of items discriminated by their `"Type"` field. Adding a new item type requires a `KLALBConfigItem` subclass **plus** new cases in both `KLALBConfigItem.getDefaultJsonDeserializer()` and `getDefaultJsonSerializer()`; unknown types are preserved as `UnknownKLALBConfigItem`. Any Gson instance handling config must register these adapters via `registerToGsonBuilder` (see `KLALBProxySystem`).
Key controller config fields:
- `externalEndpoints` / `autoConnections`: published vs auto-connect endpoint lists (renamed from `openConnections`, which itself replaced legacy `LineTable`; the old name was a developer naming mistake — these addresses are this node's externally published endpoints, not "connections").
- `ntpServers`: time server list (replaces `ntpServerTable`).
- `denyExternalEndpointQuery` / `denyExternalEndpointBroadcast`: safety flags — the query flag hides ONLY the external-endpoint list in full node-info responses (device name/description still answer; Tiny queries are never gated), the broadcast flag disables LAN multicast discovery (renamed from `denyConnectionQuery` / `denyConnectionBroadcast`, which replaced `denyLineTableQuery` / `denyLineTableBroadcast`).
- `enableTUN`: boolean flag for TUN interface creation (`"TUNName"` configures device name).
- `webListen`: Web API listen address, normally `http://0.0.0.0:4665`; legacy `webPort` is accepted on load/API input.
Legacy JSON keys are still accepted on load: `KLALBConfigItem.getDefaultJsonDeserializer()` normalizes old key names (`openConnections`/`LineTable`, `denyConnectionQuery`, `denyLineTable*`, ...) before reflective deserialization (manual rewrite because gson-2.1 has no `@SerializedName(alternate=...)`), and `handleConfig` in the web server accepts them too. New saves always write canonical names.
Gson quirks:
- **gson-2.1 (vendored) is ancient**: its `JSON_ELEMENT` adapter factory only matches exact `JsonElement.class`, NOT subclasses. Calling `gson.toJson(Object)` with a runtime `JsonObject`/`JsonArray` reflectively serializes the internal field as `{"members": {...}}`. `KLALBWebServer.sendJsonResponse` guards against this by using `JsonElement.toString()` for JsonElement instances — keep that guard when adding new response paths. SSE avoids the issue entirely via `JsonObject.toString()`.
- `/api/config` GET/POST is parsed field-by-field in `KLALBWebServer.handleConfig` (NOT whole-object Gson reflection) because polymorphic fields (`List<InetAddress>`, `List<MultiProtocolSocketAddress>`) break reflective mapping. Keep new config fields in sync there, accepting both legacy and new JSON key names.
- `InetAddress`, `MultiProtocolSocketAddress`, and `KLALBConfigItem` custom adapters are registered on the shared Gson in `KLALBProxySystem`; the web server reuses that instance via `proxySystem.getGson()`.
- Saving via web API persists through `KLALBProxySystem.saveConfigToFile()` (GUI save consumer takes precedence when present).
## Conventions
- Sources are UTF-8; comments, log/UI strings, and commit messages are largely Chinese.
+2
View File
@@ -249,5 +249,7 @@
</library>
</orderEntry>
<orderEntry type="jdk" jdkName="jdk-26.0.1" jdkType="JavaSDK" />
<orderEntry type="library" name="jfreechart-1.5.3" level="project" />
<orderEntry type="library" name="jctools-core-jdk11-4.0.7" level="project" />
</component>
</module>
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1242
View File
File diff suppressed because it is too large Load Diff
-20
View File
@@ -1,20 +0,0 @@
//加速线路
{KLALBRemote}->43.248.189.107:35000
{KLALBRemote}->cn-bj-bgp-3.openfrp.top:65529
{KLALBRemote}->180.76.147.250:65529
{KLALBRemote}->cn-ah-dx-1.natfrp.cloud:65529
{KLALBRemote}->cn-nn-dx-1.natfrp.cloud:65529
{KLALBRemote}->cn-wh-dx-1.natfrp.cloud:65529
{KLALBRemote}->cn-zz-bgp-10.natfrp.cloud:23330
{KLALBRemote}->cn-zz-bgp-7.natfrp.cloud:33336
{KLALBRemote}->43.143.109.64:49965
{KLALBRemote}->frp.104300.xyz:49965
{KLALBRemote}->us.afrps.cn:49966
{KLALBRemote}->hk.afrps.cn:49966
{KLALBRemote}->la.afrps.cn:49966
{KLALBRemote}->frp.freefrp.net:49965
{KLALBRemote}->frp1.freefrp.net:49965
{KLALBRemote}->frp2.freefrp.net:49965
{KLALBRemote}->frp4.freefrp.net:49965
0.0.0.0:25565->{KLALBVirtual}[171d:a999:e697:4b23:ae52:9f29:4532:e1ee]:25565
Submodule
+1
Submodule dashboard added at 885a0c2d89
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 318 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

-107
View File
@@ -1,107 +0,0 @@
[
{
"language": "ZH_CN",
"nogui": false,
"VirtualAddress": "2486:1:0:0:0:0:0:8889",
"VirtualASN": 2142606939373348329,
"DNS": [
"2486:1:0:0:0:0:0:8888"
],
"TCPListen": "tcp://0.0.0.0:4565",
"UDPListen": "udp://0.0.0.0:4572",
"VirtualSocketName": "kltp",
"LineTable": [
"tcp://kne03.yoyo250.fun:4565",
"tcp://kne04.yoyo250.fun:4565"
],
"ConnectLineTable": [
"tcp://07f4acdef99b.ofalias.net:4565",
"tcp://kne01.yoyo250.fun:4565",
"tcp://kne02.yoyo250.fun:4565"
],
"ntpServerTable": [
"ntp://ntp1.aliyun.com",
"ntp://ntp2.aliyun.com",
"ntp://ntp3.aliyun.com",
"ntp://ntp4.aliyun.com",
"ntp://ntp5.aliyun.com",
"ntp://ntp6.aliyun.com",
"ntp://ntp7.aliyun.com",
"ntp://ntp1.tencent.com",
"ntp://ntp2.tencent.com",
"ntp://ntp4.tencent.com",
"ntp://ntp5.tencent.com",
"ntp://time.google.com",
"ntp://time.apple.com",
"ntp://pool.ntp.org",
"ntp://ntp.ntsc.ac.cn",
"ntp://us.ntp.org.cn"
],
"ExtraRoutes": [],
"denyLineTableQuery": false,
"denyLineTableBroadcast": false,
"congestionAlgorithm": "BBR",
"burstLimit": 2.0,
"delayUpperBound": 1.2,
"delayLowerBound": 1.1,
"nagleDelayTime": 1000000,
"linkNagleDelayTime": 0,
"linkConnectionsCount": 1,
"TUNName": null,
"performanceStrategy": "multiscatter",
"DeviceName": "SerinaNya PC",
"DeviceDescription": "desc",
"NetworkInterfaceExcepts": [],
"Type": "KLALBController"
},
{
"Listen": "kltp://[::0]:5201",
"Bridge": {
"DEFAULT": "SocketBridge"
},
"Connect": {
"DEFAULT": "tcp://127.0.0.1:5201"
},
"Type": "SocketBridge"
},
{
"Listen": "tcp://[::1]:5202",
"Bridge": {
"DEFAULT": "SocketBridge"
},
"Connect": {
"DEFAULT": "kltp://[2486:5acd:e339:4837:a6d9:3aed:4de2:30fd]:5201"
},
"Type": "SocketBridge"
},
{
"Listen": "tcp://127.0.0.1:35000",
"Bridge": {
"DEFAULT": "SocketBridge"
},
"Connect": {
"DEFAULT": "kltp://[2486:1::8888]:23333"
},
"Type": "SocketBridge"
},
{
"Listen": "kltp://[::0]:25565",
"Bridge": {
"DEFAULT": "SocketBridge"
},
"Connect": {
"DEFAULT": "tcp://127.0.0.1:25566"
},
"Type": "SocketBridge"
},
{
"Listen": "tcp://127.0.0.1:35565",
"Bridge": {
"DEFAULT": "SocketBridge"
},
"Connect": {
"DEFAULT": "kltp://[2486:1::8888]:25565"
},
"Type": "SocketBridge"
}
]
-79
View File
@@ -1,79 +0,0 @@
[
{"Type":"KLALBController" ,
"TCPListen" : "0.0.0.0:4565",
"UDPListen" : "{UDP}0.0.0.0:4572",
"VirtualSocketName" : "KLALB_Stream",
"LineTable":["43.248.189.107:65529",
"cn-bj-bgp-3.openfrp.top:65529",
"180.76.147.250:65529",
"cn-ah-dx-1.natfrp.cloud:65529",
"cn-nn-dx-1.natfrp.cloud:65529",
"cn-wh-dx-1.natfrp.cloud:65529",
"cn-zz-bgp-10.natfrp.cloud:23330",
"cn-zz-bgp-7.natfrp.cloud:33336",
"43.143.109.64:49965",
"frp.104300.xyz:49965",
"us.afrps.cn:49966",
"hk.afrps.cn:49966",
"la.afrps.cn:49966",
"frp.freefrp.net:49965",
"frp1.freefrp.net:49965",
"frp2.freefrp.net:49965",
"frp4.freefrp.net:49965",
"cn-he-plc-2.openfrp.top:4572",
"{UDP}cn-he-plc-2.openfrp.top:4572",
"frp-mom.top:22226",
"frp-man.top:36555",
"us-sjc-bgp-2.openfrp.top:36555",
"us-sjc-bgp-1.openfrp.top:36555",
"[240e:340:d856:b700:6d02:6a6a:f9fc:865b]:4572"]
},
{"Type":"SocketBridge" ,
"Listen" : "0.0.0.0:4572",
"Bridge":{
"KLALB" : "SocketBridge",
"RDP":"SocketBridge",
"HTTP":"SocketBridge",
"HTTPS":"SocketBridge",
"DEFAULT":"MinecraftSocketBridge23332"
} ,
"Connect" : {
"KLALB" : "127.0.0.1:4565",
"RDP":"127.0.0.1:3389",
"HTTP":"127.0.0.1:5212",
"HTTPS":"192.168.1.235:443",
"DEFAULT":"127.0.0.1:36555"
}
},
{"Type":"SocketBridge" ,
"Listen" : "{KLALB_Stream}[::0]:23332",
"Bridge":"MinecraftSocketBridge23332",
"Connect" : "127.0.0.1:36555"
},
{ "Type":"SocketBridge" ,
"Listen" : "{KLALB_Stream}[::0]:23333",
"Bridge":"SocketBridge",
"Connect" : "127.0.0.1:5212"
},
{"Type":"SocketBridge" ,
"Listen" : "0.0.0.0:25561",
"Bridge":{
"KLALB" : "SocketBridge",
"DEFAULT":"MinecraftSocketBridge23330"
} ,
"Connect" : {
"KLALB" : "127.0.0.1:4565",
"DEFAULT":"127.0.0.1:25562"
}
},
{"Type":"SocketBridge" ,
"Listen" : "{KLALB_Stream}[::0]:23330",
"Bridge":"MinecraftSocketBridge23330",
"Connect" : "127.0.0.1:25562"
}
]
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-29
View File
@@ -1,29 +0,0 @@
0.0.0.0:35000(RDP)->192.168.1.233:3389
0.0.0.0:35000(HTTPS)->192.168.1.233:8444
0.0.0.0:35000(HTTP)->192.168.1.233:5212
0.0.0.0:35000(SSH)->192.168.1.236:22
0.0.0.0:35000(KLALB)->{KLALBRemote}
0.0.0.0:35000->127.0.0.1:35001 //Winds服(普通线路)
{KLALBVirtual}[::0]:25565->127.0.0.1:35001 //Winds服(加速线路)
//加速线路
{KLALBRemote}->43.248.189.107:35000
{KLALBRemote}->cn-bj-bgp-3.openfrp.top:65529
{KLALBRemote}->180.76.147.250:65529
{KLALBRemote}->cn-ah-dx-1.natfrp.cloud:65529
{KLALBRemote}->cn-nn-dx-1.natfrp.cloud:65529
{KLALBRemote}->cn-wh-dx-1.natfrp.cloud:65529
{KLALBRemote}->cn-zz-bgp-10.natfrp.cloud:23330
{KLALBRemote}->cn-zz-bgp-7.natfrp.cloud:33336
{KLALBRemote}->43.143.109.64:49965
{KLALBRemote}->frp.104300.xyz:49965
{KLALBRemote}->us.afrps.cn:49966
{KLALBRemote}->hk.afrps.cn:49966
{KLALBRemote}->la.afrps.cn:49966
{KLALBRemote}->frp.freefrp.net:49965
{KLALBRemote}->frp1.freefrp.net:49965
{KLALBRemote}->frp2.freefrp.net:49965
{KLALBRemote}->frp4.freefrp.net:49965
0.0.0.0:25565->{KLALBVirtual}[171d:a999:e697:4b23:ae52:9f29:4532:e1ee]:25565
-18
View File
@@ -1,18 +0,0 @@
43.248.189.107:65529
cn-bj-bgp-3.openfrp.top:65529
180.76.147.250:65529
cn-ah-dx-1.natfrp.cloud:65529
cn-nn-dx-1.natfrp.cloud:65529
cn-wh-dx-1.natfrp.cloud:65529
cn-zz-bgp-10.natfrp.cloud:23330
cn-zz-bgp-7.natfrp.cloud:33336
43.143.109.64:49965
frp.104300.xyz:49965
us.afrps.cn:49966
hk.afrps.cn:49966
la.afrps.cn:49966
frp.freefrp.net:49965
frp1.freefrp.net:49965
frp2.freefrp.net:49965
frp4.freefrp.net:49965
cn-he-plc-2.openfrp.top:4569
View File
-8
View File
@@ -1,8 +0,0 @@
0.0.0.0:35000(RDP)->192.168.1.233:3389
0.0.0.0:35000(HTTPS)->192.168.1.233:8444
0.0.0.0:35000(HTTP)->192.168.1.233:5212
0.0.0.0:35000(SSH)->192.168.1.236:22
0.0.0.0:35000(KLALB)->{KLALBRemote}
0.0.0.0:35000->127.0.0.1:35001 //Winds服(普通线路)
{KLALBVirtual}[::0]:25565->127.0.0.1:35001 //Winds服(加速线路)
+11
View File
@@ -0,0 +1,11 @@
{
"version": 1,
"skills": {
"shadcn": {
"source": "shadcn/ui",
"sourceType": "github",
"skillPath": "skills/shadcn/SKILL.md",
"computedHash": "c1a68ee06a668aced9ab2b5fbdea5f989864123794eb2e056b339a072dbb7f10"
}
}
}
+13
View File
@@ -112,3 +112,16 @@ performancestrategy=Performance strategy
singlecore=Single-Core - Cache Affinity First (Best energy/performance ratio, for low-power & cloud)
multifill=Multi-Core - Fill Cores Sequentially (Recommended for general-purpose physical servers)
multiscatter=Multi-Core - Spread Load Evenly (Optimized for multi-socket NUMA architectures)
webapisettings=Web API settings
enablewebapi=Enable Web API
weblistenaddr=Web API listen address
invaildweblistenaddr=Invalid Web API listen address
enabletun=Enable TUN adapter
congestionmonitor=Congestion control monitor
rttbaseline=RTT baseline
rtt=RTT
rttmax=RTT max
rttmin=RTT min
rto=RTO
cpuload=CPU load
ramusage=RAM usage
+13
View File
@@ -112,3 +112,16 @@ performancestrategy=性能策略
singlecore=单核-缓存命中率优先(高能耗比,适合低功耗设备、云机)
multifill=多核-负载按顺序填充(适合大多数物理服务器、电脑)
multiscatter=多核-负载均匀打散分配(适合特殊的多路NUMA服务器)
webapisettings=Web API 设置
enablewebapi=启用 Web API
weblistenaddr=Web API 监听地址:端口
invaildweblistenaddr=无效的 Web API 监听地址:端口
enabletun=启用TUN虚拟网卡
congestionmonitor=拥塞控制监视器
rttbaseline=往返延迟基线
rtt=往返延迟
rttmax=往返延迟上限
rttmin=往返延迟下限
rto=重传超时
cpuload=CPU负载
ramusage=RAM占用率
@@ -5,6 +5,9 @@ import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.kne.math.Long128;
@@ -256,4 +259,27 @@ public class NTPTimestamps {
.setBit(63);
}
}
public static Long128 median(List<Long128> values) {
Objects.requireNonNull(values);
if ( values.isEmpty()) {
throw new IllegalArgumentException("列表不能为空");
}
// 1. 创建副本并排序
List<Long128> sorted = new ArrayList<>(values);
sorted.sort(Long128::compareTo);
// 2. 计算中位数
int size = sorted.size();
if (size % 2 == 1) {
// 奇数个:取中间值
return sorted.get(size / 2);
} else {
// 偶数个:取中间两个的平均值
Long128 left = sorted.get(size / 2 - 1);
Long128 right = sorted.get(size / 2);
return left.add(right).divide(Long128.valueOf(2));
}
}
}
@@ -0,0 +1,277 @@
package org.kne.cloud.network;
import java.io.IOException;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.SocketAddress;
import java.net.SocketOption;
import java.nio.ByteBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.DatagramChannel;
import java.nio.channels.MembershipKey;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.spi.SelectorProvider;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* 自制的 UDP AIO 通道,内部使用线程池模拟异步收包。
* 用户只需设置回调,无需自己管理线程。
*
* 完整委托了 DatagramChannel 的所有公共方法。
*/
public class AsynchronousDatagramChannel implements AutoCloseable {
private final DatagramChannel delegate;
private final ExecutorService workerPool;
private final AtomicBoolean running = new AtomicBoolean(true);
private volatile ReceiveHandler handler;
private final int bufferSize;
private int threadCount;
// ==================== 回调接口 ====================
@FunctionalInterface
public interface ReceiveHandler {
void onReceive(SocketAddress from, ByteBuffer data);
}
// ==================== 构造方法 ====================
public AsynchronousDatagramChannel() throws IOException {
this(65535, Runtime.getRuntime().availableProcessors());
}
public AsynchronousDatagramChannel(int bufferSize, int threadCount) throws IOException {
this.bufferSize = bufferSize;
this.delegate = DatagramChannel.open();
delegate.configureBlocking(true);
this.threadCount=threadCount;
this.workerPool = Executors.newFixedThreadPool(threadCount, r -> {
Thread t = new Thread(r, "AsyncDatagramReader");
t.setDaemon(true);
return t;
});
}
public void startWorkers() {
for (int i = 0; i < threadCount; i++) {
workerPool.submit(this::readerLoop);
}
}
// ==================== 核心方法 ====================
public void setReceiveHandler(ReceiveHandler handler) {
this.handler = handler;
}
private void readerLoop() {
ByteBuffer buffer = ByteBuffer.allocateDirect(bufferSize);
while (running.get()) {
try {
buffer.clear();
SocketAddress from = delegate.receive(buffer);
if (from == null) {
continue;
}
buffer.flip();
ReceiveHandler localHandler = handler;
if (localHandler != null) {
localHandler.onReceive(from, buffer);
}
} catch (IOException e) {
if (running.get()) {
e.printStackTrace();
}
break;
}
}
}
public int send(ByteBuffer data, SocketAddress target) throws IOException {
return delegate.send(data, target);
}
// ==================== DatagramChannel 委托方法 ====================
// --- 连接相关 ---
public DatagramChannel connect(SocketAddress remote) throws IOException {
return delegate.connect(remote);
}
public DatagramChannel disconnect() throws IOException {
return delegate.disconnect();
}
public boolean isConnected() {
return delegate.isConnected();
}
// --- 绑定相关 ---
public DatagramChannel bind(SocketAddress local) throws IOException {
return delegate.bind(local);
}
public SocketAddress getLocalAddress() throws IOException {
return delegate.getLocalAddress();
}
public SocketAddress getRemoteAddress() throws IOException {
return delegate.getRemoteAddress();
}
// --- 读写相关 ---
public int read(ByteBuffer dst) throws IOException {
return delegate.read(dst);
}
public long read(ByteBuffer[] dsts) throws IOException {
return delegate.read(dsts);
}
public long read(ByteBuffer[] dsts, int offset, int length) throws IOException {
return delegate.read(dsts, offset, length);
}
public int write(ByteBuffer src) throws IOException {
return delegate.write(src);
}
public long write(ByteBuffer[] srcs) throws IOException {
return delegate.write(srcs);
}
public long write(ByteBuffer[] srcs, int offset, int length) throws IOException {
return delegate.write(srcs, offset, length);
}
// --- 接收相关 ---
public SocketAddress receive(ByteBuffer dst) throws IOException {
return delegate.receive(dst);
}
// --- 多播 (Multicast) ---
public MembershipKey join(InetAddress group, NetworkInterface interf) throws IOException {
return delegate.join(group, interf);
}
public MembershipKey join(InetAddress group, NetworkInterface interf, InetAddress source) throws IOException {
return delegate.join(group, interf, source);
}
// --- 选项相关 ---
public <T> DatagramChannel setOption(SocketOption<T> name, T value) throws IOException {
return delegate.setOption(name, value);
}
public <T> T getOption(SocketOption<T> name) throws IOException {
return delegate.getOption(name);
}
public Set<SocketOption<?>> supportedOptions() {
return delegate.supportedOptions();
}
// --- 阻塞模式 ---
public boolean isBlocking() {
return delegate.isBlocking();
}
// --- 选择器相关 ---
public SelectableChannel configureBlocking(boolean block) throws IOException {
return delegate.configureBlocking(block);
}
public SelectorProvider provider() {
return delegate.provider();
}
public int validOps() {
return delegate.validOps();
}
public SelectionKey keyFor(Selector sel) {
return delegate.keyFor(sel);
}
public SelectionKey register(Selector sel, int ops) throws ClosedChannelException {
return delegate.register(sel, ops);
}
public SelectionKey register(Selector sel, int ops, Object att) throws ClosedChannelException {
return delegate.register(sel, ops, att);
}
// --- 状态查询 ---
public boolean isOpen() {
return delegate.isOpen() && running.get();
}
public DatagramSocket socket() {
return delegate.socket();
}
// ==================== 关闭方法 ====================
@Override
public void close() throws Exception {
running.set(false);
workerPool.shutdownNow();
delegate.close();
}
// ==================== 静态工厂方法 ====================
public static AsynchronousDatagramChannel open() throws IOException {
return new AsynchronousDatagramChannel();
}
public static AsynchronousDatagramChannel open(int bufferSize) throws IOException {
return new AsynchronousDatagramChannel(bufferSize, Runtime.getRuntime().availableProcessors());
}
public static AsynchronousDatagramChannel open(int bufferSize, int threadCount) throws IOException {
return new AsynchronousDatagramChannel(bufferSize, threadCount);
}
// ==================== 测试示例 ====================
public static void main(String[] args) throws Exception {
int port = 4567;
AsynchronousDatagramChannel channel = AsynchronousDatagramChannel.open();
channel.bind(new InetSocketAddress(port));
channel.setReceiveHandler((from, data) -> {
byte[] bytes = new byte[data.remaining()];
data.get(bytes);
String msg = new String(bytes);
System.out.println("Received from " + from + ": " + msg);
});
System.out.println("Echo server running on port " + port);
System.out.println("Press Enter to stop...");
System.in.read();
channel.close();
System.out.println("Server stopped.");
}
}
@@ -8,6 +8,8 @@ import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.net.*;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
@@ -148,21 +150,39 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
}
}).start();
}
// 1. 接收用的 ThreadLocal 缓冲区
private static final ThreadLocal<ByteBuffer> RX_BUFFER = ThreadLocal.withInitial(() -> ByteBuffer.allocate(2048));
// 2. 发送用的 ThreadLocal 缓冲区 (广播包一般很小)
private static final ThreadLocal<ByteBuffer> TX_BUFFER = ThreadLocal.withInitial(() -> ByteBuffer.allocate(2048));
private MultiProtocolSocketAddress receive() throws IOException {
byte[] b = new byte[65535];
DatagramPacket dp = new DatagramPacket(b, b.length);
ByteBuffer bbf = RX_BUFFER.get();
bbf.clear(); // 重置 position=0, limit=2048
// 借用底层数组
DatagramPacket dp = new DatagramPacket(bbf.array(), bbf.limit());
soc.receive(dp);
ByteArrayInputStream bis = new ByteArrayInputStream(b, 0, dp.getLength());
DataInputStream dis = new DataInputStream(bis);
String val = dis.readUTF();
long h=dis.readLong();
long l=dis.readLong();
UUID uid=new UUID(h, l);
dis.close();
// 限制可读范围
int length = dp.getLength();
bbf.limit(length);
// 直接读 UTF 字符串 (不用 DataInputStream)
// 读取前两个字节的长度,然后创建 String
int strLen = bbf.getShort() & 0xFFFF; // 无符号转 int
byte[] strBytes = new byte[strLen]; // 这里为了 String 还是得分配一次,但很小
bbf.get(strBytes);
String val = new String(strBytes, StandardCharsets.UTF_8);
// 直接读两个 Long 拼成 UUID
long h = bbf.getLong();
long l = bbf.getLong();
UUID uid = new UUID(h, l); // UUID 对象很小,年轻代瞬间回收,不用池化
MultiProtocolSocketAddress mpsa = new MultiProtocolSocketAddress(val);
if(mpsa.getInetAddress().isAnyLocalAddress()){
mpsa=new MultiProtocolSocketAddress(mpsa.getProtocol(),dp.getAddress().getHostAddress(),mpsa.getPort());
if (mpsa.getInetAddress().isAnyLocalAddress()) {
mpsa = new MultiProtocolSocketAddress(mpsa.getProtocol(), dp.getAddress().getHostAddress(), mpsa.getPort());
}
if(debug) {
if(uid.equals(selfUUID)) {
@@ -176,15 +196,23 @@ public class IPMulticastDiscovery implements Closeable, AutoCloseable {
}
private void send(MultiProtocolSocketAddress multiProtocolSocketAddress) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dops = new DataOutputStream(baos);
dops.writeUTF(multiProtocolSocketAddress.toString());
dops.writeLong(selfUUID.getMostSignificantBits());
dops.writeLong(selfUUID.getLeastSignificantBits());
dops.close();
byte[] b = baos.toByteArray();
DatagramPacket dp = new DatagramPacket(b, b.length, group);
ByteBuffer bbf = TX_BUFFER.get();
bbf.clear(); // 重置
// 写字符串 (长度 + 字节)
byte[] strBytes = multiProtocolSocketAddress.toString().getBytes(StandardCharsets.UTF_8);
bbf.putShort((short) strBytes.length);
bbf.put(strBytes);
// 写 UUID
bbf.putLong(selfUUID.getMostSignificantBits());
bbf.putLong(selfUUID.getLeastSignificantBits());
bbf.flip(); // 准备读取
DatagramPacket dp = new DatagramPacket(bbf.array(), bbf.limit(), group);
soc.send(dp);
if(debug)
System.out.println(ninterface.getDisplayName()+" 发送广播:"+ multiProtocolSocketAddress +" "+group+" "+selfUUID);
@@ -11,14 +11,13 @@ public class BBRCongestionAlgorithm implements CongestionAlgorithm ,DetnetConges
private Consumer<Long> windowControlConsumer;
private long MIN_RTTVAR = 5000000L;
private volatile long RTTMin = 1000000000L;
private volatile long RTTVar = 1000000000L;
private volatile long RTTAvg = 1000000000L;
private volatile long RTTAvg2 = 1000000000L;
private volatile long RTTTotal = 0;
private volatile long RTTCount = 0;
private volatile long RTO = 1000000000L;
private RTTEstimator rttEstimator=new RTTEstimator();
private volatile long window=MIN_WINDOW;
@@ -54,10 +53,7 @@ public class BBRCongestionAlgorithm implements CongestionAlgorithm ,DetnetConges
} else {
RTTMin = (RTTMin * 99999 + latencyns) / 100000;
}
RTTVar = (RTTVar * 3 + Math.abs(RTTAvg - latencyns)) / 4;
RTTAvg = (RTTAvg * 7 + latencyns) / 8;
RTO = RTTAvg + Math.max(MIN_RTTVAR, RTTVar * 4);// RTTVar*4
rttEstimator.putRTT(latencyns);
RTTTotal += latencyns;
RTTCount++;
@@ -93,13 +89,13 @@ public class BBRCongestionAlgorithm implements CongestionAlgorithm ,DetnetConges
}
@Override
public void putLoss(long packetSize, long lossns, int losscounter) {
public void putLoss(long packetSize, long lossns) {
}
@Override
public long getRTO() {
return RTO;
return rttEstimator.getRTO();
}
@Override
@@ -136,4 +132,29 @@ public class BBRCongestionAlgorithm implements CongestionAlgorithm ,DetnetConges
return "BBR";
}
@Override
public long getBaseRTT() {
return rttEstimator.getBaseRTT();
}
@Override
public long getCurrentRTT() {
return rttEstimator.getCurrentRTT();
}
@Override
public double getUpperDelayBound() {
return MAX_GAIN;
}
@Override
public double getLowerDelayBound() {
return MIN_GAIN;
}
@Override
public double getBurstLimit() {
return burstLimit;
}
}
@@ -6,13 +6,13 @@ public interface CongestionAlgorithm {
public void reset();
public void setWindowControlConsumer(Consumer<Long>windowControlConsumer);
public void putAck(long packetSize,long RTTns,boolean ecn);
default public void putAck(long packetSize,long RTTns,long OWDup,boolean ecn) {
putAck(packetSize,RTTns,ecn);
}
public void putLoss(long packetSize,long lossns,int losscounter);
public void putLoss(long packetSize,long lossns);
public void setCurrentWindowUsed(long maxwindow);
public void setCurrentBandwidth(long bandwidth);
public long getRTO();
public String getName();
public long getBaseRTT();
public long getCurrentRTT();
}
@@ -1,126 +0,0 @@
package org.kne.cloud.network.congestion;
import java.util.function.Consumer;
import org.jctools.counters.FixedSizeStripedLongCounter;
import org.kne.cloud.network.klalb.KLALBUtils;
public class DCTCP2CongestionAlgorithm implements CongestionAlgorithm {
private static final long BURST_TIME = 1000000L;
private static final long BIAS = 2000000L;
private static long MIN_WINDOW = 8192;
private Consumer<Long> windowControlConsumer;
private long MIN_RTTVAR = 100000000L;
private volatile long RTTVar = 1000000000L;
private volatile long RTTAvg = 1000000000L;
private double ECNAvg2 = 0L;
private double alpha=0;
private volatile FixedSizeStripedLongCounter ECNSize = KLALBUtils.createCounter();
private volatile FixedSizeStripedLongCounter TotalSize = KLALBUtils.createCounter();
private volatile long RTO = 500000000L;
private volatile long window=MIN_WINDOW;
private volatile long maxwindow = 99999999999L;
private volatile long congressWindowSize=MIN_WINDOW;
private long RTTstartTime = System.nanoTime();
@Override
public void reset() {
window = MIN_WINDOW;
if (windowControlConsumer != null) {
windowControlConsumer.accept(window);
}
}
@Override
public void setWindowControlConsumer(Consumer<Long> windowControlConsumer) {
this.windowControlConsumer = windowControlConsumer;
}
@Override
public synchronized void putAck(long packetSize, long latencyns, boolean ecn) {
RTTVar = (RTTVar * 3 + Math.abs(RTTAvg - latencyns)) / 4;
RTTAvg = (RTTAvg * 7 + latencyns) / 8;
RTO = RTTAvg + Math.max(MIN_RTTVAR, RTTVar * 4);// RTTVar*4
if(ecn) {
ECNSize.inc(packetSize);
}
TotalSize.inc(packetSize);
long curr=System.nanoTime();
if(curr-RTTstartTime>(RTTAvg+BIAS)) {
RTTstartTime=curr;
long totalSize=TotalSize.getAndReset();
long ecnSize=ECNSize.getAndReset();
if(totalSize!=0) {
ECNAvg2=ecnSize/(double)totalSize;
alpha=(alpha*7+ECNAvg2)/8;
}
if(ECNAvg2>0.5) {
congressWindowSize-=200;
}else if(ECNAvg2>0.01){
congressWindowSize+=200;
}else {
congressWindowSize+=4000;
}
//System.out.println(ECNAvg2);
//System.out.println(speed+" "+window);
updateWindowSize();
}
}
private void updateWindowSize() {
if(congressWindowSize<MIN_WINDOW) {
congressWindowSize=MIN_WINDOW;
}else if(congressWindowSize>maxwindow) {
congressWindowSize=maxwindow;
}
window=Math.max( congressWindowSize,MIN_WINDOW);
if (windowControlConsumer != null) {
windowControlConsumer.accept(window);
}
}
@Override
public void putLoss(long packetSize, long lossns, int losscounter) {
}
@Override
public long getRTO() {
return RTO;
}
@Override
public void setCurrentWindowUsed(long used) {
this.maxwindow=(long) (used*1.5)+MIN_WINDOW;
}
@Override
public void setCurrentBandwidth(long bandwidth) {
}
@Override
public String getName() {
return "DCTCP2";
}
}
@@ -13,16 +13,13 @@ public class DCTCPCongestionAlgorithm implements CongestionAlgorithm {
private Consumer<Long> windowControlConsumer;
private AtomicBoolean firstUpdate = new AtomicBoolean(true);
private long MIN_RTTVAR = 100000000L;
private volatile long RTTMin = 1000000000L;
private volatile long RTTVar = 1000000000L;
private volatile long RTTAvg = 1000000000L;
private double ECNAvg2 = 0L;
private double alpha=0;
private volatile AtomicLong ECNSize = new AtomicLong(0);
private volatile AtomicLong TotalSize = new AtomicLong(0);
private volatile long RTO = 1000000000L;
private RTTEstimator rttEstimator=new RTTEstimator(100000000L);
private volatile long window=MIN_WINDOW;
@@ -54,14 +51,7 @@ public class DCTCPCongestionAlgorithm implements CongestionAlgorithm {
} else {
RTTMin = (RTTMin * 99999 + latencyns) / 100000;
}
if (firstUpdate.compareAndSet(true, false)) {
RTTAvg = latencyns;
RTTVar = latencyns / 2;
} else {
RTTVar = (RTTVar * 3 + Math.abs(RTTAvg - latencyns)) / 4;
RTTAvg = (RTTAvg * 7 + latencyns) / 8;
}
RTO = RTTAvg + Math.max(MIN_RTTVAR, RTTVar * 4);// RTTVar*4
rttEstimator.putRTT(latencyns);
if(ecn) {
ECNSize.addAndGet(packetSize);
@@ -88,7 +78,7 @@ public class DCTCPCongestionAlgorithm implements CongestionAlgorithm {
}
}else {
if(windowUsed*3L>=congressWindowSize) {
congressWindowSize+=4000;
congressWindowSize+=4096;
updateWindowSize();
}
}
@@ -109,13 +99,13 @@ public class DCTCPCongestionAlgorithm implements CongestionAlgorithm {
}
@Override
public void putLoss(long packetSize, long lossns, int losscounter) {
public void putLoss(long packetSize, long lossns) {
}
@Override
public long getRTO() {
return RTO;
return rttEstimator.getRTO();
}
@Override
@@ -133,6 +123,15 @@ public class DCTCPCongestionAlgorithm implements CongestionAlgorithm {
return "DCTCP";
}
@Override
public long getBaseRTT() {
return rttEstimator.getBaseRTT();
}
@Override
public long getCurrentRTT() {
return rttEstimator.getCurrentRTT();
}
}
@@ -4,4 +4,7 @@ public interface DetnetCongestionAlgorithm extends CongestionAlgorithm {
public void setUpperDelayBound(double upper);
public void setLowerDelayBound(double lower);
public void setBurstLimit(double limit);
public double getUpperDelayBound( );
public double getLowerDelayBound();
public double getBurstLimit();
}
@@ -1,6 +1,7 @@
package org.kne.cloud.network.congestion;
import org.jctools.queues.MpscArrayQueue;
import org.jctools.queues.MessagePassingQueue;
import org.jctools.queues.varhandle.MpscVarHandleArrayQueue;
import org.kne.cloud.network.ThreadTool;
import org.kne.opencl64.Releaser;
@@ -82,7 +83,7 @@ class MpscMessageBatcherReleaser<T> extends Releaser<MpscMessageBatcher0<T>> {
class MpscMessageBatcher0<T> implements Runnable, MessageBatcher<T> {
private final MpscArrayQueue<T> queue;
private final MessagePassingQueue<T> queue;
private final int batchSize;
private final long maxDelay;
private volatile Consumer<List<T>> consumer;
@@ -94,7 +95,7 @@ class MpscMessageBatcher0<T> implements Runnable, MessageBatcher<T> {
public MpscMessageBatcher0(int batchSize, long maxDelay, int queueCapacity) {
this.batchSize = batchSize;
this.maxDelay = maxDelay;
this.queue = new MpscArrayQueue<>(queueCapacity);
this.queue = new MpscVarHandleArrayQueue<>(queueCapacity);
this.batchThread = ThreadTool.makeVDaemonThread("MpscMessageBatcher", this);
this.batchThread.start();
@@ -31,7 +31,7 @@ public class NOCongestionAlgorithm implements CongestionAlgorithm{
}
@Override
public void putLoss(long packetSize, long lossns, int losscounter) {
public void putLoss(long packetSize, long lossns) {
// TODO 自动生成的方法存根
}
@@ -63,4 +63,14 @@ public class NOCongestionAlgorithm implements CongestionAlgorithm{
return "NO";
}
@Override
public long getBaseRTT() {
return 0;
}
@Override
public long getCurrentRTT() {
return 0;
}
}
@@ -0,0 +1,150 @@
package org.kne.cloud.network.congestion;
import org.kne.cloud.clock.HighAccuracyClock;
import org.kne.cloud.network.klalb.KLALBUtils;
import org.kne.cloud.network.monitor.ArrayListTimestampMonitor;
import org.kne.cloud.network.monitor.DelaySampler;
import org.kne.cloud.network.monitor.TimestampMonitor;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
/**
* 线程安全的 RTT 平滑与 RTO 计算工具。
* 基于 RFC 6298 的 Jacobson/Karels 算法。
*
* 公式:
* RTTAvg = (RTTAvg * 7 + RTTns) / 8
* RTTVar = (RTTVar * 3 + |RTTAvg - RTTns|) / 4
* RTO = RTTAvg + max(MIN_RTTVAR, RTTVar * 4)
*/
public class RTTEstimator {
/** RTTVar 下限(纳秒),防止 RTO 过小。默认 20ms。 */
private static final long DEFAULT_MIN_RTTVAR_NS = 20_000_000L;
private final AtomicLong curr;
private final AtomicLong rttAvg;
private final AtomicLong rttVar;
private final AtomicLong rto;
private final AtomicLong basertt;
private final AtomicBoolean firstUpdate = new AtomicBoolean(true); // 首次标记
private final long minRttVarNs;
private long timewindowmax=500000000L;
long stime=System.nanoTime();
public RTTEstimator() {
this(DEFAULT_MIN_RTTVAR_NS);
}
public RTTEstimator(long minRttVarNs) {
this.rttAvg = new AtomicLong(1000000000L);
this.rttVar = new AtomicLong(1000000000L);
this.curr = new AtomicLong(1000000000L);
this.rto = new AtomicLong(1000000000L);
this.basertt = new AtomicLong(1000000000L);
this.minRttVarNs = minRttVarNs;
}
private final static long TIME_WINDOW=30000000000L;
private final TimestampMonitor<UUID> rttMonitor=new ArrayListTimestampMonitor<UUID>(new HighAccuracyClock(),"RTTMonitor", 100, TIME_WINDOW);
private final DelaySampler dsp=new DelaySampler();
public void update() {
dsp.update();
if(dsp.getSnapshotPacketCount()>0&&dsp.getSnapshotTotalDelay()>0) {
rttMonitor. recordPacket(KLALBUtils.createGlobalUUID(),0, 0, dsp.getAvgDelayNanos(),dsp.getMinDelayNanos());
}
long time=System.nanoTime();
if(time-stime>timewindowmax) {
basertt.lazySet( rttMonitor.calculateMinDelay(TIME_WINDOW));
stime=time;
}
}
/**
* 更新 RTT 估算值(线程安全)。
*
* @param rttNs 本次 RTT 测量值(纳秒)
*/
/**
* 更新 RTT 估算值(线程安全,无锁)。
*/
public void putRTT(long rttNs) {
if (rttNs < 0) {
throw new IllegalArgumentException("RTT 不能为负数: " + rttNs);
}
dsp.recordDelay(rttNs);
curr.lazySet(rttNs);
// 首次更新:CAS 成功则直接初始化,无锁
if (firstUpdate.compareAndSet(true, false)) {
rttAvg.set(rttNs);
rttVar.set(rttNs / 2);
return;
}
// 后续更新:CAS 循环保证原子性
long oldAvg = rttAvg.get();
long newAvg = (oldAvg * 7 + rttNs) / 8;
long delta = Math.abs(oldAvg - rttNs);
rttAvg.lazySet(newAvg);
long oldVar = rttVar.get();
long newVar = (oldVar * 3 + delta) / 4;
rttVar.lazySet(newVar);
rto .lazySet( newAvg + Math.max(minRttVarNs, newVar * 4));
}
/**
* 获取平滑 RTT 平均值(纳秒)。
*/
public long getRTTAvg() {
return rttAvg.getPlain();
}
/**
* 获取 RTT 偏差(纳秒)。
*/
public long getRTTVar() {
return rttVar.getPlain();
}
/**
* 获取 RTO(重传超时时间,纳秒)。
* RTO = RTTAvg + max(MIN_RTTVAR, RTTVar * 4)
*/
public long getRTO() {
return rto.getPlain();
}
/**
* 重置估算器(用于连接重建等场景)。
*/
public void reset() {
rttAvg.set(0);
rttVar.set(0);
}
@Override
public String toString() {
return String.format("RttEstimator{avg=%.3fms, var=%.3fms, rto=%.3fms}",
rttAvg.get() / 1_000_000.0,
rttVar.get() / 1_000_000.0,
getRTO() / 1_000_000.0);
}
public long getCurrentRTT() {
return curr.get();
}
public long getBaseRTT() {
return basertt.get();
}
}
@@ -5,7 +5,10 @@ import java.net.SocketException;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
@@ -13,13 +16,15 @@ import java.util.function.Consumer;
import org.kne.cloud.network.NetworkPacket;
import org.kne.cloud.network.ThreadTool;
import org.kne.cloud.network.klalb.KLALBScheduler;
import org.kne.cloud.network.klalb.SendItem;
import org.kne.concurrent.ThreadParker;
public class SendPacketSlidingWindow<K, V extends NetworkPacket> implements Closeable, AutoCloseable {
private final ScheduledFuture<?> checkerTask;
private ThreadParker tp=new ThreadParker();
private Map<K, SendItem<V>> sendMap = new ConcurrentHashMap<>(2048);
private volatile Map<K, SendItem<V>> sendMap = new ConcurrentHashMap<>(2048);
private AtomicLong sendWindowUsed=new AtomicLong(0);
//private LongAdder sendWindowUsed = new LongAdder();
@@ -51,13 +56,12 @@ public class SendPacketSlidingWindow<K, V extends NetworkPacket> implements Clos
}
public SendPacketSlidingWindow(CongestionAlgorithm algorithm, long windowSize, int headerCalibrate,
boolean removeAtResend) {
boolean removeAtResend) {
this.headerCalibrate = headerCalibrate;
this.removeAtResend = removeAtResend;
this.algorithm = algorithm;
this.windowSize=windowSize;
Thread t = ThreadTool.makeVThread("重路由计时器线程", checker);
t.start();
this.checkerTask=KLALBScheduler.getScheduler().scheduleWithFixedDelay(checker,1,1, TimeUnit.MILLISECONDS);
}
public long getWindowSize() {
@@ -131,16 +135,15 @@ public class SendPacketSlidingWindow<K, V extends NetworkPacket> implements Clos
algorithm.setCurrentWindowUsed(newWindow);
}
public V ack(K sequence) {
return ack(sequence,false);
return ack(sequence,0,false);
}
public V ack(K sequence,boolean ecn) {
public V ack(K sequence,long RTTns,boolean ecn) {
SendItem<V> ipv6;
if ((ipv6 = sendMap.remove(sequence)) != null) {
long newWindow =sendWindowUsed.addAndGet(-ipv6.getPacketLength() );
algorithm.setCurrentWindowUsed(newWindow);
long RTTC = System.nanoTime() - ipv6.getSendtime();
algorithm.putAck(ipv6.getPacketLength(), RTTC, ecn);
algorithm.putAck(ipv6.getPacketLength(), RTTns, ecn);
tp.unpark();
// System.out.println("remove:"+aseq.getSequence()+" "+sendMap.size());
Lock lockx = lock;
@@ -162,73 +165,59 @@ public class SendPacketSlidingWindow<K, V extends NetworkPacket> implements Clos
}
}
public V ack(K sequence,boolean ecn,long OWDdown) {
SendItem<V> ipv6;
if ((ipv6 = sendMap.remove(sequence)) != null) {
long newWindow =sendWindowUsed.addAndGet(-ipv6.getPacketLength() );
algorithm.setCurrentWindowUsed(newWindow);
long RTTC = System.nanoTime() - ipv6.getSendtime();
algorithm.putAck(ipv6.getPacketLength(), RTTC,OWDdown, ecn);
tp.unpark();
// System.out.println("remove:"+aseq.getSequence()+" "+sendMap.size());
Lock lockx = lock;
if (lockx != null) {
lockx.lock();
try {
Condition cds = condition;
if (cds != null) {
cds.signalAll();
}
} finally {
lockx.unlock();
}
}
return ipv6.getPacket();
} else {
// System.out.println("miss:"+aseq.getSequence()+" "+sendMap.size());
return null;
}
}
private class ResendChecker implements Runnable {
@Override
public void run() {
while (!closed) {
Collection<SendItem<V>> vals = sendMap.values();
for (Iterator<SendItem<V>> iterator = vals.iterator(); iterator.hasNext();) {
SendItem<V> sitm = (SendItem<V>) iterator.next();
long current = System.nanoTime();
if (current - sitm.getSendtime() > algorithm.getRTO()) {
if (removeAtResend) {
iterator.remove();
long newWindow = sendWindowUsed.addAndGet((int) -sitm.getPacketLength() );
algorithm.setCurrentWindowUsed(newWindow);
tp.unpark();
} else {
sitm.setSendtime(current);
try {
if (closed) {
checkerTask.cancel(false);
// 清理资源
if (closingConsumer != null) {
for (SendItem<V> item : sendMap.values()) {
closingConsumer.accept(item.getPacket());
}
V pktr = sitm.getPacket();
if (resendConsumer != null)
resendConsumer.accept(pktr);
}
}
sendMap.clear();
sendWindowUsed.set(0);
} else {
Set<Map.Entry<K,SendItem<V>>> vals = sendMap.entrySet();
for (Iterator<Map.Entry<K,SendItem<V>>> iterator = vals.iterator(); iterator.hasNext(); ) {
Map.Entry<K,SendItem<V>> entry = (Map.Entry<K,SendItem<V>>) iterator.next();
SendItem<V> sitm=entry.getValue();
long current = System.nanoTime();
long ftime=current - sitm.getSendtime();
if (ftime> algorithm.getRTO()) {
V pktr = sitm.getPacket();
if (removeAtResend) {
SendItem<V> sitm2=sendMap.remove(entry.getKey());
if(sitm2!=null) {
if (resendConsumer != null)
resendConsumer.accept(pktr);
algorithm.putLoss(sitm2.getPacketLength(),ftime);
long newWindow = sendWindowUsed.addAndGet((int) -sitm2.getPacketLength());
algorithm.setCurrentWindowUsed(newWindow);
tp.unpark();
}
} else {
sitm.setSendtime(current);
if (resendConsumer != null)
resendConsumer.accept(pktr);
}
}
}
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
} catch (Exception e) {
e.printStackTrace();
}
// 清理资源
if (closingConsumer != null) {
for (SendItem<V> item : sendMap.values()) {
closingConsumer.accept(item.getPacket());
}
}
sendMap.clear();
sendWindowUsed.set(0);
}
}
@@ -2,6 +2,9 @@ package org.kne.cloud.network.congestion;
import java.util.function.Consumer;
import org.kne.cloud.network.monitor.DelaySampler;
import org.kne.concurrent.SpinLock;
public class Vegas2CongestionAlgorithm implements CongestionAlgorithm,DetnetCongestionAlgorithm {
// 可配置的时延膨胀比上下限 - Vegas2.0核心参数
@@ -11,29 +14,26 @@ public class Vegas2CongestionAlgorithm implements CongestionAlgorithm,DetnetCong
private static final long BURST_TIME = 2000000L;
private static final long BIAS = 1000000L;
private static long MIN_WINDOW = 16384;
private static long MIN_WINDOW = 8192;
private Consumer<Long> windowControlConsumer;
private long MIN_RTTVAR = 30000000L;
private volatile long RTTCurr = 1000000000L; // RTT
private volatile long RTTMin = 1000000000L; // BaseRTT
private volatile long RTTVar = 1000000000L;
private volatile long RTTAvg = 1000000000L;
private volatile long RTTAvg2 = 1000000000L; // 当前平滑RTT
private volatile long RTTTotal = 0;
private volatile long RTTCount = 0;
private volatile long RTO = 1000000000L;
private RTTEstimator rttEstimator=new RTTEstimator();
private volatile long OWDTotal = 0;
private volatile long OWDCount = 0;
private volatile long OWDAvg2 = 1000000000L; // 当前平滑OWD
private volatile long OWDMin = 1000000000L; // BaseRTT
private volatile long window = MIN_WINDOW;
private volatile long window2 = MIN_WINDOW;
private volatile long maxwindow = 99999999999L;
private final SpinLock slock=new SpinLock();
public Vegas2CongestionAlgorithm() {
reset();
}
@@ -58,106 +58,13 @@ public class Vegas2CongestionAlgorithm implements CongestionAlgorithm,DetnetCong
@Override
public void putAck(long packetSize, long RTTns, long OWDup, boolean ecn) {
if(RTTns<0) {
throw new IllegalArgumentException("RTT:"+ RTTns+" is negative!");
}
// 处理ECN信号 - 将其视为强烈的拥塞信号
if (ecn) {
// 当收到ECN时,更激进地减少窗口
window2 = Math.max(0, window2 * 3 / 4);
if (windowControlConsumer != null) {
windowControlConsumer.accept(window2);
}
}
// 更新最小RTT(BaseRTT)
if (RTTns <= RTTMin) {
RTTMin = RTTns;
} else {
// 缓慢适应:当网络路径真正变化时,BaseRTT应能缓慢更新
// 这里使用极慢的衰减因子,只有在持续观测到更低RTT时才快速更新
RTTMin = (RTTMin * 999 + RTTns) / 1000;
}
// 更新最小OWD(BaseOWD)
if (OWDup <= OWDMin) {
OWDMin = OWDup;
} else {
// 缓慢适应:当网络路径真正变化时,BaseRTT应能缓慢更新
// 这里使用极慢的衰减因子,只有在持续观测到更低RTT时才快速更新
OWDMin = (OWDMin * 999 + OWDup) / 1000;
}
// 更新平滑RTT估计
RTTVar = (RTTVar * 3 + Math.abs(RTTAvg - RTTns)) / 4;
RTTAvg = (RTTAvg * 7 + RTTns) / 8;
RTO = RTTAvg + Math.max(MIN_RTTVAR, RTTVar * 4);
RTTTotal += RTTns;
RTTCount++;
OWDTotal += OWDup;
OWDCount++;
long curr = System.nanoTime();
// 每个RTT周期调整一次窗口(使用当前估计的RTT)
if (curr - RTTstartTime > Math.max(BIAS, 2*RTTMin)) {
long RTTCountx=RTTCount;
if (RTTCountx > 0) {
long currentRTT = RTTTotal / RTTCountx;
//RTTAvg2 = (RTTAvg2 * 3 + currentRTT) / 4; // 平滑当前RTT
RTTAvg2=currentRTT;
RTTTotal = 0;
RTTCount = 0;
}
long OWDCountx=OWDCount;
if (OWDCountx > 0) {
long currentOWD = OWDTotal / OWDCountx;
OWDAvg2=currentOWD;
OWDTotal = 0;
OWDCount = 0;
}
// ================== Vegas2.0核心逻辑 ==================
// 1. 计算时延膨胀比
double delayRatio = (double) OWDAvg2 / (double) Math.max(BIAS, OWDMin);
//System.out.println(delayRatio+" "+RTTAvg2/1000000.0+" "+RTTMin/1000000.0);
// 2. 基于比值的窗口调整(取代原来的基于差值的调整)
if (delayRatio > delayUpperBound) {
// 时延过高:减小窗口,减少幅度与超标程度成正比
window2 -= 4096;
} else if (delayRatio < delayLowerBound) {
// 时延过低:增大窗口,增加幅度与低于目标程度成正比
window2 += 4096;
} else {
}
// ===================================================
updateWindowSize();
RTTstartTime = curr;
}
}
@Override
public void putAck(long packetSize, long RTTns, boolean ecn) {
if(RTTns<0) {
throw new IllegalArgumentException("RTT:"+ RTTns+" is negative!");
}
this.RTTCurr=RTTns;
// 处理ECN信号 - 将其视为强烈的拥塞信号
if (ecn) {
// 当收到ECN时,更激进地减少窗口
@@ -167,63 +74,53 @@ public class Vegas2CongestionAlgorithm implements CongestionAlgorithm,DetnetCong
}
}
// 更新最小RTT(BaseRTT)
if (RTTns <= RTTMin) {
RTTMin = RTTns;
} else {
// 缓慢适应:当网络路径真正变化时,BaseRTT应能缓慢更新
// 这里使用极慢的衰减因子,只有在持续观测到更低RTT时才快速更新
RTTMin = (RTTMin * 999 + RTTns) / 1000;
}
// 更新平滑RTT估计
RTTVar = (RTTVar * 3 + Math.abs(RTTAvg - RTTns)) / 4;
RTTAvg = (RTTAvg * 7 + RTTns) / 8;
RTO = RTTAvg + Math.max(MIN_RTTVAR, RTTVar * 4);
rttEstimator.putRTT(RTTns);
RTTTotal += RTTns;
RTTCount++;
if(slock.tryLock())try {
long curr = System.nanoTime();
// 每个RTT周期调整一次窗口(使用当前估计的RTT)
if (curr - RTTstartTime > Math.max(BIAS, 2 * RTTMin)) {
rttEstimator.update();
RTTMin = rttEstimator.getBaseRTT();
long curr = System.nanoTime();
// 每个RTT周期调整一次窗口(使用当前估计的RTT)
if (curr - RTTstartTime > Math.max(BIAS, 2*RTTMin)) {
long RTTCountx=RTTCount;
if (RTTCountx > 0) {
long currentRTT = RTTTotal / RTTCountx;
//RTTAvg2 = (RTTAvg2 * 3 + currentRTT) / 4; // 平滑当前RTT
RTTAvg2=currentRTT;
RTTTotal = 0;
RTTCount = 0;
}
// ================== Vegas2.0核心逻辑 ==================
// 1. 计算时延膨胀比
double delayRatio = (double) RTTAvg2 / (double) Math.max(BIAS, RTTMin);
//System.out.println(delayRatio+" "+RTTAvg2/1000000.0+" "+RTTMin/1000000.0);
// 2. 基于比值的窗口调整(取代原来的基于差值的调整)
if (delayRatio > delayUpperBound) {
// 时延过高:减小窗口,减少幅度与超标程度成正比
window2 -= 4096;
} else if (delayRatio < delayLowerBound) {
// 时延过低:增大窗口,增加幅度与低于目标程度成正比
window2 += 4096;
} else {
long RTTCountx = RTTCount;
if (RTTCountx > 0) {
long currentRTT = RTTTotal / RTTCountx;
//RTTAvg2 = (RTTAvg2 * 3 + currentRTT) / 4; // 平滑当前RTT
RTTAvg2 = currentRTT;
RTTTotal = 0;
RTTCount = 0;
}
// ================== Vegas2.0核心逻辑 ==================
// 1. 计算时延膨胀比
double delayRatio = (double) RTTAvg2 / (double) Math.max(BIAS, RTTMin);
//System.out.println(delayRatio+" "+RTTAvg2/1000000.0+" "+RTTMin/1000000.0);
// 2. 基于比值的窗口调整(取代原来的基于差值的调整)
if (delayRatio > delayUpperBound) {
// 时延过高:减小窗口,减少幅度与超标程度成正比
window2 = window2 - 4096;
} else if (delayRatio < delayLowerBound) {
// 时延过低:增大窗口,增加幅度与低于目标程度成正比
window2 = window2 + 4096;
} else {
//window2=window2+4096
}
// ===================================================
updateWindowSize();
RTTstartTime = curr;
}
// ===================================================
updateWindowSize();
RTTstartTime = curr;
}finally{
slock.unlock();
}
}
@@ -245,12 +142,12 @@ public class Vegas2CongestionAlgorithm implements CongestionAlgorithm,DetnetCong
}
@Override
public void putLoss(long packetSize, long lossns, int losscounter) {
public void putLoss(long packetSize, long lossns) {
}
@Override
public long getRTO() {
return RTO;
return rttEstimator.getRTO();
}
@Override
@@ -269,12 +166,14 @@ public class Vegas2CongestionAlgorithm implements CongestionAlgorithm,DetnetCong
}
public long getBaseRTT() {
return RTTMin;
return rttEstimator.getBaseRTT();
}
public long getCurrentRTT() {
public long getAvgRTT() {
return RTTAvg2;
}
public long getCurrentRTT() {
return RTTCurr;
}
@Override
public void setUpperDelayBound(double upper) {
@@ -295,4 +194,19 @@ public class Vegas2CongestionAlgorithm implements CongestionAlgorithm,DetnetCong
return "Vegas2";
}
@Override
public double getUpperDelayBound() {
return delayUpperBound;
}
@Override
public double getLowerDelayBound() {
return delayLowerBound;
}
@Override
public double getBurstLimit() {
return burstLimit;
}
}
@@ -1,5 +1,8 @@
package org.kne.cloud.network.ipv6;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.function.BiConsumer;
import java.util.function.Supplier;
@@ -10,7 +13,21 @@ public abstract class AbstractIPv6NetworkLink implements IPv6NetworkLink {
private CopyOnWriteArraySet<IPv6LinkStateListener> listeners = new CopyOnWriteArraySet<>();
private SRv6Router srv6Router;
private final boolean loopback;
private final boolean filter;
public AbstractIPv6NetworkLink(boolean loopback) {
super();
this.loopback = loopback;
this.filter = false;
}
public AbstractIPv6NetworkLink(boolean loopback, boolean filter) {
super();
this.loopback = loopback;
this.filter = filter;
}
public SRv6Router getSrv6Router() {
return srv6Router;
@@ -53,8 +70,54 @@ public abstract class AbstractIPv6NetworkLink implements IPv6NetworkLink {
}
private BiConsumer<IPv6NetworkLink, Supplier<IPv6Packet>> receiveConsumer;
private Supplier<Long> costSupplier;
@Override
public void setReceiveConsumer(BiConsumer<IPv6NetworkLink,Supplier< IPv6Packet>> receiveConsumer) {
this.receiveConsumer = receiveConsumer;
}
@Override
public boolean isLoopBack() {
return loopback;
}
public Supplier<Long> getCostSupplier() {
return costSupplier;
}
public void setCostSupplier(Supplier<Long> costSupplier) {
this.costSupplier = costSupplier;
}
@Override
public List<RouteItem> getRouteItems() {
List<RouteItem> rlist = new ArrayList<>();
if(isLoopBack()) {
for(IPv6AddressGroup group:getAddressGroups()) {
rlist.add(new RouteItem(new IPv6AddressGroup(group.getAddress(), 128),
group.getAddress(), this, RouteItem.DIRECT, 0, 0, null, "D"));
}
}
if(isUp()) {
for (Iterator<Neighbor> iteratorx = getNeighborsInfo().iterator(); iteratorx.hasNext();) {
Neighbor addresses = (Neighbor) iteratorx.next();
RouteItem ri = new RouteItem(new IPv6AddressGroup(addresses.getAddress().getAddress(), 128),
addresses.getAddress().getAddress(), this,RouteItem.SRv6_ENDXSID, 0, 128, costSupplier, "D");
rlist.add(ri);
RouteItem ris = new RouteItem(addresses.getLocator(),
addresses.getLocator().getAddress(), this, RouteItem.SRv6_ENDSID, 13, 128,
costSupplier, "D");
rlist.add(ris);
}
}
return rlist;
}
@Override
public boolean isFilter() {
return filter;
}
}
@@ -61,6 +61,10 @@ public final class IPv6Address implements Comparable<IPv6Address> {
((long)(bytes[15] & 0xFF));
}
public IPv6Address(String string) throws UnknownHostException {
this((Inet6Address) Inet6Address.getByName(string));
}
/**
* 从两个long创建IPv6地址
*/
@@ -67,7 +67,16 @@ public class IPv6AddressGroup implements Comparable<IPv6AddressGroup>{
prefixLength=in.read();
}
public boolean checkMatch(IPv6Address address2) {
return address2.equals(address.maskWith(IPv6Address.createMask(prefixLength)));
IPv6Address cmsk= IPv6Address.createMask(prefixLength);
return address2.maskWith(cmsk).equals(address.maskWith(cmsk));
}
/**
* 返回裁剪后的路由表地址(主机位清零)。
* 例如 2001:db8:1::100/64 → 2001:db8:1::/64
*/
public IPv6AddressGroup toNetworkRoute() {
IPv6Address mask = IPv6Address.createMask(prefixLength);
IPv6Address maskedAddr = address.maskWith(mask);
return new IPv6AddressGroup(maskedAddr, prefixLength);
}
}
@@ -30,4 +30,5 @@ public interface IPv6NetworkLink {
public void addIPv6LinkStateListener(IPv6LinkStateListener listener);
public void removeIPv6LinkStateListener(IPv6LinkStateListener listener);
public boolean isFilter();
}
@@ -1378,7 +1378,7 @@ public class IPv6Packet extends NetworkPacket {
// 重路由计数器
private AtomicInteger rerouteCounter = new AtomicInteger(0);
public AtomicInteger getRerouteCounter() {
public AtomicInteger getRouteCounter() {
return rerouteCounter;
}
@@ -41,6 +41,7 @@ public class IPv6TUNLoopbackNetworkLink extends AbstractIPv6NetworkLink
private Lock slok = new SpinLock();
public IPv6TUNLoopbackNetworkLink(String name,IPv6AddressGroup hostAddress, int mtu, List<InetAddress> dns) throws IOException {
super(true);
if (tun != null)
throw new IllegalStateException("already open!");
try {
@@ -204,13 +205,6 @@ public class IPv6TUNLoopbackNetworkLink extends AbstractIPv6NetworkLink
LockSupport.unpark(tr);
}
@Override
public boolean isLoopBack() {
return true;
}
@Override
public List<Neighbor> getNeighborsInfo() {
List<Neighbor> hs = new ArrayList<>();
@@ -219,10 +213,6 @@ public class IPv6TUNLoopbackNetworkLink extends AbstractIPv6NetworkLink
return hs;
}
/*
* @Override public boolean isCongress(IPv6Packet iPv6Packet,double scale) {
* return sendQueue.size() > 1000*scale; }
*/
@Override
public String getName() {
@@ -255,27 +245,4 @@ public class IPv6TUNLoopbackNetworkLink extends AbstractIPv6NetworkLink
return List.of(hostAddress);
}
@Override
public List<RouteItem> getRouteItems() {
List<RouteItem> rlist = new ArrayList<>();
for (IPv6AddressGroup grp : getAddressGroups()) {
rlist.add(new RouteItem(new IPv6AddressGroup(grp.getAddress(), 128), grp.getAddress(), this, "Direct", 0, 1,
null, "D", true));
}
for (Iterator<Neighbor> iteratorx = getNeighborsInfo().iterator(); iteratorx.hasNext();) {
Neighbor addresses = (Neighbor) iteratorx.next();
RouteItem ri = new RouteItem(new IPv6AddressGroup(addresses.getAddress().getAddress(), 128),
addresses.getAddress().getAddress(), this, "Direct", 0, 128, null, "D", false);
rlist.add(ri);
RouteItem ris = new RouteItem(addresses.getLocator(), addresses.getLocator().getAddress(), this,
"KLALB SRv6", 13, 128, null, "D", false);
rlist.add(ris);
}
return rlist;
}
}
@@ -32,6 +32,7 @@ public class LoopbackIPv6NetworkLink extends AbstractIPv6NetworkLink implements
//new Inet6AddressGroup(loopbackAddress, 128) new Inet6AddressGroup((Inet6Address) Inet6Address.getByName("::1"), 128)
public LoopbackIPv6NetworkLink(List<IPv6AddressGroup> addressGroupsx,SRv6Router router) {
super(true);
this.addressGroups .addAll( addressGroupsx);
}
@@ -54,10 +55,7 @@ public class LoopbackIPv6NetworkLink extends AbstractIPv6NetworkLink implements
}
}
@Override
public boolean isLoopBack() {
return true;
}
@Override
public List<Neighbor> getNeighborsInfo() {
@@ -93,29 +91,6 @@ public class LoopbackIPv6NetworkLink extends AbstractIPv6NetworkLink implements
return rerouteConsumer;
}
@Override
public List<RouteItem> getRouteItems() {
List<RouteItem> rlist = new ArrayList<>();
for(IPv6AddressGroup group:addressGroups) {
rlist.add(new RouteItem(new IPv6AddressGroup(group.getAddress(), 128),
group.getAddress(), this, "Direct", 0, 0, null, "D", true));
}
for (Iterator<Neighbor> iteratorx = getNeighborsInfo().iterator(); iteratorx.hasNext();) {
Neighbor addresses = (Neighbor) iteratorx.next();
RouteItem ri = new RouteItem(new IPv6AddressGroup(addresses.getAddress().getAddress(), 128),
addresses.getAddress().getAddress(), this, "Direct", 0, 128, null, "D",
false);
rlist.add(ri);
RouteItem ris = new RouteItem(addresses.getLocator(),
addresses.getLocator().getAddress(), this, "KLALB SRv6", 13, 128,
null, "D", false);
rlist.add(ris);
}
return rlist;
}
public SRv6Router getRouter() {
+9 -10
View File
@@ -4,6 +4,11 @@ import java.util.Objects;
import java.util.function.Supplier;
public class RouteItem implements Comparable<RouteItem>,Cloneable{
public static final String DIRECT="Direct";
public static final String SRv6_ENDSID="SRv6 ENDSID";
public static final String SRv6_ENDXSID="SRv6 ENDXSID";
public static final String KLALB_SRv6 = "KLALB_SRv6";
private IPv6AddressGroup destination;
private IPv6Address nexthop;
private IPv6NetworkLink destlink;
@@ -12,7 +17,6 @@ public class RouteItem implements Comparable<RouteItem>,Cloneable{
private long cost;
private String flag;
private Supplier<Long> costSupplier;
private boolean isLoopback;
public String getFlag() {
return flag;
}
@@ -34,21 +38,20 @@ public class RouteItem implements Comparable<RouteItem>,Cloneable{
&& pre == other.pre && Objects.equals(proto, other.proto);
}
public RouteItem(IPv6AddressGroup destination, IPv6Address nexthop, IPv6NetworkLink destlink, String proto, int pre,
long cost,String flag,boolean isLoopback) {
long cost,String flag) {
super();
this.destination = destination;
this.destination = destination.toNetworkRoute();
this.nexthop = nexthop;
this.destlink = destlink;
this.proto = proto;
this.pre = pre;
this.cost = cost;
this.flag=flag;
this.isLoopback=isLoopback;
}
public RouteItem(IPv6AddressGroup destination, IPv6Address nexthop, IPv6NetworkLink destlink, String proto, int pre,
long cost,Supplier<Long> costSupplier,String flag,boolean isLoopback) {
long cost,Supplier<Long> costSupplier,String flag) {
super();
this.destination = destination;
this.destination = destination.toNetworkRoute();
this.nexthop = nexthop;
this.destlink = destlink;
this.proto = proto;
@@ -56,7 +59,6 @@ public class RouteItem implements Comparable<RouteItem>,Cloneable{
this.cost = cost;
this.costSupplier=costSupplier;
this.flag=flag;
this.isLoopback=isLoopback;
}
public IPv6AddressGroup getDestination() {
return destination;
@@ -73,9 +75,6 @@ public class RouteItem implements Comparable<RouteItem>,Cloneable{
public int getPre() {
return pre;
}
public boolean isLoopback() {
return isLoopback;
}
@Override
public String toString() {
return getDestination()+"\t"+getProto()+"\t"+getPre()+"\t"+getCost()+"\t"+getFlag()+"\t"+getNexthop()+"\t"+getDestlink().getName();
@@ -13,6 +13,10 @@ import org.kne.cloud.network.ipv6.RouteItem;
public abstract class AbstractControlledIPv6NetworkLink extends AbstractIPv6NetworkLink implements ControlledIPv6NetworkLink{
public AbstractControlledIPv6NetworkLink(boolean loopback) {
super(loopback);
}
}
+1 -1
View File
@@ -12,7 +12,7 @@ public class CONST {
public static final String KLALB_S_RV6 = "KLALB_SRv6";
public static final String KLTP_VIRTUAL_SOCKET_NAME="kltp";
public static final int itemwidth = 1000;
public static final int linepanelheight = 41;

Some files were not shown because too many files have changed in this diff Show More