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:
@@ -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`).
|
||||
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user