feat(core): programs' threads, and FILIUS' echo server and generic client after Java, measured against Java

Programs run as generators that yield what they wait for (threads.ts); the
engine resumes them after every event. TCP connect, send, receive, accept
and name resolution exist as such steps; the blocking calls wrap them.
EchoRun records eight stories in Java FILIUS, frames, tables and each
program's window lines; echoJava.test.ts plays them card by card. Not
taken from Java: a connection without a route hangs for ever; a server
switched off leaves its connections' last FIN unanswered.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 16:27:44 +00:00
co-authored by Claude Opus 5.5
parent 0abce60aef
commit de3a1d4dfe
24 changed files with 13454 additions and 28 deletions
+34
View File
@@ -546,3 +546,37 @@ Gemessen (01.10.2026):
in der Javas Threads senden; mit acht Clients stauen sich die Broadcasts,
ein Frame erreicht die Karten bis zu rund 450 ms versetzt, und Javas
Mitschnitt verliert eigene Frames eines Clients.
## Echo-Server und einfacher Client (F3.1)
`EchoRun` installiert auf dem Netz von `tcp-lan` Filius' Echo-Server
(`ServerBaustein`) auf .10, eingeschaltet auf Port 55555, bevor die
Simulation startet (wie eine Datei ihn lädt), und den einfachen Client
(`ClientBaustein`) auf .20 und .30. Es macht die Aufrufe wie die Fenster:
`verbinden` und `trennen` gehen an den Thread des Programms, `senden` läuft
im Thread des Aufrufers, bis es zurückkehrt. Es schreibt nach
`echo-java-<ablauf>.java.json` die Frames jeder Karte, Javas Tabellen und
jede Zeile, die ein Programm in sein Fenster schreibt (seine Beobachter),
dazu die Aufrufe. Abläufe: `exchange`, `closedPort`, `noRoute`,
`notConnected`, `serverOff`, `reconnect`, `twoClients`, `otherPort`.
Gemessen (01.10.2026):
* `Anwendung.starten` meldet seinen Beobachtern `null`; das Fenster
überspringt es.
* Der Client merkt die Verbindung erst beim nächsten Blick
(`TCPSocket.verbinden` schläft 100 ms), der Server ebenso eine neue
Verbindung (`ServerSocket.oeffnen`) und dass er ausgeschaltet wurde
(„Server-Socket geschlossen“ rund 20 ms nach dem Ausschalten).
* Ohne Route zum Ziel stirbt der Socket-Thread des Clients an der
`RouteNotFoundException`; `verbinden` wartet ewig, das Fenster bleibt
stumm, kein Frame geht hinaus.
* Ausgeschaltet schließt der Server seine Verbindungen mit FIN, gibt aber
zugleich den Port frei (`ServerSocket.schliessen`); das FIN des Clients
findet keinen Socket mehr und bleibt ohne ACK.
* Aus- und sofort wieder eingeschaltet (ohne Pause) merkt Javas Schleife das
Ausschalten nicht und lauscht weiter auf dem alten Port, auch wenn er
geändert wurde. Wer im Fenster den Port wechselt, lässt Sekunden
dazwischen; so misst es `otherPort`.
* Der Client sendet nur ein SYN (wie bei `TcpRun`, `closedPort`).
+13 -1
View File
@@ -21,7 +21,7 @@ if [ ! -f "$upstream/target/filius.jar" ]; then
fi
classpath="$upstream/target/filius.jar:$upstream/target/lib/*"
mkdir -p "$here/.build"
javac -encoding UTF-8 -d "$here/.build" -cp "$classpath" "$here/src/FlsOracle.java" "$here/src/GetterDefaults.java" "$here/src/IpCheckCases.java" "$here/src/DnsHostsCases.java" "$here/src/EmailFileCases.java" "$here/src/BeansDump.java" "$here/src/Base64Cases.java" "$here/src/ScenarioNetwork.java" "$here/src/DhcpRun.java" "$here/src/TcpRun.java" "$here/src/DnsRun.java" "$here/src/ArpRun.java" "$here/src/RipRun.java" "$here/src/NatRun.java" "$here/src/WlanRun.java" "$here/src/ExampleRun.java"
javac -encoding UTF-8 -d "$here/.build" -cp "$classpath" "$here/src/FlsOracle.java" "$here/src/GetterDefaults.java" "$here/src/IpCheckCases.java" "$here/src/DnsHostsCases.java" "$here/src/EmailFileCases.java" "$here/src/BeansDump.java" "$here/src/Base64Cases.java" "$here/src/ScenarioNetwork.java" "$here/src/DhcpRun.java" "$here/src/TcpRun.java" "$here/src/DnsRun.java" "$here/src/ArpRun.java" "$here/src/RipRun.java" "$here/src/NatRun.java" "$here/src/WlanRun.java" "$here/src/ExampleRun.java" "$here/src/EchoRun.java"
# FILIUS writes its settings below $HOME; keep them out of the real one.
mkdir -p "$here/.build/home"
# Removed first: a crashed run must not leave the previous result standing.
@@ -194,6 +194,18 @@ for story in pingWired pingWireless pingWithoutCable pingFromWithoutCable; do
test -s "$output"
done
# The echo server and the generic client as Java FILIUS runs them, on
# tcp-lan's network (EchoRun); frames and each program's window lines.
for run in exchange:12000 closedPort:20000 noRoute:8000 notConnected:3000 serverOff:14000 reconnect:12000 \
twoClients:11000 otherPort:22000; do
story="${run%%:*}"
output="$root/packages/core/test/scenarios/echo-java-$story.java.json"
rm -f "$output"
HOME="$here/.build/home" xvfb-run -a java -cp "$classpath:$here/.build" EchoRun \
"$root/packages/core/test/scenarios/tcp-lan.scenario.json" "$story" "${run##*:}" "$output" 2>&1 | grep -v 'logback\|^$' || true
test -s "$output"
done
# FILIUS' own examples as Java FILIUS runs them, opened and started as FILIUS
# does (ExampleRun): the acceptance of F2 (packages/fls/test/examplesJava.test.ts).
for run in internetwork_komplett:routing:12000 einfaches_rechnernetz_komplett:dhcp:20000 dns_server:dns:12000 \
+188
View File
@@ -0,0 +1,188 @@
// Runs a story of FILIUS' echo server (ServerBaustein) and generic client
// (ClientBaustein) on a core scenario's network in Java FILIUS
// (ScenarioNetwork) and records what really happens: every frame each card
// saw and every line each program wrote into its window (its observers).
//
// EchoRun <scenario.json> <story> <milliseconds> <out.json>
//
// The stories use the network of tcp-lan (server 192.168.0.10, clients
// 192.168.0.20 and .30). The server program runs on .10, switched on
// (aktiv) and on its port 55555 before the simulation starts, as a file
// loads it; each client program on .20 and .30. The calls come as the
// windows make them: verbinden and trennen hand over to the program's
// thread, senden runs in the caller's thread (the window's) until it returns.
// exchange .20 connects, sends "Hallo" and "Welt", disconnects
// closedPort .20 connects to .30, where no server runs
// noRoute .20 connects to 10.0.0.1, outside its network, no gateway
// notConnected .20 sends without a connection
// serverOff connected, the server is switched off, .20 sends; the
// server is switched on again, .20 connects and sends
// reconnect .20 connects, disconnects, connects again and sends
// twoClients .20 and .30 connected at once, each sends, each disconnects
// otherPort the server switched off, a second later over to port 7 and
// on (as its window does it); .20 connects to 7 and sends, .30
// connects to 55555 (no longer served)
import filius.software.clientserver.ClientBaustein;
import filius.software.clientserver.ServerBaustein;
import java.util.ArrayList;
import java.util.List;
public class EchoRun {
static final String SERVER = "00000000-0000-0000-0000-000000000101";
static final String CLIENT = "00000000-0000-0000-0000-000000000102";
static final String OTHER_CLIENT = "00000000-0000-0000-0000-000000000103";
static final String SERVER_CLASS = "filius.software.clientserver.ServerBaustein";
static final String CLIENT_CLASS = "filius.software.clientserver.ClientBaustein";
static ScenarioNetwork network;
static final List<Object[]> events = new ArrayList<>();
static synchronized void event(String who, String what) {
events.add(new Object[] { System.currentTimeMillis() - network.start, who, what });
}
static void sleepUntil(long milliseconds) throws InterruptedException {
long rest = network.start + milliseconds - System.currentTimeMillis();
if (rest > 0) Thread.sleep(rest);
}
static ClientBaustein client(String nodeId) {
return (ClientBaustein) network.system(nodeId).holeSoftware(CLIENT_CLASS);
}
static void connect(String who, ClientBaustein client, String address, int port) {
event(who, "call verbinden " + address + ":" + port);
client.setZielIPAdresse(address);
client.setZielPort(port);
client.verbinden();
}
static void send(String who, ClientBaustein client, String text) {
event(who, "call senden " + text);
client.senden(text);
event(who, "returned senden");
}
static void disconnect(String who, ClientBaustein client) {
event(who, "call trennen");
client.trennen();
}
public static void main(String[] args) throws Exception {
network = ScenarioNetwork.read(args[0]);
String story = args[1];
long end = Long.parseLong(args[2]);
network.system(SERVER).installApp(SERVER_CLASS, false);
network.system(CLIENT).installApp(CLIENT_CLASS, false);
network.system(OTHER_CLIENT).installApp(CLIENT_CLASS, false);
ServerBaustein server = (ServerBaustein) network.system(SERVER).holeSoftware(SERVER_CLASS);
server.setPort(55555);
server.setAktiv(true);
// The windows: opened after loading, so they see what follows.
server.hinzuBeobachter((observable, data) -> event("server", String.valueOf(data)));
client(CLIENT).hinzuBeobachter((observable, data) -> event("client", String.valueOf(data)));
client(OTHER_CLIENT).hinzuBeobachter((observable, data) -> event("other", String.valueOf(data)));
network.start();
ClientBaustein client = client(CLIENT);
ClientBaustein other = client(OTHER_CLIENT);
switch (story) {
case "exchange":
sleepUntil(500);
connect("client", client, "192.168.0.10", 55555);
sleepUntil(2000);
send("client", client, "Hallo");
sleepUntil(4000);
send("client", client, "Welt");
sleepUntil(6000);
disconnect("client", client);
break;
case "closedPort":
sleepUntil(500);
connect("client", client, "192.168.0.30", 55555);
break;
case "noRoute":
sleepUntil(500);
connect("client", client, "10.0.0.1", 55555);
break;
case "notConnected":
sleepUntil(500);
send("client", client, "Hallo");
break;
case "serverOff":
sleepUntil(500);
connect("client", client, "192.168.0.10", 55555);
sleepUntil(2000);
event("server", "call setAktiv false");
server.setAktiv(false);
sleepUntil(4000);
send("client", client, "Hallo");
sleepUntil(6000);
event("server", "call setAktiv true");
server.setAktiv(true);
sleepUntil(7000);
connect("client", client, "192.168.0.10", 55555);
sleepUntil(9000);
send("client", client, "Welt");
break;
case "reconnect":
sleepUntil(500);
connect("client", client, "192.168.0.10", 55555);
sleepUntil(2000);
disconnect("client", client);
sleepUntil(4000);
connect("client", client, "192.168.0.10", 55555);
sleepUntil(6000);
send("client", client, "Hallo");
break;
case "twoClients":
sleepUntil(500);
connect("client", client, "192.168.0.10", 55555);
sleepUntil(1000);
connect("other", other, "192.168.0.10", 55555);
sleepUntil(2000);
send("client", client, "eins");
sleepUntil(3000);
send("other", other, "zwei");
sleepUntil(4000);
disconnect("client", client);
sleepUntil(5000);
disconnect("other", other);
break;
case "otherPort":
sleepUntil(500);
event("server", "call setAktiv false");
server.setAktiv(false);
sleepUntil(1500);
event("server", "call setPort 7");
server.setPort(7);
event("server", "call setAktiv true");
server.setAktiv(true);
sleepUntil(2000);
connect("client", client, "192.168.0.10", 7);
sleepUntil(4000);
send("client", client, "Hallo");
sleepUntil(6000);
connect("other", other, "192.168.0.10", 55555);
break;
default:
throw new IllegalArgumentException("unknown story " + story);
}
sleepUntil(end);
ScenarioNetwork.Json out = new ScenarioNetwork.Json();
out.open('{');
network.writeFrames(out);
synchronized (EchoRun.class) {
out.key("events").open('[');
for (Object[] each : events) out.open('[').number((Long) each[0]).string((String) each[1]).string((String) each[2]).close(']');
out.close(']');
}
out.close('}');
network.stop();
ScenarioNetwork.write(args[3], out);
System.exit(0);
}
}