Skip to content

TCP and UDP sockets

@fkn/lib/net and @fkn/lib/dgram are Node’s net and dgram in the browser: the same classes, events and callbacks, with the bytes carried through the broker (the connection your page holds into FKN) to the relay (the server that holds the real socket at the far end). This page follows a socket from connect or bind to the error that ends it, and says which of Node’s behaviours the relay keeps and which it changes.

A plain HTTP request written by hand shows the whole surface at once:

app.ts
import * as
import net
net
from '@fkn/lib/net'
const
const chunks: Buffer<ArrayBufferLike>[]
chunks
:
interface Buffer<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>
Buffer
[] = []
const
const socket: net.Socket
socket
=
import net
net
.
function connect(options: SocketConnectOpts, connectionListener?: () => void): net.Socket
export connect
connect
({
TcpSocketConnectOpts.host?: string | undefined
host
: 'example.org',
TcpSocketConnectOpts.port: number
port
: 80 }, () => {
const socket: net.Socket
socket
.
Socket$2.remotePort: number

The numeric representation of the remote port. For example, 80 or 21. Value may be undefined if the socket is destroyed (for example, if the client disconnected).

@sincev0.5.10

remotePort
// 80, and every address getter answers from here on
const socket: net.Socket
socket
.
Socket$2.write(buffer: Uint8Array | string, cb?: (err?: Error) => void): boolean (+1 overload)

The writable.write() method writes some data to the stream, and calls the supplied callback once the data has been fully handled. If an error occurs, the callback will be called with the error as its first argument. The callback is called asynchronously and before 'error' is emitted.

The return value is true if the internal buffer is less than the highWaterMark configured when the stream was created after admitting chunk. If false is returned, further attempts to write data to the stream should stop until the 'drain' event is emitted.

While a stream is not draining, calls to write() will buffer chunk, and return false. Once all currently buffered chunks are drained (accepted for delivery by the operating system), the 'drain' event will be emitted. Once write() returns false, do not write more chunks until the 'drain' event is emitted. While calling write() on a stream that is not draining is allowed, Node.js will buffer all written chunks until maximum memory usage occurs, at which point it will abort unconditionally. Even before it aborts, high memory usage will cause poor garbage collector performance and high RSS (which is not typically released back to the system, even after the memory is no longer required). Since TCP sockets may never drain if the remote peer does not read the data, writing a socket that is not draining may lead to a remotely exploitable vulnerability.

Writing data while the stream is not draining is particularly problematic for a Transform, because the Transform streams are paused by default until they are piped or a 'data' or 'readable' event handler is added.

If the data to be written can be generated or fetched on demand, it is recommended to encapsulate the logic into a Readable and use

pipe

. However, if calling write() is preferred, it is possible to respect backpressure and avoid memory issues using the 'drain' event:

function write(data, cb) {
if (!stream.write(data)) {
stream.once('drain', cb);
} else {
process.nextTick(cb);
}
}
// Wait for cb to be called before doing any other write.
write('hello', () => {
console.log('Write completed, do more writes now.');
});

A Writable stream in object mode will always ignore the encoding argument.

Writes data to the stream, with an explicit encoding for string data.

write
('GET /api/catalog.json HTTP/1.0\r\nHost: example.org\r\n\r\n')
})
const socket: net.Socket
socket
.
Stream.Duplex.on<"error">(eventName: "error", listener: (err: Error) => void): net.Socket (+1 overload)

Adds the listener function to the end of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times.

server.on('connection', (stream) => {
console.log('someone connected!');
});

Returns a reference to the EventEmitter, so that calls can be chained.

By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array.

import { EventEmitter } from 'node:events';
const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a

@sincev0.1.101

@parameventName The name of the event.

@paramlistener The callback function

on
('error', (
error: Error
error
:
interface Error
Error
) =>
error: Error
error
.
Error.message: string
message
) // a refused connect lands here, and nothing else fires
const socket: net.Socket
socket
.
Stream.Duplex.on<"data">(eventName: "data", listener: (chunk: any) => void): net.Socket (+1 overload)

Adds the listener function to the end of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times.

server.on('connection', (stream) => {
console.log('someone connected!');
});

Returns a reference to the EventEmitter, so that calls can be chained.

By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array.

import { EventEmitter } from 'node:events';
const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a

@sincev0.1.101

@parameventName The name of the event.

@paramlistener The callback function

on
('data', (
chunk: Buffer<ArrayBufferLike>
chunk
:
interface Buffer<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>
Buffer
) =>
const chunks: Buffer<ArrayBufferLike>[]
chunks
.
Array<Buffer<ArrayBufferLike>>.push(...items: Buffer<ArrayBufferLike>[]): number

Appends new elements to the end of an array, and returns the new length of the array.

@paramitems New elements to add to the array.

push
(
chunk: Buffer<ArrayBufferLike>
chunk
)) // a Buffer per read, in arrival order
const socket: net.Socket
socket
.
Stream.Duplex.on<"end">(eventName: "end", listener: () => void): net.Socket (+1 overload)

Adds the listener function to the end of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times.

server.on('connection', (stream) => {
console.log('someone connected!');
});

Returns a reference to the EventEmitter, so that calls can be chained.

By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array.

import { EventEmitter } from 'node:events';
const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a

@sincev0.1.101

@parameventName The name of the event.

@paramlistener The callback function

on
('end', () => {
var Buffer: BufferConstructor
Buffer
.
BufferConstructor.concat(list: readonly Uint8Array[], totalLength?: number): Buffer<ArrayBuffer>

Returns a new Buffer which is the result of concatenating all the Buffer instances in the list together.

If the list has no items, or if the totalLength is 0, then a new zero-length Buffer is returned.

If totalLength is not provided, it is calculated from the Buffer instances in list by adding their lengths.

If totalLength is provided, it must be an unsigned integer. If the combined length of the Buffers in list exceeds totalLength, the result is truncated to totalLength. If the combined length of the Buffers in list is less than totalLength, the remaining space is filled with zeros.

import { Buffer } from 'node:buffer';
// Create a single `Buffer` from a list of three `Buffer` instances.
const buf1 = Buffer.alloc(10);
const buf2 = Buffer.alloc(14);
const buf3 = Buffer.alloc(18);
const totalLength = buf1.length + buf2.length + buf3.length;
console.log(totalLength);
// Prints: 42
const bufA = Buffer.concat([buf1, buf2, buf3], totalLength);
console.log(bufA);
// Prints: <Buffer 00 00 00 00 ...>
console.log(bufA.length);
// Prints: 42

Buffer.concat() may also use the internal Buffer pool like Buffer.allocUnsafe() does.

@sincev0.7.11

@paramlist List of Buffer or Uint8Array instances to concatenate.

@paramtotalLength Total length of the Buffer instances in list when concatenated.

concat
(
const chunks: Buffer<ArrayBufferLike>[]
chunks
).
Buffer<ArrayBuffer>.toString(encoding?: BufferEncoding, start?: number, end?: number): string

Decodes buf to a string according to the specified character encoding inencoding. start and end may be passed to decode only a subset of buf.

If encoding is 'utf8' and a byte sequence in the input is not valid UTF-8, then each invalid byte is replaced with the replacement character U+FFFD.

The maximum length of a string instance (in UTF-16 code units) is available as

constants.MAX_STRING_LENGTH

.

import { Buffer } from 'node:buffer';
const buf1 = Buffer.allocUnsafe(26);
for (let i = 0; i < 26; i++) {
// 97 is the decimal ASCII value for 'a'.
buf1[i] = i + 97;
}
console.log(buf1.toString('utf8'));
// Prints: abcdefghijklmnopqrstuvwxyz
console.log(buf1.toString('utf8', 0, 5));
// Prints: abcde
const buf2 = Buffer.from('tést');
console.log(buf2.toString('hex'));
// Prints: 74c3a97374
console.log(buf2.toString('utf8', 0, 3));
// Prints: té
console.log(buf2.toString(undefined, 0, 3));
// Prints: té

@sincev0.1.90

@paramencoding The character encoding to use.

@paramstart The byte offset to start decoding at.

@paramend The byte offset to stop decoding at (not inclusive).

toString
('utf8') // the response head and the catalog
const socket: net.Socket
socket
.
Stream.Readable.destroy(error?: Error): net.Socket

Destroy the stream. Optionally emit an 'error' event, and emit a 'close' event (unless emitClose is set to false). After this call, the readable stream will release any internal resources and subsequent calls to push() will be ignored.

Once destroy() has been called any further calls will be a no-op and no further errors except from _destroy() may be emitted as 'error'.

Implementors should not override this method, but instead implement readable._destroy().

@sincev8.0.0

@paramerror Error which will be passed as payload in 'error' event

destroy
()
})

connect hands you the Socket at once and runs your listener only when the relay has answered. The write runs in that listener, and the three handlers are attached right away.

The examples use Node’s Buffer, which a browser bundle has to shim, see install.

Nothing here is a fetch. The bytes on the wire are yours, so a torrent engine or an IRC client can run from a page. See backends for where the work runs.

This page imports from @fkn/lib/net and @fkn/lib/dgram, and from @fkn/lib/dns, @fkn/lib and @fkn/lib/api where an example needs them. The root and cloud spellings are the same objects. See entry points.

@fkn/net and @fkn/dgram are the same objects published under Node’s own types, for code written against @types/node. That cast has a cost: the positional connect(80, 'example.org') compiles there, and so does new Socket() on a datagram socket, which throws at run time. See UDP.

connect(options, listener?), and its alias createConnection, build a Socket and hand it to you synchronously while the broker requests the connection from the relay. host may be a name or an IP literal, and the broker resolves names before dialing. connect fires once the socket is open, and your listener runs then.

The published types accept the options form only. The Socket#connect method underneath takes Node’s positional overloads. A path argument throws FKN WebVPN does not support IPC connections, since there is no unix socket to open:

app.ts
import * as
import net
net
from '@fkn/lib/net'
import net
net
.
function connect(options: SocketConnectOpts, connectionListener?: () => void): net.Socket
export connect
connect
(80, 'example.org')
Error ts(2345) ― Argument of type 'number' is not assignable to parameter of type 'SocketConnectOpts'.
new
import net
net
.
new Socket(options?: SocketConstructorOpts & Stream.DuplexOptions & {
connection?: ReturnType<(options: TcpSocketOptions) => Promise<TcpSocketResult>>;
}): net.Socket
export Socket
Socket
().
Socket$2.connect(port: number, host: string, connectionListener?: (() => void) | undefined): net.Socket (+4 overloads)

Initiate a connection on a given socket.

Possible signatures:

  • socket.connect(options[, connectListener])
  • socket.connect(path[, connectListener]) for IPC connections.
  • socket.connect(port[, host][, connectListener]) for TCP connections.
  • Returns: net.Socket The socket itself.

This function is asynchronous. When the connection is established, the 'connect' event will be emitted. If there is a problem connecting, instead of a 'connect' event, an 'error' event will be emitted with the error passed to the 'error' listener. The last parameter connectListener, if supplied, will be added as a listener for the 'connect' event once.

This function should only be used for reconnecting a socket after'close' has been emitted or otherwise it may lead to undefined behavior.

connect
(80, 'example.org') // compiles, the method normalises the positional forms

The first line is the compile error. The second is the way out when you port positional calls.

A refused connect emits error and nothing else: no close, no listener call. The emit is never synchronous. An error with no listener throws, so attach the listener on the line after connect.

What arrives there depends on who refused. The broker mints its own messages before the relay ever answers: getaddrinfo ENOTFOUND <hostname> when the name has no answer, tcp connect to <address>:<port> timed out after 12000ms when the relay does not acknowledge within 12,000 ms, and WebVPN setup timeout: tcp data stream claim took over 30000ms when the acknowledged socket’s data stream is not claimed within 30,000 ms.

The relay’s refusals arrive verbatim, among them webvpn: egress to a non-public address refused for a target it does not reach. A peer that refuses the dial arrives as the operating system’s own text.

With no host the target is localhost. A loopback or wildcard target, resolved or written out as 127.0.0.1, 0.0.0.0, ::1 or ::, pairs with a net.Server listening in the same data plane (the shared worker behind the broker, which the pages of one origin share) and never dials the relay.

With no such listener the relay refuses it, since a loopback address is not public. See listening.

Read the address getters remoteAddress, remotePort, remoteFamily, localAddress, localPort and localFamily inside the connect handler or later. They throw Socket is not connected until the relay has answered, and address() answers {} until then. family is the string 'IPv4' or 'IPv6'.

write(chunk, cb?) hands one chunk at a time to the writable stream the broker holds for the socket, and waits for that stream to be ready before the callback runs. Node’s drain event and the false return of write mean what they mean in Node. end() closes your half, and the far side sees EOF and can still answer. destroy() tears the socket down and releases its busy token, one of the reasons your realm (the window or worker running your code) reports itself busy:

app.ts
const
const socket: net.Socket
socket
=
import net
net
.
function connect(options: SocketConnectOpts, connectionListener?: () => void): net.Socket
export connect
connect
({
TcpSocketConnectOpts.host?: string | undefined
host
: '67.215.246.10',
TcpSocketConnectOpts.port: number
port
: 6881 })
const socket: net.Socket
socket
.
Stream.Duplex.on<"error">(eventName: "error", listener: (err: Error) => void): net.Socket (+1 overload)

Adds the listener function to the end of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times.

server.on('connection', (stream) => {
console.log('someone connected!');
});

Returns a reference to the EventEmitter, so that calls can be chained.

By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array.

import { EventEmitter } from 'node:events';
const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a

@sincev0.1.101

@parameventName The name of the event.

@paramlistener The callback function

on
('error', (
error: Error
error
:
interface Error
Error
) =>
error: Error
error
.
Error.message: string
message
)
const socket: net.Socket
socket
.
Stream.Duplex.once(eventName: string | symbol, listener: (...args: any[]) => void): net.Socket (+1 overload)

Adds a one-time listener function for the event named eventName. The next time eventName is triggered, this listener is removed and then invoked.

server.once('connection', (stream) => {
console.log('Ah, we have our first user!');
});

Returns a reference to the EventEmitter, so that calls can be chained.

By default, event listeners are invoked in the order they are added. The emitter.prependOnceListener() method can be used as an alternative to add the event listener to the beginning of the listeners array.

import { EventEmitter } from 'node:events';
const myEE = new EventEmitter();
myEE.once('foo', () => console.log('a'));
myEE.prependOnceListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a

@sincev0.3.0

@parameventName The name of the event.

@paramlistener The callback function

once
('connect', async () => {
for (const
const frame: Uint8Array<ArrayBufferLike>
frame
of
const frames: Uint8Array<ArrayBufferLike>[]
frames
) {
if (!
const socket: net.Socket
socket
.
Socket$2.write(buffer: Uint8Array | string, cb?: (err?: Error) => void): boolean (+1 overload)

The writable.write() method writes some data to the stream, and calls the supplied callback once the data has been fully handled. If an error occurs, the callback will be called with the error as its first argument. The callback is called asynchronously and before 'error' is emitted.

The return value is true if the internal buffer is less than the highWaterMark configured when the stream was created after admitting chunk. If false is returned, further attempts to write data to the stream should stop until the 'drain' event is emitted.

While a stream is not draining, calls to write() will buffer chunk, and return false. Once all currently buffered chunks are drained (accepted for delivery by the operating system), the 'drain' event will be emitted. Once write() returns false, do not write more chunks until the 'drain' event is emitted. While calling write() on a stream that is not draining is allowed, Node.js will buffer all written chunks until maximum memory usage occurs, at which point it will abort unconditionally. Even before it aborts, high memory usage will cause poor garbage collector performance and high RSS (which is not typically released back to the system, even after the memory is no longer required). Since TCP sockets may never drain if the remote peer does not read the data, writing a socket that is not draining may lead to a remotely exploitable vulnerability.

Writing data while the stream is not draining is particularly problematic for a Transform, because the Transform streams are paused by default until they are piped or a 'data' or 'readable' event handler is added.

If the data to be written can be generated or fetched on demand, it is recommended to encapsulate the logic into a Readable and use

pipe

. However, if calling write() is preferred, it is possible to respect backpressure and avoid memory issues using the 'drain' event:

function write(data, cb) {
if (!stream.write(data)) {
stream.once('drain', cb);
} else {
process.nextTick(cb);
}
}
// Wait for cb to be called before doing any other write.
write('hello', () => {
console.log('Write completed, do more writes now.');
});

A Writable stream in object mode will always ignore the encoding argument.

Writes data to the stream, with an explicit encoding for string data.

write
(
const frame: Uint8Array<ArrayBufferLike>
frame
)) await new
var Promise: PromiseConstructor
new <unknown>(executor: (resolve: (value: unknown) => void, reject: (reason?: any) => void) => void) => Promise<unknown>

Creates a new Promise.

@paramexecutor A callback used to initialize the promise. This callback is passed two arguments: a resolve callback used to resolve the promise with a value or the result of another promise, and a reject callback used to reject the promise with a provided reason or error.

Promise
(
resolve: (value: unknown) => void
resolve
=>
const socket: net.Socket
socket
.
Stream.Duplex.once(eventName: string | symbol, listener: (...args: any[]) => void): net.Socket (+1 overload)

Adds a one-time listener function for the event named eventName. The next time eventName is triggered, this listener is removed and then invoked.

server.once('connection', (stream) => {
console.log('Ah, we have our first user!');
});

Returns a reference to the EventEmitter, so that calls can be chained.

By default, event listeners are invoked in the order they are added. The emitter.prependOnceListener() method can be used as an alternative to add the event listener to the beginning of the listeners array.

import { EventEmitter } from 'node:events';
const myEE = new EventEmitter();
myEE.once('foo', () => console.log('a'));
myEE.prependOnceListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a

@sincev0.3.0

@parameventName The name of the event.

@paramlistener The callback function

once
('drain',
resolve: (value: unknown) => void
resolve
)) // the transport has not taken the last chunk yet
}
const socket: net.Socket
socket
.
Stream.Writable.end(cb?: () => void): net.Socket (+2 overloads)

Calling the writable.end() method signals that no more data will be written to the Writable. The optional chunk and encoding arguments allow one final additional chunk of data to be written immediately before closing the stream.

Calling the

write

method after calling

end

will raise an error.

// Write 'hello, ' and then end with 'world!'.
import fs from 'node:fs';
const file = fs.createWriteStream('example.txt');
file.write('hello, ');
file.end('world!');
// Writing more now is not allowed!

@sincev0.9.4

@paramcb Callback for when the stream is finished.

end
() // half-close, the peer sees EOF and its own data still arrives
})
const socket: net.Socket
socket
.
Stream.Duplex.on<"close">(eventName: "close", listener: () => void): net.Socket (+1 overload)

Adds the listener function to the end of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times.

server.on('connection', (stream) => {
console.log('someone connected!');
});

Returns a reference to the EventEmitter, so that calls can be chained.

By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array.

import { EventEmitter } from 'node:events';
const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a

@sincev0.1.101

@parameventName The name of the event.

@paramlistener The callback function

on
('close', () =>
const socket: net.Socket
socket
.
Stream.Readable.destroyed: boolean

Is true after readable.destroy() has been called.

@sincev8.0.0

destroyed
) // true, and the tcp socket busy token is gone

The pushback is the transport’s flow control, not a relay acknowledgement per chunk. A write before connect fails its callback with Socket not connected, which the stream surfaces as error.

destroySoon() destroys after finish. resetAndDestroy() asks the relay for a reset instead of a close.

createServer(options?, listener?) and listen(port?, host?, cb?) open a listener on the relay and hand you a Socket per accepted connection. A connect with no host in the same data plane pairs with it:

app.ts
import * as
import net
net
from '@fkn/lib/net'
const
const server: net.Server
server
=
import net
net
.
function createServer(options?: ServerOpts | ((socket: net.Socket) => void), connectionListener?: (socket: net.Socket) => void): net.Server
export createServer
createServer
((
peer: net.Socket
peer
) => {
peer: net.Socket
peer
.
Socket$2.remotePort: number

The numeric representation of the remote port. For example, 80 or 21. Value may be undefined if the socket is destroyed (for example, if the client disconnected).

@sincev0.5.10

remotePort
// a number, the endpoints land before the event
peer: net.Socket
peer
.
Stream.Writable.end(chunk: any, cb?: () => void): net.Socket (+2 overloads)

Signals that no more data will be written, with one final chunk of data.

@seeWritable.end for full details.

@sincev0.9.4

@paramchunk Optional data to write. For streams not operating in object mode, chunk must be a {string}, {Buffer}, {TypedArray} or {DataView}. For object mode streams, chunk may be any JavaScript value other than null.

@paramcb Callback for when the stream is finished.

end
('hello')
})
const server: net.Server
server
.
NodeJS.EventEmitter<any>.on<string | symbol>(eventName: string | symbol, listener: (...args: any[]) => void): net.Server

Adds the listener function to the end of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times.

server.on('connection', (stream) => {
console.log('someone connected!');
});

Returns a reference to the EventEmitter, so that calls can be chained.

By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array.

import { EventEmitter } from 'node:events';
const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a

@sincev0.1.101

@parameventName The name of the event.

@paramlistener The callback function

on
('error', (
error: Error
error
:
interface Error
Error
) =>
error: Error
error
.
Error.message: string
message
) // a refused bind lands here, and no listening follows
const server: net.Server
server
.
Server$1.listen(port?: number | undefined, listeningListener?: (() => void) | undefined): net.Server (+9 overloads)

Start a server listening for connections. A net.Server can be a TCP or an IPC server depending on what it listens to.

Possible signatures:

  • server.listen(handle[, backlog][, callback])
  • server.listen(options[, callback])
  • server.listen(path[, backlog][, callback]) for IPC servers
  • server.listen([port[, host[, backlog]]][, callback]) for TCP servers

This function is asynchronous. When the server starts listening, the 'listening' event will be emitted. The last parameter callbackwill be added as a listener for the 'listening' event.

All listen() methods can take a backlog parameter to specify the maximum length of the queue of pending connections. The actual length will be determined by the OS through sysctl settings such as tcp_max_syn_backlog and somaxconn on Linux. The default value of this parameter is 511 (not 512).

All

Socket

are set to SO_REUSEADDR (see socket(7) for details).

The server.listen() method can be called again if and only if there was an error during the first server.listen() call or server.close() has been called. Otherwise, an ERR_SERVER_ALREADY_LISTEN error will be thrown.

One of the most common errors raised when listening is EADDRINUSE. This happens when another server is already listening on the requestedport/path/handle. One way to handle this would be to retry after a certain amount of time:

server.on('error', (e) => {
if (e.code === 'EADDRINUSE') {
console.error('Address in use, retrying...');
setTimeout(() => {
server.close();
server.listen(PORT, HOST);
}, 1000);
}
});

listen
(0, () => {
const
const address: string | AddressInfo | null
address
=
const server: net.Server
server
.
Server$1.address(): string | AddressInfo | null

Returns the bound address, the address family name, and port of the server as reported by the operating system if listening on an IP socket (useful to find which port was assigned when getting an OS-assigned address):{ port: 12346, family: 'IPv4', address: '127.0.0.1' }.

For a server listening on a pipe or Unix domain socket, the name is returned as a string.

const server = net.createServer((socket) => {
socket.end('goodbye\n');
}).on('error', (err) => {
// Handle errors here.
throw err;
});
// Grab an arbitrary unused port.
server.listen(() => {
console.log('opened server on', server.address());
});

server.address() returns null before the 'listening' event has been emitted or after calling server.close().

@sincev0.1.90

address
()
if (!
const address: string | AddressInfo | null
address
|| typeof
const address: string | AddressInfo
address
!== 'object') return
const address: AddressInfo
address
.
AddressInfo.port: number
port
// the port the relay granted
const
const socket: net.Socket
socket
=
import net
net
.
function connect(options: SocketConnectOpts, connectionListener?: () => void): net.Socket
export connect
connect
({
TcpSocketConnectOpts.port: number
port
:
const address: AddressInfo
address
.
AddressInfo.port: number
port
}) // no host, so it pairs with the server above and never dials the relay
const socket: net.Socket
socket
.
Stream.Duplex.on<"error">(eventName: "error", listener: (err: Error) => void): net.Socket (+1 overload)

Adds the listener function to the end of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times.

server.on('connection', (stream) => {
console.log('someone connected!');
});

Returns a reference to the EventEmitter, so that calls can be chained.

By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array.

import { EventEmitter } from 'node:events';
const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a

@sincev0.1.101

@parameventName The name of the event.

@paramlistener The callback function

on
('error', (
error: Error
error
:
interface Error
Error
) =>
error: Error
error
.
Error.message: string
message
)
const socket: net.Socket
socket
.
Stream.Duplex.on<"data">(eventName: "data", listener: (chunk: any) => void): net.Socket (+1 overload)

Adds the listener function to the end of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times.

server.on('connection', (stream) => {
console.log('someone connected!');
});

Returns a reference to the EventEmitter, so that calls can be chained.

By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array.

import { EventEmitter } from 'node:events';
const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a

@sincev0.1.101

@parameventName The name of the event.

@paramlistener The callback function

on
('data', (
chunk: Buffer<ArrayBufferLike>
chunk
:
interface Buffer<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>
Buffer
) =>
chunk: Buffer<ArrayBufferLike>
chunk
.
Buffer<ArrayBufferLike>.toString(encoding?: BufferEncoding, start?: number, end?: number): string

Decodes buf to a string according to the specified character encoding inencoding. start and end may be passed to decode only a subset of buf.

If encoding is 'utf8' and a byte sequence in the input is not valid UTF-8, then each invalid byte is replaced with the replacement character U+FFFD.

The maximum length of a string instance (in UTF-16 code units) is available as

constants.MAX_STRING_LENGTH

.

import { Buffer } from 'node:buffer';
const buf1 = Buffer.allocUnsafe(26);
for (let i = 0; i < 26; i++) {
// 97 is the decimal ASCII value for 'a'.
buf1[i] = i + 97;
}
console.log(buf1.toString('utf8'));
// Prints: abcdefghijklmnopqrstuvwxyz
console.log(buf1.toString('utf8', 0, 5));
// Prints: abcde
const buf2 = Buffer.from('tést');
console.log(buf2.toString('hex'));
// Prints: 74c3a97374
console.log(buf2.toString('utf8', 0, 3));
// Prints: té
console.log(buf2.toString(undefined, 0, 3));
// Prints: té

@sincev0.1.90

@paramencoding The character encoding to use.

@paramstart The byte offset to start decoding at.

@paramend The byte offset to stop decoding at (not inclusive).

toString
('utf8')) // 'hello'
})

Port 0 asks for any port, and the relay reports the one it granted. address() answers that port while bound and null otherwise. With no host the server binds '::' and falls back to '0.0.0.0' when that is refused.

The relay may refuse a port, and the error names the reason. A session at its socket limit is refused with webvpn: socket capacity reached.

A refused bind behaves like a refused connect: error fires, and listening, the callback and close do not. The server holds nothing open.

close(cb?) waits for the relay to unbind, emits close once and calls cb. On a server that never bound, whether listen was never called or the bind was refused, it calls nothing, so a promisified close there never settles.

When the session drops from under a listener, error fires only if you attached a listener for it, then close. When the relay releases the binding from its side, close fires alone, with no error even when you listen for one.

getConnections(cb) throws Method not implemented.. maxConnections, connections and listening are never set.

The setters that reach the relay send one code from @fkn/lib/wire. The rest are local:

SetterOn the wireNotes
setNoDelay(noDelay = true)TCP_OPTION_NODELAY
setKeepAlive(enable = false, initialDelay?)TCP_OPTION_KEEPALIVEa positive delay travels in whole seconds, at least 1, and none travels as 0
setSendBufferSize(size), setRecvBufferSize(size)the two buffer codes
setTimeout(ms, cb?)nothinga local idle timer, restarted by every read and write, that only emits timeout, and 0 disables it, while cb stays attached for every later timeout
setTypeOfService(tos)nothingcached locally, getTypeOfService() reads it back
ref(), unref()nothingreturn this

A setter that reaches the relay does not throw when it runs too early. The guard is on connect() having been called, so a setter on a socket from new net.Socket() emits error with Cannot set socket option before connect until you call connect. A setter after connect() returns is safe: it waits for the connection and travels when the relay answers.

A relay that refuses an option emits the refusal through the same error event, and the local rows emit nothing:

app.ts
import * as
import net
net
from '@fkn/lib/net'
const
const socket: net.Socket
socket
=
import net
net
.
function connect(options: SocketConnectOpts, connectionListener?: () => void): net.Socket
export connect
connect
({
TcpSocketConnectOpts.host?: string | undefined
host
: '67.215.246.10',
TcpSocketConnectOpts.port: number
port
: 6881 })
const socket: net.Socket
socket
.
Stream.Duplex.on<"error">(eventName: "error", listener: (err: Error) => void): net.Socket (+1 overload)

Adds the listener function to the end of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times.

server.on('connection', (stream) => {
console.log('someone connected!');
});

Returns a reference to the EventEmitter, so that calls can be chained.

By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array.

import { EventEmitter } from 'node:events';
const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a

@sincev0.1.101

@parameventName The name of the event.

@paramlistener The callback function

on
('error', (
error: Error
error
:
interface Error
Error
) =>
error: Error
error
.
Error.message: string
message
) // the relay's refusal of an option lands here
const socket: net.Socket
socket
.
Socket$2.setNoDelay(noDelay?: boolean | undefined): net.Socket

Enable/disable the use of Nagle's algorithm.

When a TCP connection is created, it will have Nagle's algorithm enabled.

Nagle's algorithm delays data before it is sent via the network. It attempts to optimize throughput at the expense of latency.

Passing true for noDelay or not passing an argument will disable Nagle's algorithm for the socket. Passing false for noDelay will enable Nagle's algorithm.

@sincev0.1.90

@paramnoDelay

@returnThe socket itself.

setNoDelay
(true) // TCP_OPTION_NODELAY travels to the relay once the connection lands
const socket: net.Socket
socket
.
Socket$2.setKeepAlive(enable?: boolean | undefined, initialDelay?: number | undefined): net.Socket

Enable/disable keep-alive functionality, and optionally set the initial delay before the first keepalive probe is sent on an idle socket.

Set initialDelay (in milliseconds) to set the delay between the last data packet received and the first keepalive probe. Setting 0 forinitialDelay will leave the value unchanged from the default (or previous) setting.

Enabling the keep-alive functionality will set the following socket options:

  • SO_KEEPALIVE=1
  • TCP_KEEPIDLE=initialDelay
  • TCP_KEEPCNT=10
  • TCP_KEEPINTVL=1

@sincev0.1.92

@paramenable

@paraminitialDelay

@returnThe socket itself.

setKeepAlive
(true, 30_000) // 30 seconds on the wire
const socket: net.Socket
socket
.
Socket$2.setTimeout(timeout: number, callback?: (() => void) | undefined): net.Socket

Sets the socket to timeout after timeout milliseconds of inactivity on the socket. By default net.Socket do not have a timeout.

When an idle timeout is triggered the socket will receive a 'timeout' event but the connection will not be severed. The user must manually call socket.end() or socket.destroy() to end the connection.

socket.setTimeout(3000);
socket.on('timeout', () => {
console.log('socket timeout');
socket.end();
});

If timeout is 0, then the existing idle timeout is disabled.

The optional callback parameter will be added as a one-time listener for the 'timeout' event.

@sincev0.1.90

@returnThe socket itself.

setTimeout
(60_000, () =>
const socket: net.Socket
socket
.
Stream.Readable.destroy(error?: Error): net.Socket

Destroy the stream. Optionally emit an 'error' event, and emit a 'close' event (unless emitClose is set to false). After this call, the readable stream will release any internal resources and subsequent calls to push() will be ignored.

Once destroy() has been called any further calls will be a no-op and no further errors except from _destroy() may be emitted as 'error'.

Implementors should not override this method, but instead implement readable._destroy().

@sincev8.0.0

@paramerror Error which will be passed as payload in 'error' event

destroy
(new
var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+1 overload)
Error
('idle for 60s'))) // local, nothing is sent

The two relay options travel on the socket, and the timeout stays in the page.

bytesRead, bytesWritten, bufferSize, connecting, pending, readyState, timeout and autoSelectFamilyAttemptedAddresses are declared and never assigned, so read none of them. allowHalfOpen on a server, and localAddress and localPort on a connect, are accepted by the types and never read. There is no TTL setter on a TCP socket, even though TCP_OPTION_TTL exists on the wire.

dgram.createSocket('udp4' | 'udp6' | { type }, onMessage?) builds a Socket. bind(port?, address?, cb?) reserves a port on the relay, and send and message carry datagrams:

app.ts
import * as
import dgram
dgram
from '@fkn/lib/dgram'
import {
const lookup: <T extends boolean = false>(hostname: string, options?: {
all?: T;
family?: 0 | 4 | 6;
}) => Promise<T extends true ? AddressLookupResult[] : AddressLookupResult | undefined>
lookup
} from '@fkn/lib/dns'
const
const socket: dgram.Socket
socket
=
import dgram
dgram
.
function createSocket(options: SocketType | SocketOptions, callback?: (msg: Buffer, rinfo: RemoteInfo) => void): dgram.Socket
export createSocket
createSocket
('udp4')
const socket: dgram.Socket
socket
.
NodeJS.EventEmitter<any>.on<string | symbol>(eventName: string | symbol, listener: (...args: any[]) => void): dgram.Socket

Adds the listener function to the end of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times.

server.on('connection', (stream) => {
console.log('someone connected!');
});

Returns a reference to the EventEmitter, so that calls can be chained.

By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array.

import { EventEmitter } from 'node:events';
const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a

@sincev0.1.101

@parameventName The name of the event.

@paramlistener The callback function

on
('error', (
error: Error
error
:
interface Error
Error
) =>
error: Error
error
.
Error.message: string
message
) // a refused bind lands here, and no listening follows
const socket: dgram.Socket
socket
.
NodeJS.EventEmitter<any>.on<string | symbol>(eventName: string | symbol, listener: (...args: any[]) => void): dgram.Socket

Adds the listener function to the end of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times.

server.on('connection', (stream) => {
console.log('someone connected!');
});

Returns a reference to the EventEmitter, so that calls can be chained.

By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array.

import { EventEmitter } from 'node:events';
const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a

@sincev0.1.101

@parameventName The name of the event.

@paramlistener The callback function

on
('message', (
message: Buffer<ArrayBufferLike>
message
:
interface Buffer<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike>
Buffer
,
rinfo: any
rinfo
) => {
message: Buffer<ArrayBufferLike>
message
.
Uint8Array<ArrayBufferLike>.length: number

The length of the array.

length
// the datagram's bytes, the same number as rinfo.size
rinfo: any
rinfo
// { address, family: 'IPv4', port, size }
})
const socket: dgram.Socket
socket
.
Socket.bind(port?: number | undefined, callback?: (() => void) | undefined): dgram.Socket (+3 overloads)

For UDP sockets, causes the dgram.Socket to listen for datagram messages on a named port and optional address. If port is not specified or is 0, the operating system will attempt to bind to a random port. If address is not specified, the operating system will attempt to listen on all addresses. Once binding is complete, a 'listening' event is emitted and the optional callback function is called.

Specifying both a 'listening' event listener and passing a callback to the socket.bind() method is not harmful but not very useful.

A bound datagram socket keeps the Node.js process running to receive datagram messages.

If binding fails, an 'error' event is generated. In rare case (e.g. attempting to bind with a closed socket), an Error may be thrown.

Example of a UDP server listening on port 41234:

import dgram from 'node:dgram';
const server = dgram.createSocket('udp4');
server.on('error', (err) => {
console.error(`server error:\n${err.stack}`);
server.close();
});
server.on('message', (msg, rinfo) => {
console.log(`server got: ${msg} from ${rinfo.address}:${rinfo.port}`);
});
server.on('listening', () => {
const address = server.address();
console.log(`server listening ${address.address}:${address.port}`);
});
server.bind(41234);
// Prints: server listening 0.0.0.0:41234

bind
(0, () => {
const socket: dgram.Socket
socket
.
Socket.address(): AddressInfo

Returns an object containing the address information for a socket. For UDP sockets, this object will contain address, family, and port properties.

This method throws EBADF if called on an unbound socket.

@sincev0.1.99

address
().
AddressInfo.port: number
port
// the port the relay granted
})
const
const tracker: AddressLookupResult | undefined
tracker
= await
lookup<false>(hostname: string, options?: {
all?: false | undefined;
family?: 0 | 4 | 6;
} | undefined): Promise<AddressLookupResult | undefined>
lookup
('example.org') // resolve once, a name in send() costs a round trip per datagram
if (
const tracker: AddressLookupResult | undefined
tracker
) {
const socket: dgram.Socket
socket
.
Socket.send(msg: string | readonly any[] | Uint8Array, port?: number | undefined, address?: string | undefined, callback?: ((error: Error | null, bytes: number) => void) | undefined): void (+6 overloads)

Broadcasts a datagram on the socket. For connectionless sockets, the destination port and address must be specified. Connected sockets, on the other hand, will use their associated remote endpoint, so the port and address arguments must not be set.

The msg argument contains the message to be sent. Depending on its type, different behavior can apply. If msg is a Buffer, any TypedArray or a DataView, the offset and length specify the offset within the Buffer where the message begins and the number of bytes in the message, respectively. If msg is a String, then it is automatically converted to a Buffer with 'utf8' encoding. With messages that contain multi-byte characters, offset and length will be calculated with respect to byte length and not the character position. If msg is an array, offset and length must not be specified.

The address argument is a string. If the value of address is a host name, DNS will be used to resolve the address of the host. If address is not provided or otherwise nullish, '127.0.0.1' (for udp4 sockets) or '::1' (for udp6 sockets) will be used by default.

If the socket has not been previously bound with a call to bind, the socket is assigned a random port number and is bound to the "all interfaces" address ('0.0.0.0' for udp4 sockets, '::0' for udp6 sockets.)

An optional callback function may be specified to as a way of reporting DNS errors or for determining when it is safe to reuse the buf object. DNS lookups delay the time to send for at least one tick of the Node.js event loop.

The only way to know for sure that the datagram has been sent is by using a callback. If an error occurs and a callback is given, the error will be passed as the first argument to the callback. If a callback is not given, the error is emitted as an 'error' event on the socket object.

Offset and length are optional but both must be set if either are used. They are supported only when the first argument is a Buffer, a TypedArray, or a DataView.

This method throws ERR_SOCKET_BAD_PORT if called on an unbound socket.

Example of sending a UDP packet to a port on localhost;

import dgram from 'node:dgram';
import { Buffer } from 'node:buffer';
const message = Buffer.from('Some bytes');
const client = dgram.createSocket('udp4');
client.send(message, 41234, 'localhost', (err) => {
client.close();
});

Example of sending a UDP packet composed of multiple buffers to a port on127.0.0.1;

import dgram from 'node:dgram';
import { Buffer } from 'node:buffer';
const buf1 = Buffer.from('Some ');
const buf2 = Buffer.from('bytes');
const client = dgram.createSocket('udp4');
client.send([buf1, buf2], 41234, (err) => {
client.close();
});

Sending multiple buffers might be faster or slower depending on the application and operating system. Run benchmarks to determine the optimal strategy on a case-by-case basis. Generally speaking, however, sending multiple buffers is faster.

Example of sending a UDP packet using a socket connected to a port on localhost:

import dgram from 'node:dgram';
import { Buffer } from 'node:buffer';
const message = Buffer.from('Some bytes');
const client = dgram.createSocket('udp4');
client.connect(41234, 'localhost', (err) => {
client.send(message, (err) => {
client.close();
});
});

send
(
var Buffer: BufferConstructor
Buffer
.
BufferConstructor.from(string: WithImplicitCoercion<string>, encoding?: BufferEncoding): Buffer<ArrayBuffer> (+3 overloads)

Creates a new Buffer containing string. The encoding parameter identifies the character encoding to be used when converting string into bytes.

import { Buffer } from 'node:buffer';
const buf1 = Buffer.from('this is a tést');
const buf2 = Buffer.from('7468697320697320612074c3a97374', 'hex');
console.log(buf1.toString());
// Prints: this is a tést
console.log(buf2.toString());
// Prints: this is a tést
console.log(buf1.toString('latin1'));
// Prints: this is a tést

A TypeError will be thrown if string is not a string or another type appropriate for Buffer.from() variants.

Buffer.from(string) may also use the internal Buffer pool like Buffer.allocUnsafe() does.

@sincev5.10.0

@paramstring A string to encode.

@paramencoding The encoding of string. Default: 'utf8'.

from
('ping'), 6881,
const tracker: AddressLookupResult
tracker
.
address: string
address
, (
error: Error | null
error
,
bytes: number
bytes
) => {
bytes: number
bytes
// 4, handed to the transport, which is not the same as delivered
})
}

bind defaults to port 0, and to '0.0.0.0' for udp4 or '::' for udp6. On success it emits listening and runs the callback. On refusal it emits error and nothing else.

close(cb?), connect, disconnect and address() before bind throw synchronously instead, with Socket not bound or EBADF. new dgram.Socket() with no argument throws Missing options, so build one with createSocket.

send takes the Node shapes, (msg, port?, address?, cb?) and (msg, offset, length, port?, address?, cb?). The payload may be a string, a Uint8Array, any ArrayBufferView or an array of those. send on a socket that was never bound calls bind() with the defaults above first.

A datagram to an IP literal, or to the connected peer, is framed by the library and posted over a data port, and the callback fires as soon as the frame is queued. A datagram to a name goes through the broker instead, which resolves the name on every call. The example above resolves the name once and sends to the literal to avoid that round trip per datagram.

lookup is the library’s one dns call, see HTTP and DNS.

With no address and no connected peer, a datagram goes to 127.0.0.1 on udp4 or ::1 on udp6, port 0, where it is dropped. Always pass a target or connect first.

A callback with no error means the datagram was handed to the transport, not that it was delivered. The broker drops datagrams once 262,144 bytes of upload are queued, and nothing reports that back.

connect(port, address?, cb?) fixes the peer. After that, send(msg, cb?) needs no target, and remoteAddress() answers the peer:

app.ts
import * as
import dgram
dgram
from '@fkn/lib/dgram'
const
const socket: dgram.Socket
socket
=
import dgram
dgram
.
function createSocket(options: SocketType | SocketOptions, callback?: (msg: Buffer, rinfo: RemoteInfo) => void): dgram.Socket
export createSocket
createSocket
({
SocketOptions.type: SocketType
type
: 'udp4' })
const socket: dgram.Socket
socket
.
NodeJS.EventEmitter<any>.on<string | symbol>(eventName: string | symbol, listener: (...args: any[]) => void): dgram.Socket

Adds the listener function to the end of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times.

server.on('connection', (stream) => {
console.log('someone connected!');
});

Returns a reference to the EventEmitter, so that calls can be chained.

By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array.

import { EventEmitter } from 'node:events';
const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a

@sincev0.1.101

@parameventName The name of the event.

@paramlistener The callback function

on
('error', (
error: Error
error
:
interface Error
Error
) =>
error: Error
error
.
Error.message: string
message
) // the relay's refusal, when the peer is one it does not reach
const socket: dgram.Socket
socket
.
Socket.bind(callback?: (() => void) | undefined): dgram.Socket (+3 overloads)

For UDP sockets, causes the dgram.Socket to listen for datagram messages on a named port and optional address. If port is not specified or is 0, the operating system will attempt to bind to a random port. If address is not specified, the operating system will attempt to listen on all addresses. Once binding is complete, a 'listening' event is emitted and the optional callback function is called.

Specifying both a 'listening' event listener and passing a callback to the socket.bind() method is not harmful but not very useful.

A bound datagram socket keeps the Node.js process running to receive datagram messages.

If binding fails, an 'error' event is generated. In rare case (e.g. attempting to bind with a closed socket), an Error may be thrown.

Example of a UDP server listening on port 41234:

import dgram from 'node:dgram';
const server = dgram.createSocket('udp4');
server.on('error', (err) => {
console.error(`server error:\n${err.stack}`);
server.close();
});
server.on('message', (msg, rinfo) => {
console.log(`server got: ${msg} from ${rinfo.address}:${rinfo.port}`);
});
server.on('listening', () => {
const address = server.address();
console.log(`server listening ${address.address}:${address.port}`);
});
server.bind(41234);
// Prints: server listening 0.0.0.0:41234

bind
(() => {
const socket: dgram.Socket
socket
.
Socket.connect(port: number, address?: string, callback?: () => void): void (+1 overload)

Associates the dgram.Socket to a remote address and port. Every message sent by this handle is automatically sent to that destination. Also, the socket will only receive messages from that remote peer. Trying to call connect() on an already connected socket will result in an ERR_SOCKET_DGRAM_IS_CONNECTED exception. If address is not provided, '127.0.0.1' (for udp4 sockets) or '::1' (for udp6 sockets) will be used by default. Once the connection is complete, a 'connect' event is emitted and the optional callback function is called. In case of failure, the callback is called or, failing this, an 'error' event is emitted.

connect
(6881, '67.215.246.10', () => {
const socket: dgram.Socket
socket
.
Socket.remoteAddress(): AddressInfo

Returns an object containing the address, family, and port of the remote endpoint. This method throws an ERR_SOCKET_DGRAM_NOT_CONNECTED exception if the socket is not connected.

@sincev12.0.0

remoteAddress
() // { address: '67.215.246.10', family: 'IPv4', port: 6881 }
const socket: dgram.Socket
socket
.
Socket.send(msg: string | readonly any[] | Uint8Array, callback?: ((error: Error | null, bytes: number) => void) | undefined): void (+6 overloads)

Broadcasts a datagram on the socket. For connectionless sockets, the destination port and address must be specified. Connected sockets, on the other hand, will use their associated remote endpoint, so the port and address arguments must not be set.

The msg argument contains the message to be sent. Depending on its type, different behavior can apply. If msg is a Buffer, any TypedArray or a DataView, the offset and length specify the offset within the Buffer where the message begins and the number of bytes in the message, respectively. If msg is a String, then it is automatically converted to a Buffer with 'utf8' encoding. With messages that contain multi-byte characters, offset and length will be calculated with respect to byte length and not the character position. If msg is an array, offset and length must not be specified.

The address argument is a string. If the value of address is a host name, DNS will be used to resolve the address of the host. If address is not provided or otherwise nullish, '127.0.0.1' (for udp4 sockets) or '::1' (for udp6 sockets) will be used by default.

If the socket has not been previously bound with a call to bind, the socket is assigned a random port number and is bound to the "all interfaces" address ('0.0.0.0' for udp4 sockets, '::0' for udp6 sockets.)

An optional callback function may be specified to as a way of reporting DNS errors or for determining when it is safe to reuse the buf object. DNS lookups delay the time to send for at least one tick of the Node.js event loop.

The only way to know for sure that the datagram has been sent is by using a callback. If an error occurs and a callback is given, the error will be passed as the first argument to the callback. If a callback is not given, the error is emitted as an 'error' event on the socket object.

Offset and length are optional but both must be set if either are used. They are supported only when the first argument is a Buffer, a TypedArray, or a DataView.

This method throws ERR_SOCKET_BAD_PORT if called on an unbound socket.

Example of sending a UDP packet to a port on localhost;

import dgram from 'node:dgram';
import { Buffer } from 'node:buffer';
const message = Buffer.from('Some bytes');
const client = dgram.createSocket('udp4');
client.send(message, 41234, 'localhost', (err) => {
client.close();
});

Example of sending a UDP packet composed of multiple buffers to a port on127.0.0.1;

import dgram from 'node:dgram';
import { Buffer } from 'node:buffer';
const buf1 = Buffer.from('Some ');
const buf2 = Buffer.from('bytes');
const client = dgram.createSocket('udp4');
client.send([buf1, buf2], 41234, (err) => {
client.close();
});

Sending multiple buffers might be faster or slower depending on the application and operating system. Run benchmarks to determine the optimal strategy on a case-by-case basis. Generally speaking, however, sending multiple buffers is faster.

Example of sending a UDP packet using a socket connected to a port on localhost:

import dgram from 'node:dgram';
import { Buffer } from 'node:buffer';
const message = Buffer.from('Some bytes');
const client = dgram.createSocket('udp4');
client.connect(41234, 'localhost', (err) => {
client.send(message, (err) => {
client.close();
});
});

send
('ping', (
error: Error | null
error
,
bytes: number
bytes
) =>
bytes: number
bytes
) // 4, to the connected peer
})
})

disconnect() clears the peer at once. It throws ERR_SOCKET_DGRAM_NOT_CONNECTED when there is none, and so does remoteAddress(). Do not call it on the line after a send: send reads the peer one microtask later and would fall back to the loopback default.

Every UDP setter that reaches the relay sends a code from @fkn/lib/wire, and the rest are local. A setter called before bind emits error with Cannot set socket option before bind, the UDP twin of the TCP message in socket options:

SetterOn the wireNotes
setBroadcast(flag)UDP_OPTION_BROADCAST
setTTL(ttl)UDP_OPTION_TTLreturns ttl
setMulticastTTL(ttl)UDP_OPTION_MULTICAST_TTL_V4the v4 code only
setMulticastLoopback(flag)both loopback codes
addMembership(group, iface?), dropMembership(group, iface?)the v4 codes with the interface address, 0.0.0.0 by default, or the v6 codes with ifIndex: 0a group that is neither emits Invalid multicast address: <address>
setSendBufferSize(size), setRecvBufferSize(size)the two buffer codesthe getters echo your last request, 0 before any
setMulticastInterface, addSourceSpecificMembership, dropSourceSpecificMembership, ref, unrefnothingno-ops
getSendQueueSize(), getSendQueueCount()nothingalways 0

The relay decides what a membership does with the address it is given, and refuses through the same error event. The TTL, multicast TTL and loopback rows send nothing until you call them, and there is no getter to read them back.

The broker dials WebTransport first. It falls back to a WebSocket when the realm has no WebTransport, when the WebTransport setup does not complete, or for a while after a session stalled right after its setup. That choice is feature detection plus a switch fixed when the broker is built.

Your sockets share one relay session, and a lost session closes every socket on it. The next socket call dials again, since nothing reconnects on its own:

app.ts
import * as
import dgram
dgram
from '@fkn/lib/dgram'
let
let closing: boolean
closing
= false
const
const open: () => dgram.Socket
open
= () => {
const
const next: dgram.Socket
next
=
import dgram
dgram
.
function createSocket(options: SocketType | SocketOptions, callback?: (msg: Buffer, rinfo: RemoteInfo) => void): dgram.Socket
export createSocket
createSocket
('udp4')
const next: dgram.Socket
next
.
NodeJS.EventEmitter<any>.on<string | symbol>(eventName: string | symbol, listener: (...args: any[]) => void): dgram.Socket

Adds the listener function to the end of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times.

server.on('connection', (stream) => {
console.log('someone connected!');
});

Returns a reference to the EventEmitter, so that calls can be chained.

By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array.

import { EventEmitter } from 'node:events';
const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a

@sincev0.1.101

@parameventName The name of the event.

@paramlistener The callback function

on
('error', (
error: Error
error
:
interface Error
Error
) =>
error: Error
error
.
Error.message: string
message
) // 'WebVPN session closed' when the session dropped
const next: dgram.Socket
next
.
NodeJS.EventEmitter<any>.on<string | symbol>(eventName: string | symbol, listener: (...args: any[]) => void): dgram.Socket

Adds the listener function to the end of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times.

server.on('connection', (stream) => {
console.log('someone connected!');
});

Returns a reference to the EventEmitter, so that calls can be chained.

By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array.

import { EventEmitter } from 'node:events';
const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a

@sincev0.1.101

@parameventName The name of the event.

@paramlistener The callback function

on
('close', () => { if (!
let closing: boolean
closing
)
function setTimeout<[]>(callback: () => void, delay?: number): NodeJS.Timeout (+1 overload)
setTimeout
(() => {
let socket: dgram.Socket
socket
=
const open: () => dgram.Socket
open
() }, 1_000) }) // a lost session, so bind again on a fresh one
const next: dgram.Socket
next
.
Socket.bind(port?: number | undefined, address?: string | undefined, callback?: (() => void) | undefined): dgram.Socket (+3 overloads)

For UDP sockets, causes the dgram.Socket to listen for datagram messages on a named port and optional address. If port is not specified or is 0, the operating system will attempt to bind to a random port. If address is not specified, the operating system will attempt to listen on all addresses. Once binding is complete, a 'listening' event is emitted and the optional callback function is called.

Specifying both a 'listening' event listener and passing a callback to the socket.bind() method is not harmful but not very useful.

A bound datagram socket keeps the Node.js process running to receive datagram messages.

If binding fails, an 'error' event is generated. In rare case (e.g. attempting to bind with a closed socket), an Error may be thrown.

Example of a UDP server listening on port 41234:

import dgram from 'node:dgram';
const server = dgram.createSocket('udp4');
server.on('error', (err) => {
console.error(`server error:\n${err.stack}`);
server.close();
});
server.on('message', (msg, rinfo) => {
console.log(`server got: ${msg} from ${rinfo.address}:${rinfo.port}`);
});
server.on('listening', () => {
const address = server.address();
console.log(`server listening ${address.address}:${address.port}`);
});
server.bind(41234);
// Prints: server listening 0.0.0.0:41234

bind
(6881)
return
const next: dgram.Socket
next
}
let
let socket: dgram.Socket
socket
=
const open: () => dgram.Socket
open
()
const
const stop: () => void
stop
= () => { // your own teardown, which must not rebind
let closing: boolean
closing
= true
let socket: dgram.Socket
socket
.
Socket.close(callback?: (() => void) | undefined): dgram.Socket

Close the underlying socket and stop listening for data on it. If a callback is provided, it is added as a listener for the 'close' event.

@sincev0.1.99

@paramcallback Called when the socket has been closed.

close
() // the socket that is bound right now, whichever rebind produced it
}

The rebind is yours, gated by a flag you set before your own close(), because the close event follows a session loss and an intentional close alike. Every rebind produces a new socket, so keep the handle in a variable you reassign and close that one.

The error before that close is guarded on two holders. A net.Server losing its listener and a dgram.Socket losing its binding emit it only when you attached a listener, so an unwatched one simply closes.

A TCP net.Socket has no such guard and surfaces the failure whether you listen or not, so every socket wants an error listener. The relay reaches public addresses only, and the refusals you meet are in the table below.

Every open socket holds a busy token, and you release it by closing the socket: tcp socket from connect until destroy, tcp server from listen until the first close, and udp socket from bind until close or a transport loss. A refused connect or bind releases it at once.

shell.busyReasons() lists the tokens held in this realm. See busy tokens. The list is per realm. A worker that imports @fkn/lib/net keeps its own tally, and shell.busyReasons() on the page cannot see it.

An app whose transfers live in a worker asks the worker for its own shell.busyReasons(), not the page, before it reloads. See run sockets in a worker.

await using closes a dgram.Socket and does nothing for a net.Server, so call close() on a server yourself.

connect, listen and bind are also the only calls that bound their broker wait with apiWithin: 8,000 ms for the first call, then 1,000 ms per call once any deadline was missed. A miss lands on the error event as a BrokerUnreachableError.

On the working path the token appears with listen and goes with the first close. With no broker at all the error branch runs instead, and the listen callback never fires:

app.ts
import {
(alias) namespace shell
import shell
shell
} from '@fkn/lib'
import * as
import net
net
from '@fkn/lib/net'
import {
class BrokerUnreachableError
BrokerUnreachableError
,
const API_DEADLINE_MS: 8000

apiPromise bounded by a deadline, for call sites that must not park forever.

apiPromise NEVER REJECTS: epochs.first settles only when a broker connection exists, so a broker frame that never bridges leaves it pending for the life of the realm. Awaiting it directly is correct wherever hanging is the honest answer, and wrong wherever the caller owns a socket, a timer or a UI that has to say something.

That distinction is not academic. In a WORKER realm the osra transport is {receive: self, emit: self}, which is inert until the page bridges it, so an unbridged worker parks every socket call here with no listening, no error and no rejection. The engine then reports a listener that neither succeeded nor failed, its reopen counters stay at 0 because reopen only runs from an error or close that never arrives, and the relay is never contacted at all. That state cost a long diagnosis: it presents as a transport fault and is invisible from every counter.

The latch mirrors storage.ts: once the broker has missed one deadline, later calls stop paying the full wait. net.ts needs it especially, because its listen path is bind('::').catch(() => bind('0.0.0.0')), so an unbounded-then-rejecting version would charge the deadline twice.

API_DEADLINE_MS
} from '@fkn/lib/api'
const
const server: net.Server
server
=
import net
net
.
function createServer(options?: ServerOpts | ((socket: net.Socket) => void), connectionListener?: (socket: net.Socket) => void): net.Server
export createServer
createServer
((
peer: net.Socket
peer
) =>
peer: net.Socket
peer
.
Stream.Writable.end(cb?: () => void): net.Socket (+2 overloads)

Calling the writable.end() method signals that no more data will be written to the Writable. The optional chunk and encoding arguments allow one final additional chunk of data to be written immediately before closing the stream.

Calling the

write

method after calling

end

will raise an error.

// Write 'hello, ' and then end with 'world!'.
import fs from 'node:fs';
const file = fs.createWriteStream('example.txt');
file.write('hello, ');
file.end('world!');
// Writing more now is not allowed!

@sincev0.9.4

@paramcb Callback for when the stream is finished.

end
())
const server: net.Server
server
.
NodeJS.EventEmitter<any>.on<string | symbol>(eventName: string | symbol, listener: (...args: any[]) => void): net.Server

Adds the listener function to the end of the listeners array for the event named eventName. No checks are made to see if the listener has already been added. Multiple calls passing the same combination of eventName and listener will result in the listener being added, and called, multiple times.

server.on('connection', (stream) => {
console.log('someone connected!');
});

Returns a reference to the EventEmitter, so that calls can be chained.

By default, event listeners are invoked in the order they are added. The emitter.prependListener() method can be used as an alternative to add the event listener to the beginning of the listeners array.

import { EventEmitter } from 'node:events';
const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// Prints:
// b
// a

@sincev0.1.101

@parameventName The name of the event.

@paramlistener The callback function

on
('error', (
error: Error
error
:
interface Error
Error
) => {
if (
error: Error
error
instanceof
class BrokerUnreachableError
BrokerUnreachableError
) {
const API_DEADLINE_MS: 8000

apiPromise bounded by a deadline, for call sites that must not park forever.

apiPromise NEVER REJECTS: epochs.first settles only when a broker connection exists, so a broker frame that never bridges leaves it pending for the life of the realm. Awaiting it directly is correct wherever hanging is the honest answer, and wrong wherever the caller owns a socket, a timer or a UI that has to say something.

That distinction is not academic. In a WORKER realm the osra transport is {receive: self, emit: self}, which is inert until the page bridges it, so an unbridged worker parks every socket call here with no listening, no error and no rejection. The engine then reports a listener that neither succeeded nor failed, its reopen counters stay at 0 because reopen only runs from an error or close that never arrives, and the relay is never contacted at all. That state cost a long diagnosis: it presents as a transport fault and is invisible from every counter.

The latch mirrors storage.ts: once the broker has missed one deadline, later calls stop paying the full wait. net.ts needs it especially, because its listen path is bind('::').catch(() => bind('0.0.0.0')), so an unbounded-then-rejecting version would charge the deadline twice.

API_DEADLINE_MS
// 8000
error: BrokerUnreachableError
error
.
Error.message: string
message
// @fkn/lib: no broker connection within 8000ms, so a tcp listener could not be requested
}
})
const server: net.Server
server
.
Server$1.listen(port?: number | undefined, hostname?: string | undefined, listeningListener?: (() => void) | undefined): net.Server (+9 overloads)

Start a server listening for connections. A net.Server can be a TCP or an IPC server depending on what it listens to.

Possible signatures:

  • server.listen(handle[, backlog][, callback])
  • server.listen(options[, callback])
  • server.listen(path[, backlog][, callback]) for IPC servers
  • server.listen([port[, host[, backlog]]][, callback]) for TCP servers

This function is asynchronous. When the server starts listening, the 'listening' event will be emitted. The last parameter callbackwill be added as a listener for the 'listening' event.

All listen() methods can take a backlog parameter to specify the maximum length of the queue of pending connections. The actual length will be determined by the OS through sysctl settings such as tcp_max_syn_backlog and somaxconn on Linux. The default value of this parameter is 511 (not 512).

All

Socket

are set to SO_REUSEADDR (see socket(7) for details).

The server.listen() method can be called again if and only if there was an error during the first server.listen() call or server.close() has been called. Otherwise, an ERR_SERVER_ALREADY_LISTEN error will be thrown.

One of the most common errors raised when listening is EADDRINUSE. This happens when another server is already listening on the requestedport/path/handle. One way to handle this would be to retry after a certain amount of time:

server.on('error', (e) => {
if (e.code === 'EADDRINUSE') {
console.error('Address in use, retrying...');
setTimeout(() => {
server.close();
server.listen(PORT, HOST);
}, 1000);
}
});

listen
(6881, '0.0.0.0', () => {
(alias) namespace shell
import shell
shell
.
shell_d_exports.busyReasons(): string[]
export shell_d_exports.busyReasons

What a shell reload would sever right now, as a list of human-readable reasons, empty when nothing is bound to the broker connection.

This is the evidence for deciding whether to call applyUpdate yet: open sockets and listening servers, a streaming proxy response, a mounted package, a live frame attachment, unflushed write-behind. An empty list is not a promise that a reload is free, only that this realm holds nothing the lib knows about, so an app with state of its own should weigh that too.

It is per REALM, and that distinction has already caused a wrong reading once. A worker that imports @fkn/lib/net keeps its own tally, which this function cannot see from the window. An app whose transfers live in a worker should ask the worker, not the page.

busyReasons
() // ['tcp server (1)'] with nothing else open
const server: net.Server
server
.
Server$1.close(callback?: ((err?: Error | undefined) => void) | undefined): net.Server

Stops the server from accepting new connections and keeps existing connections. This function is asynchronous, the server is finally closed when all connections are ended and the server emits a 'close' event. The optional callback will be called once the 'close' event occurs. Unlike that event, it will be called with an Error as its only argument if the server was not open when it was closed.

@sincev0.1.90

@paramcallback Called when the server is closed.

close
(() => {
(alias) namespace shell
import shell
shell
.
shell_d_exports.busyReasons(): string[]
export shell_d_exports.busyReasons

What a shell reload would sever right now, as a list of human-readable reasons, empty when nothing is bound to the broker connection.

This is the evidence for deciding whether to call applyUpdate yet: open sockets and listening servers, a streaming proxy response, a mounted package, a live frame attachment, unflushed write-behind. An empty list is not a promise that a reload is free, only that this realm holds nothing the lib knows about, so an app with state of its own should weigh that too.

It is per REALM, and that distinction has already caused a wrong reading once. A worker that imports @fkn/lib/net keeps its own tally, which this function cannot see from the window. An app whose transfers live in a worker should ask the worker, not the page.

busyReasons
() // [], the token went with the first close
})
})

http pays the same deadline through the net.Socket every request opens, see HTTP and DNS.

A worker that nobody relayed shows up here. Its sockets emit this error, where every other call would wait forever.

The fix is to relay the worker from the page before it opens its first socket. See workers.

The six a socket most often ends on, each linked to its row:

MessageWhat happened
@fkn/lib: no broker connection within <ms>ms, so <what> could not be requestedBrokerUnreachableError: no broker within the deadline. In a worker, nobody ran relayWorker.
getaddrinfo ENOTFOUND <hostname>The broker’s lookup found no A or AAAA answer, before any relay was dialed. Match the message: the code does not survive the hop.
tcp connect to <address>:<port> timed out after 12000msThe relay did not acknowledge the connect within 12,000 ms. Retry.
webvpn: egress to a non-public address refusedThe target is not a public address, and no local listener paired with it.
webvpn: socket capacity reachedThe session is at its socket limit. Close what you no longer need, then retry.
WebVPN session closedThe relay session dropped and took every socket with it. The next call dials again.

None of these carries an errno. The only code a socket error carries is ERR_BUFFER_OUT_OF_BOUNDS on the range form of send, so match on error.message. See handling errors.

Every other message has its row on every error. The exact signatures live in the generated API reference for net, dgram, wire and @fkn/lib/api.