Когда вам нужно передать данные по UDP, вы сталкиваетесь с его ограничениями: данные должны передаваться небольшими порциями, чтобы уместиться в MTU, пакеты могут быть потеряны без уведомления, дублироваться или быть в неправильном порядке.
uTP (микротранспортный протокол) был изобретен торрент-пользователями для безопасной передачи файлов по UDP. По сути, он добавляет к UDP функции TCP: потерянные пакеты автоматически передаются повторно, порядок гарантируется, дубликаты отклоняются.
Эта библиотека реализует uTP поверх UDP, поэтому при подключении вы получаете объект сокета , который ведет себя во многом как TCP-сокет Node.js. Он излучает «данные» при получении данных, у него есть метод .write(), позволяющий вам отправить буфер. Подобно TCP-сокету, это поток Node.js.
Однако эта библиотека может быть несовместима с другими реализациями uTP (поэтому вам придется использовать одну и ту же библиотеку на обоих узлах), поскольку она добавляет следующую функцию: один и тот же экземпляр класса может использоваться как в качестве сервера, так и в качестве клиент одновременно на одном и том же порту. Таким образом, вы можете создать узел, привязать его к порту и в то же время начать прослушивать входящие соединения, а также осуществлять исходящие соединения из него.
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');
});
Другой метод, который здесь используется, — это перфорация UDP.
Когда сервер и/или клиент находятся за NAT, у них обычно нет IP-адреса в Интернете, к которому можно привязаться для получения входящих соединений.
Пробивка отверстий UDP обманом заставляет брандмауэры открыть временную дыру для своего пользователя, поэтому порт на устройстве NAT привязывается к порту сервера/клиента внутри локальной сети.
Чтобы это работало, и сервер, и клиент должны использовать сторонний сервер, чтобы узнать IP-адреса друг друга с NAT и координировать попытку перфорации (это должно быть сделано одновременно на сервере и на клиенте).
Но когда соединение установлено, сторонний сервер больше не нужен и он никогда не используется в качестве ретранслятора, все данные передаются напрямую между этим NAT-сервером и клиентом.
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
});
}
});
Полный пример перфорации см. в каталоге example/ .
Один и тот же класс можно использовать как сервер или как клиент, синтаксис следующий:
варианты следующие:
{
bufferSize: 64, // number of packets
mtu: 1000, // bytes excluding uTP header
timeout: 5000, // ms
resend: 100, // ms
keepAlive: 1000, // ms
}
onConnection будет передан один аргумент — сокет. Это входящие соединения сервера
Получатель максимального количества соединений, которые может обработать узел.
Геттер количества входящих соединений
Геттер количества исходящих соединений
Возвращает стандартный UDP-сокет Node.js, который используется «под капотом».
Привязанный адрес сокета (тот же, что и в Node.js UDP .address())
Привязка к хосту: порт и выполнение onBound после завершения
Начните пробивать попытки к порту хоста и запустите обратный вызов, когда попытки будут успешны или не останутся. Успех или неудача передаются обратному вызову в качестве первого логического параметра.
Превратите этот узел в сервер и выполните этот обратный вызов, когда будете готовы принять входящие соединения.
Подключитесь к серверному узлу через хост:порт и выполните обратный вызов с объектом сокета в качестве единственного параметра.
Завершите все соединения и узел, запустите обратный вызов.
Объект сокета, передаваемый конструктору Node и обратным вызовам .connect(), представляет собой поток, испускающий «данные» при получении данных. Имеет обычные методы: .write(), .end() и т.д.
Исходная библиотека utp была создана @mafintosh по адресу https://github.com/mafintosh/utp. Это переписанный современный JavaScript с исправлением ошибок и дополнительными функциями, включая использование в качестве сервера и клиента одновременно на одном и том же порту и поддержку дырокола UDP.