Cuando necesita transmitir datos a través de UDP, enfrenta sus limitaciones: los datos deben transmitirse en pequeños fragmentos para que quepan en la MTU, los paquetes podrían perderse sin notificación, venir duplicados o en el orden incorrecto.
uTP (protocolo de micro transporte) fue inventado por gente de torrent para transmitir archivos de forma segura a través de UDP. Básicamente, agrega características TCP a UDP: los paquetes perdidos se retransmiten automáticamente, el pedido está garantizado y los duplicados se rechazan.
Esta biblioteca implementa uTP sobre UDP, por lo que cuando se conecta, recibe un objeto de socket que se comporta de manera muy similar a un socket tcp de Node.js. Emite 'datos' al recibir datos, tiene el método .write() para enviar un búfer. Al igual que el socket TCP, este es un flujo de Node.js.
Sin embargo, es posible que la biblioteca no sea compatible con otras implementaciones de uTP (por lo que debe usar esta misma biblioteca en ambos pares) porque agrega la siguiente característica: la misma instancia de una clase se puede usar como servidor y como cliente al mismo tiempo en el mismo puerto. De modo que puede crear un nodo, vincularlo a un puerto y al mismo tiempo comenzar a escuchar las conexiones entrantes y también realizar conexiones salientes desde él.
npm install --save utp-punch
const Node = require('utp-punch');
let server = new Node(socket => {
console.log('server: socket connected');
socket.on('data', data => {
console.log(`server: received '${data.toString()}'`);
socket.write('world');
socket.end();
});
socket.on('end', () => {
console.log('server: socket disconnected');
server.close(); // this is how you terminate node
});
});
server.bind(20000, '127.0.0.1'); // bind to port 20000
server.listen( // run
() => console.log('server: ready')
);
let client = new Node();
client.bind(); // bind to any port
client.connect(20000, '127.0.0.1', socket => {
console.log('client: socket connected');
socket.on('data', data => console.log(`client: received '${data.toString()}'`));
socket.on('end', () => {
console.log('client: socket disconnected');
client.close(); // this is how you terminate node
});
socket.write('hello');
});
Otra técnica que se utiliza aquí es la perforación UDP.
Cuando el servidor y/o el cliente están detrás de NAT, normalmente no tienen una dirección IP de Internet a la que vincularse para recibir conexiones entrantes.
La perforación UDP engaña a los firewalls para que abran un agujero temporal para su usuario, por lo que un puerto en el dispositivo NAT queda vinculado al puerto del servidor/cliente dentro de la LAN.
Para que funcione, tanto el servidor como el cliente deben utilizar un servidor de terceros para averiguar las direcciones IP NAT de cada uno y coordinar el intento de perforación (debe hacerse simultáneamente en el servidor y en el cliente).
Pero cuando se establece la conexión, el servidor de terceros ya no es necesario y nunca se utiliza como retransmisión, todos los datos se transmiten directamente entre este servidor NAT y el cliente.
const Node = require('utp-punch');
let server = new Node();
server.bind(20000);
let client = new Node();
client.bind(30000); // client needs dedicated port
// just as the server
// both server and client must contact a third party server
// which will report their peer NATed address and port to them
let serverAddress, serverPort;
let clientAddress, clientPort;
// up to ten punches:
server.punch(10, clientPort, clientAddress, success => {
// if success is true hole is punched from our side
// nothing to do here as the client will try
// to connect normally when he is also successful
});
client.punch(10, serverPort, serverAddress, success => {
if (success) {
client.connect(serverPort, serverAddress, socket => {
// if the server had also been successful in punching
// this will succeed
});
client.on('timeout', () => {
// if the server had failed in punching we won't be
// able to connect
});
}
});
Consulte el ejemplo completo de perforación en el directorio ejemplo/ .
La misma clase se puede utilizar como servidor o como cliente, la sintaxis es la siguiente:
opciones es la siguiente:
{
bufferSize: 64, // number of packets
mtu: 1000, // bytes excluding uTP header
timeout: 5000, // ms
resend: 100, // ms
keepAlive: 1000, // ms
}
A onConnection se le pasará un único argumento: el socket. Estas son las conexiones entrantes del servidor.
Getter para el número máximo de conexiones que el nodo puede manejar
Getter para el número de conexiones entrantes
Getter para el número de conexiones salientes
Devuelve el socket UDP estándar de Node.js que se utiliza bajo el capó.
Dirección vinculada del socket (la misma que en Node.js UDP .address())
Vincularse al host: puerto y ejecutar onBound cuando haya terminado
Comience a marcar intentos en el host: puerto y ejecute la devolución de llamada cuando tenga éxito o no queden intentos. El éxito o el fracaso se pasa a la devolución de llamada como el primer parámetro booleano.
Convierta este nodo en un servidor y ejecute esta devolución de llamada cuando esté listo para aceptar conexiones entrantes.
Conéctese a un nodo de servidor en el host: puerto y ejecute la devolución de llamada con el objeto de socket como parámetro único
Termine todas las conexiones y el Nodo, ejecute la devolución de llamada.
El objeto Socket pasado al constructor de Node y las devoluciones de llamada .connect() es una secuencia que emite 'datos' al recibir datos. Tiene los métodos habituales: .write(), .end(), etc.
La biblioteca 'utp' original fue creada por @mafintosh en https://github.com/mafintosh/utp. Esta es una reescritura en JavaScript moderno con corrección de errores y características adicionales que incluyen el uso como servidor y cliente simultáneamente en el mismo puerto y soporte para perforación UDP.