@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:
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).
@since ― v0.5.10
remotePort// 80, and every address getter answers from here on
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:
functionwrite(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.
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.
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.
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.
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.
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().
@since ― v8.0.0
@param ― error 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:
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.
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:
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.
Adds a one-timelistener 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.
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:
functionwrite(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.
new <unknown>(executor: (resolve: (value:unknown) =>void, reject: (reason?:any) =>void) =>void) =>Promise<unknown>
Creates a new Promise.
@param ― executor 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.
Adds a one-timelistener 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.
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';
constfile= fs.createWriteStream('example.txt');
file.write('hello, ');
file.end('world!');
// Writing more now is not allowed!
@since ― v0.9.4
@param ― cb Callback for when the stream is finished.
end() // half-close, the peer sees EOF and its own data still arrives
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.
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:
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).
@since ― v0.5.10
remotePort// a number, the endpoints land before the event
Signals that no more data will be written, with one final chunk of data.
@see ― Writable.end for full details.
@since ― v0.9.4
@param ― chunk 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.
@param ― cb Callback for when the stream is finished.
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.
message) // a refused bind lands here, and no listening follows
constserver: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:
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.
constserver= 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().
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.
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.
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.
a 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)
nothing
cached locally, getTypeOfService() reads it back
ref(), unref()
nothing
return 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:
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.
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.
@since ― v0.1.90
@param ― noDelay
@return ― The socket itself.
setNoDelay(true) // TCP_OPTION_NODELAY travels to the relay once the connection lands
constsocket: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
@since ― v0.1.92
@param ― enable
@param ― initialDelay
@return ― The socket itself.
setKeepAlive(true, 30_000) // 30 seconds on the wire
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.
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().
@since ― v8.0.0
@param ― error 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.
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.
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.
length// the datagram's bytes, the same number as rinfo.size
rinfo: any
rinfo// { address, family: 'IPv4', port, size }
})
constsocket: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';
constserver= 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}`);
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;
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';
constbuf1= Buffer.from('Some ');
constbuf2= Buffer.from('bytes');
constclient= 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:
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.
@since ― v5.10.0
@param ― string A string to encode.
@param ― encoding The encoding of string. Default:'utf8'.
from('ping'), 6881,
consttracker: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.
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.
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:
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.
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';
constserver= 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}`);
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', () => {
constsocket: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.
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;
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';
constbuf1= Buffer.from('Some ');
constbuf2= Buffer.from('bytes');
constclient= 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:
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:
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:
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.
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.
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';
constserver= 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}`);
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.
@since ― v0.1.99
@param ― callback 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.
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:
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.
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';
constfile= fs.createWriteStream('example.txt');
file.write('hello, ');
file.end('world!');
// Writing more now is not allowed!
@since ― v0.9.4
@param ― cb Callback for when the stream is finished.
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.
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.
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:
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
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.
@since ― v0.1.90
@param ― callback Called when the server is closed.
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
The 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.