test(java-oracle): TcpRun records TCP as Java FILIUS runs it
Three stories of socket calls on tcp-lan's network (exchange, closedPort, lostData): every frame with TCP flags and numbers, when each call started, returned or failed, and each socket's states. Network building and the packet log move from DhcpRun to ScenarioNetwork. Measured: 3 RTT per attempt (8250 ms at 90 %), an orderly close after a failed send, TIME_WAIT of 3 RTT, the passive side closing by itself on FIN, one SYN only (Java's retry loop ends after the first timeout). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
// Runs a story of TCP calls on a core scenario's network in Java FILIUS
|
||||
// (ScenarioNetwork) and records what really happens: every frame each card
|
||||
// saw, when each call started, returned or failed, and every state each
|
||||
// socket went through (polled every few milliseconds).
|
||||
//
|
||||
// TcpRun <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) and run the calls as FILIUS' applications do, the
|
||||
// server's in a thread of its own:
|
||||
// exchange the client connects to port 80, sends "GET /", the server
|
||||
// answers with 3000 x (three segments); the client closes,
|
||||
// the server closes a second after it saw CLOSE_WAIT
|
||||
// closedPort a client connects to port 81, where nothing listens
|
||||
// lostData connected, all frames are lost (Verbindung.setDrop) while
|
||||
// the client sends; loss ends when senden fails
|
||||
|
||||
import filius.hardware.Verbindung;
|
||||
import filius.software.transportschicht.ServerSocket;
|
||||
import filius.software.transportschicht.Socket;
|
||||
import filius.software.transportschicht.TCPSocket;
|
||||
import filius.software.vermittlungsschicht.IpPaket;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class TcpRun {
|
||||
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 ScenarioNetwork network;
|
||||
static final List<Object[]> events = new ArrayList<>();
|
||||
static final Map<String, List<Object[]>> states = new LinkedHashMap<>();
|
||||
static final Map<String, TCPSocket> watched = new LinkedHashMap<>();
|
||||
|
||||
static synchronized void event(String what) {
|
||||
events.add(new Object[] { System.currentTimeMillis() - network.start, what });
|
||||
}
|
||||
|
||||
static synchronized void watch(String label, Socket socket) {
|
||||
watched.put(label, (TCPSocket) socket);
|
||||
states.put(label, new ArrayList<>());
|
||||
poll();
|
||||
}
|
||||
|
||||
static synchronized void poll() {
|
||||
for (Map.Entry<String, TCPSocket> entry : watched.entrySet()) {
|
||||
List<Object[]> seen = states.get(entry.getKey());
|
||||
String state = entry.getValue().getStateAsString();
|
||||
if (seen.isEmpty() || !seen.get(seen.size() - 1)[1].equals(state)) {
|
||||
seen.add(new Object[] { System.currentTimeMillis() - network.start, state });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface Call {
|
||||
Object run() throws Exception;
|
||||
}
|
||||
|
||||
/** A call with its start and its end (result or exception) in the events. */
|
||||
static Object call(String label, Call call) {
|
||||
event(label + " called");
|
||||
try {
|
||||
Object result = call.run();
|
||||
event(label + " returned" + (result instanceof String ? " " + ((String) result).length() + " characters" : ""));
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
event(label + " failed " + e.getClass().getSimpleName());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Thread server(Call body) {
|
||||
Thread thread = new Thread(() -> {
|
||||
try {
|
||||
body.run();
|
||||
} catch (Exception e) {
|
||||
event("server failed " + e.getClass().getSimpleName());
|
||||
}
|
||||
});
|
||||
thread.start();
|
||||
return thread;
|
||||
}
|
||||
|
||||
static String repeat(String text, int count) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
for (int i = 0; i < count; i++) result.append(text);
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
network = ScenarioNetwork.read(args[0]);
|
||||
String story = args[1];
|
||||
long duration = Long.parseLong(args[2]);
|
||||
network.start();
|
||||
Thread poller = new Thread(() -> {
|
||||
while (true) {
|
||||
poll();
|
||||
try {
|
||||
Thread.sleep(2);
|
||||
} catch (InterruptedException e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
poller.setDaemon(true);
|
||||
poller.start();
|
||||
long end = network.start + duration;
|
||||
|
||||
switch (story) {
|
||||
case "exchange": {
|
||||
server(() -> {
|
||||
ServerSocket listener = new ServerSocket(network.system(SERVER), 80, IpPaket.TCP, new ArrayList<>());
|
||||
Socket accepted = (Socket) call("server oeffnen", listener::oeffnen);
|
||||
watch("server", accepted);
|
||||
call("server empfangen", accepted::empfangen);
|
||||
call("server senden", () -> {
|
||||
accepted.senden(repeat("x", 3000));
|
||||
return null;
|
||||
});
|
||||
while (!((TCPSocket) accepted).getStateAsString().equals("CLOSE_WAIT")) Thread.sleep(2);
|
||||
Thread.sleep(1000);
|
||||
call("server schliessen", () -> {
|
||||
accepted.schliessen();
|
||||
return null;
|
||||
});
|
||||
return null;
|
||||
});
|
||||
Thread.sleep(500);
|
||||
TCPSocket client = new TCPSocket(network.system(CLIENT), "192.168.0.10", 80);
|
||||
watch("client", client);
|
||||
call("client verbinden", () -> {
|
||||
client.verbinden();
|
||||
return null;
|
||||
});
|
||||
call("client senden", () -> {
|
||||
client.senden("GET /");
|
||||
return null;
|
||||
});
|
||||
call("client empfangen", client::empfangen);
|
||||
call("client schliessen", () -> {
|
||||
client.schliessen();
|
||||
return null;
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "closedPort": {
|
||||
Thread.sleep(500);
|
||||
TCPSocket client = new TCPSocket(network.system(OTHER_CLIENT), "192.168.0.10", 81);
|
||||
watch("client", client);
|
||||
call("client verbinden", () -> {
|
||||
client.verbinden();
|
||||
return null;
|
||||
});
|
||||
break;
|
||||
}
|
||||
case "lostData": {
|
||||
server(() -> {
|
||||
ServerSocket listener = new ServerSocket(network.system(SERVER), 80, IpPaket.TCP, new ArrayList<>());
|
||||
Socket accepted = (Socket) call("server oeffnen", listener::oeffnen);
|
||||
watch("server", accepted);
|
||||
return null;
|
||||
});
|
||||
Thread.sleep(500);
|
||||
TCPSocket client = new TCPSocket(network.system(CLIENT), "192.168.0.10", 80);
|
||||
watch("client", client);
|
||||
call("client verbinden", () -> {
|
||||
client.verbinden();
|
||||
return null;
|
||||
});
|
||||
Thread.sleep(500);
|
||||
event("loss on");
|
||||
Verbindung.setDrop(true);
|
||||
call("client senden", () -> {
|
||||
client.senden("verloren");
|
||||
return null;
|
||||
});
|
||||
Verbindung.setDrop(false);
|
||||
event("loss off");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new IllegalArgumentException("unknown story " + story);
|
||||
}
|
||||
|
||||
long rest = end - System.currentTimeMillis();
|
||||
if (rest > 0) Thread.sleep(rest);
|
||||
poll();
|
||||
|
||||
ScenarioNetwork.Json out = new ScenarioNetwork.Json();
|
||||
out.open('{');
|
||||
network.writeFrames(out);
|
||||
synchronized (TcpRun.class) {
|
||||
out.key("events").open('[');
|
||||
for (Object[] each : events) out.open('[').number((Long) each[0]).string((String) each[1]).close(']');
|
||||
out.close(']');
|
||||
out.key("states").open('{');
|
||||
for (Map.Entry<String, List<Object[]>> entry : states.entrySet()) {
|
||||
out.key(entry.getKey()).open('[');
|
||||
for (Object[] each : entry.getValue()) out.open('[').number((Long) each[0]).string((String) each[1]).close(']');
|
||||
out.close(']');
|
||||
}
|
||||
out.close('}');
|
||||
}
|
||||
out.close('}');
|
||||
network.stop();
|
||||
ScenarioNetwork.write(args[3], out);
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user