TC$mC$ on erinomainen suunta! Se, ettC$ tuetaan molempia (TCP ja UDP)
dynaamisesti ja automaattisesti, on juuri se tapa, jolla ammattimaiset
P2P-verkot (kuten WebRTC tai BitTorrentin lokaalit protokollat) toimivat
"pellin alla".
TC$ssC$ mallissa vC$ltetC$C$n monimutkaiset ulkoiset riippuvuudet
hyC6dyntC$mC$llC$ kummankin protokollan vahvuuksia:
* **UDP:** Toimii verkon "sydC$menlyC6ntinC$" (GOSSIP/Autodiscovery) ja
**fallback-reittinC$**, koska se on tilaton (connectionless) eikC$ vC$litC$
siitC$, onko yhteys auki vai ei.
* **TCP:** Toimii **pC$C$vC$ylC$nC$** raskaammalle datalle (CHUNKS, isot
JSON-objektit), koska se takaa pakettien jC$rjestyksen ja eheyden.
### Miten automaattinen vaihto ja kC$ttely (Handshake) toimii?
1. **KC$ynnistys:** Instanssi etsii vapaan portin (esim. `33000`) ja avaa
siihen *sekC$* UDP-kuuntelijan ettC$ TCP-palvelimen.
2. **Discovery (UDP):** Instanssi alkaa huutaa UDP-broadcastilla
GOSSIP-viestejC$ verkkoon (esim. 2 sekunnin vC$lein). ViestissC$ lukee
instanssin ID ja sen TCP-portti.
3. **KC$ttely (Handshake):** Kun Node A kuulee Node B:n UDP-viestin:
* Jos Node A:lla ei ole vielC$ TCP-yhteyttC$ Node B:hen, se yrittC$C$ avata
sen.
* Jos TCP-yhteys onnistuu, tila muuttuu: `connected: TCP`.
4. **Reititys ja Fallback:**
* Kun sovellus lC$hettC$C$ viestin, reititin katsoo peer-taulua.
* Jos TCP on auki, viesti menee sinne.
* Jos TCP menee poikki (yhteysvirhe/timeout), TCP-soketti tuhotaan. Seuraava
viesti menee automaattisesti **UDP:llC$**.
5. **Auto-Reconnect:** Koska UDP-GOSSIP laulaa taustalla jatkuvasti, seuraavan
kerran kun Node B:ltC$ tulee UDP-sydC$menlyC6nti, Node A huomaa, ettC$ TCP
puuttuu, ja **yrittC$C$ automaattista uudelleenkC$ttelyC$**.
### Konkreettinen Node.js Vanilla -toteutus
### Miten sovellus kC$yttC$C$ tC$tC$?
Koska verkkokerros piilottaa TCP/UDP-kompleksisuuden ja auto-fallbackin,
sovelluksen logiikka on pelkkC$C$ "lue laatikkoa" ja "lC$hetC$ viestiC$":
```javascript
import { HybridMeshNode } from './mesh.js';
async function main() {
const node = new HybridMeshNode("MyBot_1");
await node.start(); // Etsii portin 33000-33999, kC$ynnistC$C$ palvelimet
ja gossipin
// 1. KC$ynnistC$ Mailboxin kuuntelija-looppi
(async () => {
for await (const msg of node.inbox) {
// msg.type: 0 = GOSSIP, 1 = OPS, 2 = CHAT, jne.
if (msg.type === 0) {
// Gossip on vain lokitettavaksi tai verkon visualisointiin
// console.log(`[${msg.protocol}] NC$hty peer:`,
msg.senderId);
}
else if (msg.type === 2) {
console.log(`[CHAT saapui via ${msg.protocol}]:
${msg.payload.text}`);
}
}
})();
// 2. Simuloidaan viestin lC$hetystC$ toiselle instanssille
// Todellisuudessa lukisit kohdeID:n peer-taulusta tai broadcastaisit
(targetId: null)
setInterval(() => {
const peers = Array.from(node.peers.keys());
if (peers.length > 0) {
// LC$hetetC$C$n satunnaiselle tunnetulle peerille viesti
node.send(peers[0], 2, ["chat"], { text: "Hei tC$C$ltC$ Node
ykkC6sestC$!" });
}
}, 5000);
}
main();
```
### TC$rkeC$t huomiot (Caveats)
1. **UDP:n kokorajoitus:** UDP-protokollan luonteen takia MTU (Maximum
Transmission Unit) asettaa rajan. Jos kC$ytC$t reitittimen fallbackina
UDP:tC$, viestin koon (`JSON.stringify` tulos) on syytC$ pysyC$ alle **1400
tavussa**. Jos yritC$t lC$hettC$C$ 5 megan kuvan UDP:llC$ tuolla koodilla, se
katoaa bittiavaruuteen. TCP hoitaa isotkin tiedostot automaattisesti, koska se
pilkkoo ja kokoaa ne puskureissa.
2. **TCP:n Framing:** TCP lukee dataa "putkena". Vaikka lC$hettC$isit kaksi
erillistC$ JSON-viestiC$ nopeasti, ne voivat saapua kohteeseen yhtenC$
merkkijonona. Siksi koodissani on `rawMsg.toString().trim().split('
')`. LC$hetyspuolella lisC$tC$C$n aina rivinvaihto (`
`), ja lukupuolella data pilkotaan rivinvaihdoista takaisin erillisiksi
JSON-objekteiksi. TC$mC$ on kevyin mahdollinen tapa erottaa viestit
toisistaan.
MitC$ pidC$t tC$stC$ dynamiikasta? TC$mC$ tarjoaa erittC$in robustin pohjan,
joka parantuu katkoista itsekseen ilman ulkoisia message-brokereita (kuten
RabbitMQ tai Redis).