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>
189 lines
8.0 KiB
Java
189 lines
8.0 KiB
Java
// 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);
|
|
}
|
|
}
|