feat(core): Gnutella search and download after Java, measured against Java
GnutellaRun adds a line of four peers: a search goes on as a query, each peer answers with a hit for each matching file and the hits come back the way the query came; a hit's file is fetched by HTTP on port 6346. The file type names of the model move from the project export and import into the core, so that the download uses them too. Departures from Java: a peer that sent hits keeps its connection, so that the hits of farther peers come back too (Java closes it and loses them), and sizes are in characters. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -905,6 +905,37 @@ Gemessen (02.10.2026):
|
||||
herum, einige zehn Millisekunden auseinander: Javas Thread-Takt über
|
||||
mehrere Sprünge.
|
||||
|
||||
## Gnutella, Suche und Download (F3.6b)
|
||||
|
||||
Zwei weitere Abläufe von `GnutellaRun`: die Teilnehmer bilden über ihre
|
||||
Höchstzahlen eine Linie .20 – .30 – .31 – 10.0.0.2; .31 teilt
|
||||
`notizen.txt` (in `searchBoth` auch `lied.txt`), 10.0.0.2 `Lied der
|
||||
Woche.txt`. Geschrieben werden zusätzlich die Suchergebnisse bei jeder
|
||||
Meldung und am Ende die Dateien in `/peer2peer` jedes Programms.
|
||||
|
||||
Gemessen (02.10.2026):
|
||||
|
||||
* Suche (`sucheDatei`): die Ergebnisse geleert, eine Query
|
||||
`guid//0x80//0//8//Bits//1//Worte` (ein leeres Feld als ein Leerzeichen)
|
||||
über je eine neue Verbindung an jeden Teilnehmer außer sich selbst. Wer
|
||||
sie noch nicht kennt, schickt sie (hops+1, ttl−1) an seine Teilnehmer
|
||||
außer dem Absender und antwortet für jede Datei in `/peer2peer`, deren
|
||||
Name die Worte enthält (Groß/klein egal), mit einem Treffer
|
||||
`guid//0x81//0//8//Bits//Anzahl//6346//IP//2//Name: Größe B// `; die
|
||||
Bitlänge rechnet Java, bevor der Name eingetragen ist. Treffer gehen
|
||||
(hops+1, ttl−1) zurück wie Pongs; das Ergebnis heißt `IP/Name: Größe B`,
|
||||
jedes einmal.
|
||||
* Fehler Javas: wer selbst Treffer schickt, schließt danach die
|
||||
Verbindung; die Treffer der Teilnehmer, an die er die Query
|
||||
weitergeschickt hat, gehen verloren (`searchBoth`: der von 10.0.0.2). Die
|
||||
Größe ist wieder die des Base64-dekodierten Textes („Hallo Welt“ = 6 B),
|
||||
ebenso die Bytes im Pong.
|
||||
* Download (`herunterladenDatei`): `GET Name HTTP/1.1` mit `Host:` an Port
|
||||
6346 des Rechners (der Name bis zum `:`, Leerzeichen kodiert), Antwort
|
||||
`HTTP/1.1 200 OK` mit `Content-type:` gleich dem Dateityp und dem Inhalt,
|
||||
sonst `HTTP/1.1 404 Not Found`; die Datei landet in `/peer2peer`, die
|
||||
Verbindung schließt der Client nach der Antwort.
|
||||
|
||||
## Datei-Explorer, Texteditor, Bildbetrachter (F3.5b)
|
||||
|
||||
`FileCases` tut, was die Fenster dieser Programme mit den Dateien eines
|
||||
|
||||
@@ -286,9 +286,9 @@ for language in de en; do
|
||||
test -s "$output"
|
||||
done
|
||||
|
||||
# The Gnutella program joining peers on terminal-net (GnutellaRun;
|
||||
# The Gnutella program joining peers, searching and downloading on terminal-net (GnutellaRun;
|
||||
# packages/core/test/gnutellaJava.test.ts).
|
||||
for run in pair:6000 chain:9000 twoPaths:15000 full:11000 reset:7000 errors:40000; do
|
||||
for run in pair:6000 chain:9000 twoPaths:15000 full:11000 reset:7000 errors:40000 search:29000 searchBoth:13000; do
|
||||
story="${run%%:*}"
|
||||
output="$root/packages/core/test/scenarios/gnutella-java-$story.java.json"
|
||||
rm -f "$output"
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
// Runs a story of FILIUS' Gnutella program (PeerToPeerAnwendung with its
|
||||
// PeerToPeerServer on port 6346 and PeerToPeerClient) on terminal-net in Java
|
||||
// FILIUS and records what really happens: every frame each card saw, Java's
|
||||
// tables of them, each program's known peers whenever it tells its window,
|
||||
// and at the end each program's known peers and search results.
|
||||
// tables of them, each program's known peers and search results whenever it
|
||||
// tells its window, and at the end its peers, results and files in /peer2peer.
|
||||
//
|
||||
// GnutellaRun <scenario.json> <story> <milliseconds> <out.json>
|
||||
//
|
||||
// terminal-net: the program on 192.168.0.20, .30, .31 and 10.0.0.2 (behind
|
||||
// the router), each keeping at most three peers unless the story says
|
||||
// otherwise; .10 has none. A call comes as the program's window makes it
|
||||
// (beitretenNetzwerk, resetNetwork); the acts are in the output.
|
||||
// (beitretenNetzwerk, resetNetwork, sucheDatei, herunterladenDatei,
|
||||
// loescheSuchergebnisse); the acts are in the output.
|
||||
// pair .30 joins .31
|
||||
// chain .30 joins .31, then .20 joins .30 (the ping goes on to .31)
|
||||
// twoPaths .31 joins 10.0.0.2, .30 joins .31 and 10.0.0.2, then .20 joins
|
||||
@@ -17,13 +18,22 @@
|
||||
// full .31 keeps two peers, .30 and .20 one; .30, .20 and 10.0.0.2 join .31
|
||||
// reset .30 joins .31, resets, joins again
|
||||
// errors .20 joins itself, .10 (no program), .99 (no one)
|
||||
// search a line .20 - .30 - .31 - 10.0.0.2 (by their numbers of peers);
|
||||
// .31 shares notizen.txt, 10.0.0.2 "Lied der Woche.txt"; .20
|
||||
// searches "woche" (10.0.0.2's hit comes back through .31 and
|
||||
// .30) and downloads it, searches "notiz" and downloads it,
|
||||
// again after .31 removed it, searches "xyz", clears
|
||||
// searchBoth the line, .31 also shares lied.txt; .20 searches "lied"
|
||||
|
||||
import filius.software.dateiaustausch.PeerToPeerAnwendung;
|
||||
import filius.software.system.Datei;
|
||||
import filius.software.system.Dateisystem;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.swing.tree.DefaultMutableTreeNode;
|
||||
|
||||
public class GnutellaRun {
|
||||
static final String CLIENT = "00000000-0000-0000-0000-000000000401";
|
||||
@@ -72,6 +82,25 @@ public class GnutellaRun {
|
||||
acts.add(new Object[] { 3000L, WEB, "reset" });
|
||||
acts.add(new Object[] { 3500L, WEB, "join 192.168.0.31" });
|
||||
break;
|
||||
case "search":
|
||||
acts.add(new Object[] { 500L, CLIENT, "join 192.168.0.30" });
|
||||
acts.add(new Object[] { 3000L, WEB, "join 192.168.0.31" });
|
||||
acts.add(new Object[] { 5500L, FAR, "join 192.168.0.31" });
|
||||
acts.add(new Object[] { 8000L, CLIENT, "search woche" });
|
||||
acts.add(new Object[] { 11000L, CLIENT, "download 0" });
|
||||
acts.add(new Object[] { 14000L, CLIENT, "search notiz" });
|
||||
acts.add(new Object[] { 17000L, CLIENT, "download 0" });
|
||||
acts.add(new Object[] { 20000L, ECHO, "remove notizen.txt" });
|
||||
acts.add(new Object[] { 20500L, CLIENT, "download 0" });
|
||||
acts.add(new Object[] { 23000L, CLIENT, "search xyz" });
|
||||
acts.add(new Object[] { 26000L, CLIENT, "clear" });
|
||||
break;
|
||||
case "searchBoth":
|
||||
acts.add(new Object[] { 500L, CLIENT, "join 192.168.0.30" });
|
||||
acts.add(new Object[] { 3000L, WEB, "join 192.168.0.31" });
|
||||
acts.add(new Object[] { 5500L, FAR, "join 192.168.0.31" });
|
||||
acts.add(new Object[] { 8000L, CLIENT, "search lied" });
|
||||
break;
|
||||
case "errors":
|
||||
acts.add(new Object[] { 500L, CLIENT, "join 192.168.0.20" });
|
||||
acts.add(new Object[] { 1000L, CLIENT, "join 192.168.0.10" });
|
||||
@@ -93,18 +122,43 @@ public class GnutellaRun {
|
||||
for (String host : PEERS) {
|
||||
network.system(host).installApp(CLASS, false);
|
||||
PeerToPeerAnwendung program = (PeerToPeerAnwendung) network.system(host).holeSoftware(CLASS);
|
||||
program.setMaxTeilnehmerZahl(!story.equals("full") ? 3 : host.equals(ECHO) ? 2 : host.equals(FAR) ? 3 : 1);
|
||||
program.hinzuBeobachter((observable, data) -> event(host, "peers", String.join(",", program.holeBekanntePeerToPeerTeilnehmer())));
|
||||
if (story.equals("full")) program.setMaxTeilnehmerZahl(host.equals(ECHO) ? 2 : host.equals(FAR) ? 3 : 1);
|
||||
else if (story.startsWith("search")) program.setMaxTeilnehmerZahl(host.equals(CLIENT) || host.equals(FAR) ? 1 : 2);
|
||||
else program.setMaxTeilnehmerZahl(3);
|
||||
program.hinzuBeobachter((observable, data) -> {
|
||||
event(host, "peers", String.join(",", program.holeBekanntePeerToPeerTeilnehmer()));
|
||||
event(host, "results", String.join("|", program.holeErgebnisse()));
|
||||
});
|
||||
programs.put(host, program);
|
||||
}
|
||||
if (story.startsWith("search")) {
|
||||
Dateisystem echoFiles = network.system(ECHO).getDateisystem();
|
||||
if (story.equals("searchBoth")) echoFiles.speicherDatei("/peer2peer", new Datei("lied.txt", "txt", "Hallo Welt"));
|
||||
echoFiles.speicherDatei("/peer2peer", new Datei("notizen.txt", "txt", "Notiz"));
|
||||
network.system(FAR).getDateisystem().speicherDatei("/peer2peer", new Datei("Lied der Woche.txt", "txt", "La la la"));
|
||||
}
|
||||
network.start();
|
||||
for (Object[] act : acts) {
|
||||
sleepUntil((Long) act[0]);
|
||||
String host = (String) act[1];
|
||||
String call = (String) act[2];
|
||||
event(host, "call", call);
|
||||
if (call.startsWith("join ")) programs.get(host).beitretenNetzwerk(call.substring("join ".length()));
|
||||
else programs.get(host).resetNetwork();
|
||||
PeerToPeerAnwendung program = programs.get(host);
|
||||
if (call.startsWith("join ")) program.beitretenNetzwerk(call.substring("join ".length()));
|
||||
else if (call.startsWith("search ")) program.sucheDatei(call.substring("search ".length()));
|
||||
else if (call.startsWith("download ")) {
|
||||
// The window offers only the rows it shows.
|
||||
int row = Integer.parseInt(call.substring("download ".length()));
|
||||
if (row < program.holeErgebnisse().size()) program.herunterladenDatei(row);
|
||||
else event(host, "skipped", call);
|
||||
}
|
||||
else if (call.startsWith("remove ")) {
|
||||
// The file explorer's "delete" on the peer that has the file.
|
||||
Dateisystem files = network.system(host).getDateisystem();
|
||||
DefaultMutableTreeNode folder = files.verzeichnisKnoten("/peer2peer");
|
||||
folder.remove(Dateisystem.verzeichnisKnoten(folder, call.substring("remove ".length())));
|
||||
} else if (call.equals("clear")) program.loescheSuchergebnisse();
|
||||
else program.resetNetwork();
|
||||
}
|
||||
sleepUntil(end);
|
||||
|
||||
@@ -129,6 +183,11 @@ public class GnutellaRun {
|
||||
out.key("results").open('[');
|
||||
for (String result : program.holeErgebnisse()) out.string(result);
|
||||
out.close(']');
|
||||
out.key("files").open('[');
|
||||
for (Datei file : network.system(entry.getKey()).getDateisystem().holeDateien(program.holeVerzeichnis())) {
|
||||
out.open('[').string(file.getName()).string(file.getDateiTyp()).string(file.getDateiInhalt()).close(']');
|
||||
}
|
||||
out.close(']');
|
||||
out.close('}');
|
||||
}
|
||||
out.close('}');
|
||||
|
||||
Reference in New Issue
Block a user