TCP, UDP, DNS — клиент-сервер на Node.js

Модули net (TCP), dgram (UDP) и dns дают прямой доступ к транспортному уровню — без HTTP-надстройки. Для бинарных протоколов, real-time игр, прокси, парсеров.

TCP — net.createServer

const net = require('net');

const server = net.createServer((socket) => {
  console.log(`Client connected: ${socket.remoteAddress}:${socket.remotePort}`);

  socket.setNoDelay(true); // отключить алгоритм Нэйгла — без задержки
  socket.setEncoding('utf8');

  socket.on('data', (chunk) => {
    // ВНИМАНИЕ: TCP пакеты режутся и склеиваются — нет гарантии что один write = один data
    console.log('Got:', chunk);
  });

  socket.on('end', () => console.log('Client disconnected'));
  socket.on('error', (err) => console.error(err));

  socket.write('Hello\r\n');
});

server.listen(7000, '0.0.0.0', () => console.log('TCP on 7000'));

Накопление буфера

let buffer = '';
socket.on('data', (chunk) => {
  buffer += chunk;
  let idx;
  while ((idx = buffer.indexOf('\n')) >= 0) {
    const line = buffer.slice(0, idx);
    buffer = buffer.slice(idx + 1);
    handleMessage(line); // обрабатываем по одному сообщению
  }
});

UDP — dgram

const dgram = require('dgram');
const server = dgram.createSocket('udp4');

server.on('message', (msg, rinfo) => {
  console.log(`UDP from ${rinfo.address}:${rinfo.port}:`, msg.toString());
  server.send('pong', rinfo.port, rinfo.address);
});

server.bind(41234);

// Клиент
const client = dgram.createSocket('udp4');
client.send(Buffer.from('ping'), 41234, '127.0.0.1', () => client.close());

UDP — пакетный, без соединения. Быстрее TCP но без гарантий доставки/порядка. Подходит для голоса, игр, DNS, локальной телеметрии.

DNS

const dns = require('dns').promises;

const records = await dns.resolve4('github.com');         // ['140.82.121.4']
const mx = await dns.resolveMx('gmail.com');              // [{exchange, priority}]
const reverse = await dns.reverse('8.8.8.8');             // ['dns.google']
const all = await dns.resolveAny('github.com');           // A, AAAA, MX, TXT

dns.lookup использует getaddrinfo (блокирует libuv thread pool), dns.resolve* — нативный DNS-клиент (не блокирует пул).

Подводные камни

  • TCP data ≠ одно сообщение — нужен парсер сообщений с границами (длина + payload, или разделитель)
  • socket держит процессsocket.unref если не критично для выхода
  • Connection reset (ECONNRESET) — без обработчика error крашит процесс
  • throw в обработчике сокета — теряется коннект и процесс может упасть
  • UDP MTU — пакет > 1500 байт фрагментируется; держи payload < 1400
  • DNS round-robin: resolve4 возвращает IP в случайном порядке для балансировки
  • dns.lookup блокирует thread pool — увеличь UV_THREADPOOL_SIZE или используй resolve*

🎓 Источники

  • 🎓 [Клиент-сервер на Node.js TCP и UDP, DNS] · 2018-10-03 · YouTube · [Marp](../../../Documents/TimurShemsedinov/2018-10-03 — Клиент-сервер на Node.js TCP и UDP, DNS (bHn-wTlTTR0).md)
    • Тезисы: net и dgram, createServer + listener, события сокета, pause/resume, setNoDelay и алгоритм Нэйгла, UTF-8 символ != 1 байт, накопление буфера для логических пакетов («TCP пакеты режутся и склеиваются»), ECONNRESET обработка, socket.unref, UDP — пакетный без соединения, dns.resolveAny, асинхронный DNS
    • Цитата: «TCP — это поток байт, не сообщений. Гранят пакеты ОС и сеть, не ты»
  • 🎓 [Обзор встроенного Node.js API] · 2018-09-26 · YouTube
    • Тезисы: net модуль для TCP/UDP, dns уже на промисах

См. также