| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 97b7a3f commit ef38374
3 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -363,6 +363,44 @@ socket.bind({ | |||
| 363 | 363 | }); | |
| 364 | 364 | ``` | |
| 365 | 365 | ||
| 366 | + ### `socket.bindSync([options])` | ||
| 367 | + | ||
| 368 | + <!-- YAML | ||
| 369 | + added: REPLACEME | ||
| 370 | + --> | ||
| 371 | + | ||
| 372 | + * `options` {Object} | ||
| 373 | + * `port` {integer} If omitted or `0`, the operating system will assign an | ||
| 374 | + arbitrary unused port. **Default:** `0`. | ||
| 375 | + * `address` {string} A numeric IP address to bind to. Unlike | ||
| 376 | + [`socket.bind()`][], no DNS resolution is performed, so a host name is not | ||
| 377 | + accepted. If omitted, the operating system binds to all addresses | ||
| 378 | + (`'0.0.0.0'` for `udp4` sockets, `'::'` for `udp6`). | ||
| 379 | + * Returns: {Object} The bound address as returned by [`socket.address()`][]. | ||
| 380 | + | ||
| 381 | + The synchronous counterpart of [`socket.bind()`][]. `bind(2)` is a local, | ||
| 382 | + non-blocking system call, so the bind is performed inline and the resolved | ||
| 383 | + address is returned immediately, including the operating-system-assigned | ||
| 384 | + ephemeral port when `port` is `0`: | ||
| 385 | + | ||
| 386 | + ```js | ||
| 387 | + const dgram = require('node:dgram'); | ||
| 388 | + | ||
| 389 | + const socket = dgram.createSocket('udp4'); | ||
| 390 | + const address = socket.bindSync({ address: '0.0.0.0', port: 0 }); | ||
| 391 | + console.log(address); // e.g. { address: '0.0.0.0', family: 'IPv4', port: 53124 } | ||
| 392 | + ``` | ||
| 393 | + | ||
| 394 | + A bind failure such as `EADDRINUSE` is thrown synchronously rather than emitted | ||
| 395 | + as an `'error'` event. After `bindSync()` returns, [`socket.address()`][] is | ||
| 396 | + valid synchronously and the `'listening'` event is emitted on the next tick. | ||
| 397 | + | ||
| 398 | + `address` must be a numeric IP literal; `bindSync()` never performs DNS | ||
| 399 | + resolution (asynchronous name resolution being the only genuinely blocking part | ||
| 400 | + of binding). Incoming datagrams continue to be delivered asynchronously via the | ||
| 401 | + [`'message'`][] event. `bindSync()` always binds the socket's own handle and | ||
| 402 | + does not participate in [`cluster`][] handle sharing. | ||
| 403 | + | ||
| 366 | 404 | ### `socket.close([callback])` | |
| 367 | 405 | ||
| 368 | 406 | <!-- YAML | |
@@ -1017,6 +1055,7 @@ and `udp6` sockets). The bound address and port can be retrieved using | |||
| 1017 | 1055 | [IPv6 Zone Indexes]: https://en.wikipedia.org/wiki/IPv6_address#Scoped_literal_IPv6_addresses | |
| 1018 | 1056 | [RFC 4007]: https://tools.ietf.org/html/rfc4007 | |
| 1019 | 1057 | [`'close'`]: #event-close | |
| 1058 | + [`'message'`]: #event-message | ||
| 1020 | 1059 | [`ERR_SOCKET_BAD_PORT`]: errors.md#err_socket_bad_port | |
| 1021 | 1060 | [`ERR_SOCKET_BUFFER_SIZE`]: errors.md#err_socket_buffer_size | |
| 1022 | 1061 | [`ERR_SOCKET_DGRAM_IS_CONNECTED`]: errors.md#err_socket_dgram_is_connected | |
@@ -1030,6 +1069,7 @@ and `udp6` sockets). The bound address and port can be retrieved using | |||
| 1030 | 1069 | [`dns.lookup()`]: dns.md#dnslookuphostname-options-callback | |
| 1031 | 1070 | [`socket.address().address`]: #socketaddress | |
| 1032 | 1071 | [`socket.address().port`]: #socketaddress | |
| 1072 | + [`socket.address()`]: #socketaddress | ||
| 1033 | 1073 | [`socket.bind()`]: #socketbindport-address-callback | |
| 1034 | 1074 | [`socket.close()`]: #socketclosecallback | |
| 1035 | 1075 | [byte length]: buffer.md#static-method-bufferbytelengthstring-encoding | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -39,6 +39,7 @@ const { | |||
| 39 | 39 | codes: { | |
| 40 | 40 | ERR_BUFFER_OUT_OF_BOUNDS, | |
| 41 | 41 | ERR_INVALID_ARG_TYPE, | |
| 42 | + ERR_INVALID_ARG_VALUE, | ||
| 42 | 43 | ERR_INVALID_FD_TYPE, | |
| 43 | 44 | ERR_IP_BLOCKED, | |
| 44 | 45 | ERR_MISSING_ARGS, | |
@@ -59,13 +60,14 @@ const { isIP } = require('internal/net'); | |||
| 59 | 60 | const { | |
| 60 | 61 | isInt32, | |
| 61 | 62 | validateAbortSignal, | |
| 63 | + validateObject, | ||
| 62 | 64 | validateString, | |
| 63 | 65 | validateNumber, | |
| 64 | 66 | validatePort, | |
| 65 | 67 | validateUint32, | |
| 66 | 68 | } = require('internal/validators'); | |
| 67 | 69 | const { Buffer } = require('buffer'); | |
| 68 | - const { deprecate, guessHandleType, promisify } = require('internal/util'); | ||
| 70 | + const { deprecate, guessHandleType, kEmptyObject, promisify } = require('internal/util'); | ||
| 69 | 71 | const { isArrayBufferView } = require('internal/util/types'); | |
| 70 | 72 | const EventEmitter = require('events'); | |
| 71 | 73 | const { addAbortListener } = require('internal/events/abort_listener'); | |
@@ -193,7 +195,7 @@ function createSocket(type, listener) { | |||
| 193 | 195 | } | |
| 194 | 196 | ||
| 195 | 197 | ||
| 196 | - function startListening(socket) { | ||
| 198 | + function startReceiving(socket) { | ||
| 197 | 199 | const state = socket[kStateSymbol]; | |
| 198 | 200 | ||
| 199 | 201 | state.handle.onmessage = onMessage; | |
@@ -207,10 +209,19 @@ function startListening(socket) { | |||
| 207 | 209 | ||
| 208 | 210 | if (state.sendBufferSize) | |
| 209 | 211 | bufferSize(socket, state.sendBufferSize, SEND_BUFFER); | |
| 212 | + } | ||
| 210 | 213 | ||
| 214 | + function startListening(socket) { | ||
| 215 | + startReceiving(socket); | ||
| 211 | 216 | socket.emit('listening'); | |
| 212 | 217 | } | |
| 213 | 218 | ||
| 219 | + function emitListeningNT(socket) { | ||
| 220 | + // Ensure the socket was not closed before the next tick. | ||
| 221 | + if (socket[kStateSymbol].handle) | ||
| 222 | + socket.emit('listening'); | ||
| 223 | + } | ||
| 224 | + | ||
| 214 | 225 | function replaceHandle(self, newHandle) { | |
| 215 | 226 | const state = self[kStateSymbol]; | |
| 216 | 227 | const oldHandle = state.handle; | |
@@ -410,6 +421,58 @@ Socket.prototype.bind = function(port_, address_ /* , callback */) { | |||
| 410 | 421 | return this; | |
| 411 | 422 | }; | |
| 412 | 423 | ||
| 424 | + // Synchronous counterpart of bind(). bind(2) is a local, non-blocking system | ||
| 425 | + // call, so this binds inline and returns the resolved address (including the | ||
| 426 | + // OS-assigned ephemeral port when port is 0), throwing synchronously on bind | ||
| 427 | + // errors such as EADDRINUSE. The address must be a numeric IP literal: | ||
| 428 | + // asynchronous name resolution is the only genuinely blocking part of bind(), | ||
| 429 | + // so callers resolve names separately. Message delivery stays asynchronous | ||
| 430 | + // ('message' events flow as usual); the 'listening' event is emitted on the | ||
| 431 | + // next tick. | ||
| 432 | + Socket.prototype.bindSync = function(options = kEmptyObject) { | ||
| 433 | + healthCheck(this); | ||
| 434 | + validateObject(options, 'options'); | ||
| 435 | + const state = this[kStateSymbol]; | ||
| 436 | + | ||
| 437 | + if (state.bindState !== BIND_STATE_UNBOUND) | ||
| 438 | + throw new ERR_SOCKET_ALREADY_BOUND(); | ||
| 439 | + | ||
| 440 | + // Validate arguments before mutating state so a bad argument leaves the | ||
| 441 | + // socket unbound and reusable. | ||
| 442 | + const port = validatePort(options.port ?? 0, 'options.port'); | ||
| 443 | + let { address } = options; | ||
| 444 | + if (!address) { | ||
| 445 | + address = this.type === 'udp4' ? '0.0.0.0' : '::'; | ||
| 446 | + } else { | ||
| 447 | + validateString(address, 'options.address'); | ||
| 448 | + if (isIP(address) === 0) { | ||
| 449 | + throw new ERR_INVALID_ARG_VALUE( | ||
| 450 | + 'options.address', address, | ||
| 451 | + 'must be a numeric IP address; bindSync does not perform DNS resolution'); | ||
| 452 | + } | ||
| 453 | + } | ||
| 454 | + | ||
| 455 | + state.bindState = BIND_STATE_BINDING; | ||
| 456 | + | ||
| 457 | + let flags = 0; | ||
| 458 | + if (state.reuseAddr) | ||
| 459 | + flags |= UV_UDP_REUSEADDR; | ||
| 460 | + if (state.ipv6Only) | ||
| 461 | + flags |= UV_UDP_IPV6ONLY; | ||
| 462 | + if (state.reusePort) | ||
| 463 | + flags |= UV_UDP_REUSEPORT; | ||
| 464 | + | ||
| 465 | + const err = state.handle.bind(address, port, flags); | ||
| 466 | + if (err) { | ||
| 467 | + state.bindState = BIND_STATE_UNBOUND; | ||
| 468 | + throw new ExceptionWithHostPort(err, 'bind', address, port); | ||
| 469 | + } | ||
| 470 | + | ||
| 471 | + startReceiving(this); | ||
| 472 | + process.nextTick(emitListeningNT, this); | ||
| 473 | + return this.address(); | ||
| 474 | + }; | ||
| 475 | + | ||
| 413 | 476 | Socket.prototype.connect = function(port, address, callback) { | |
| 414 | 477 | port = validatePort(port, 'Port', false); | |
| 415 | 478 | if (typeof address === 'function') { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,172 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + const common = require('../common'); | ||
| 3 | + const assert = require('assert'); | ||
| 4 | + const dgram = require('dgram'); | ||
| 5 | + | ||
| 6 | + // bindSync() binds synchronously and returns the resolved address, including | ||
| 7 | + // the OS-assigned ephemeral port when port is 0. | ||
| 8 | + { | ||
| 9 | + const sock = dgram.createSocket('udp4'); | ||
| 10 | + const addr = sock.bindSync({ address: '127.0.0.1', port: 0 }); | ||
| 11 | + | ||
| 12 | + assert.strictEqual(addr.address, '127.0.0.1'); | ||
| 13 | + assert.strictEqual(addr.family, 'IPv4'); | ||
| 14 | + assert.strictEqual(typeof addr.port, 'number'); | ||
| 15 | + assert.ok(addr.port > 0); | ||
| 16 | + | ||
| 17 | + // address() is valid synchronously and matches the returned address. | ||
| 18 | + assert.deepStrictEqual(sock.address(), addr); | ||
| 19 | + | ||
| 20 | + // The 'listening' event still fires on the next tick. | ||
| 21 | + sock.on('listening', common.mustCall(() => sock.close())); | ||
| 22 | + } | ||
| 23 | + | ||
| 24 | + // Closing synchronously after bindSync() suppresses the deferred 'listening'. | ||
| 25 | + { | ||
| 26 | + const sock = dgram.createSocket('udp4'); | ||
| 27 | + sock.bindSync({ port: 0 }); | ||
| 28 | + sock.on('listening', common.mustNotCall()); | ||
| 29 | + sock.close(); | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + // Defaults the address to the udp4 wildcard when omitted. | ||
| 33 | + { | ||
| 34 | + const sock = dgram.createSocket('udp4'); | ||
| 35 | + const addr = sock.bindSync(); | ||
| 36 | + assert.strictEqual(addr.address, '0.0.0.0'); | ||
| 37 | + assert.ok(addr.port > 0); | ||
| 38 | + sock.close(); | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + // 'message' events still flow asynchronously after a synchronous bind. | ||
| 42 | + { | ||
| 43 | + const receiver = dgram.createSocket('udp4'); | ||
| 44 | + const addr = receiver.bindSync({ address: '127.0.0.1', port: 0 }); | ||
| 45 | + | ||
| 46 | + receiver.on('message', common.mustCall((msg) => { | ||
| 47 | + assert.strictEqual(msg.toString(), 'hello'); | ||
| 48 | + receiver.close(); | ||
| 49 | + })); | ||
| 50 | + | ||
| 51 | + const sender = dgram.createSocket('udp4'); | ||
| 52 | + sender.send('hello', addr.port, '127.0.0.1', common.mustCall(() => { | ||
| 53 | + sender.close(); | ||
| 54 | + })); | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + // Throws synchronously on EADDRINUSE. | ||
| 58 | + { | ||
| 59 | + const first = dgram.createSocket('udp4'); | ||
| 60 | + const addr = first.bindSync({ address: '127.0.0.1', port: 0 }); | ||
| 61 | + | ||
| 62 | + const second = dgram.createSocket('udp4'); | ||
| 63 | + assert.throws(() => { | ||
| 64 | + second.bindSync({ address: '127.0.0.1', port: addr.port }); | ||
| 65 | + }, { | ||
| 66 | + code: 'EADDRINUSE', | ||
| 67 | + syscall: 'bind', | ||
| 68 | + }); | ||
| 69 | + | ||
| 70 | + first.close(); | ||
| 71 | + second.close(); | ||
| 72 | + } | ||
| 73 | + | ||
| 74 | + // Throws synchronously on a non-numeric address (no DNS resolution). | ||
| 75 | + { | ||
| 76 | + const sock = dgram.createSocket('udp4'); | ||
| 77 | + assert.throws(() => { | ||
| 78 | + sock.bindSync({ address: 'localhost', port: 0 }); | ||
| 79 | + }, { | ||
| 80 | + code: 'ERR_INVALID_ARG_VALUE', | ||
| 81 | + name: 'TypeError', | ||
| 82 | + }); | ||
| 83 | + sock.close(); | ||
| 84 | + } | ||
| 85 | + | ||
| 86 | + // Rejects a non-string address. | ||
| 87 | + { | ||
| 88 | + const sock = dgram.createSocket('udp4'); | ||
| 89 | + assert.throws(() => sock.bindSync({ address: 12345 }), { | ||
| 90 | + code: 'ERR_INVALID_ARG_TYPE', | ||
| 91 | + }); | ||
| 92 | + sock.close(); | ||
| 93 | + } | ||
| 94 | + | ||
| 95 | + // A rejected argument leaves the socket unbound and reusable. | ||
| 96 | + { | ||
| 97 | + const sock = dgram.createSocket('udp4'); | ||
| 98 | + assert.throws(() => sock.bindSync({ port: -1 }), { | ||
| 99 | + code: 'ERR_SOCKET_BAD_PORT', | ||
| 100 | + }); | ||
| 101 | + const addr = sock.bindSync({ port: 0 }); | ||
| 102 | + assert.ok(addr.port > 0); | ||
| 103 | + sock.close(); | ||
| 104 | + } | ||
| 105 | + | ||
| 106 | + // Throws when already bound. | ||
| 107 | + { | ||
| 108 | + const sock = dgram.createSocket('udp4'); | ||
| 109 | + sock.bindSync({ port: 0 }); | ||
| 110 | + assert.throws(() => sock.bindSync({ port: 0 }), { | ||
| 111 | + code: 'ERR_SOCKET_ALREADY_BOUND', | ||
| 112 | + }); | ||
| 113 | + sock.close(); | ||
| 114 | + } | ||
| 115 | + | ||
| 116 | + // Rejects a non-object options argument. | ||
| 117 | + { | ||
| 118 | + const sock = dgram.createSocket('udp4'); | ||
| 119 | + assert.throws(() => sock.bindSync(0), { code: 'ERR_INVALID_ARG_TYPE' }); | ||
| 120 | + sock.close(); | ||
| 121 | + } | ||
| 122 | + | ||
| 123 | + // udp6 wildcard default. | ||
| 124 | + if (common.hasIPv6) { | ||
| 125 | + const sock = dgram.createSocket('udp6'); | ||
| 126 | + const addr = sock.bindSync(); | ||
| 127 | + assert.strictEqual(addr.address, '::'); | ||
| 128 | + assert.strictEqual(addr.family, 'IPv6'); | ||
| 129 | + assert.ok(addr.port > 0); | ||
| 130 | + sock.close(); | ||
| 131 | + } | ||
| 132 | + | ||
| 133 | + // udp6 loopback with an OS-assigned ephemeral port, and async 'message' flow. | ||
| 134 | + if (common.hasIPv6) { | ||
| 135 | + const receiver = dgram.createSocket('udp6'); | ||
| 136 | + const addr = receiver.bindSync({ address: '::1', port: 0 }); | ||
| 137 | + | ||
| 138 | + assert.strictEqual(addr.address, '::1'); | ||
| 139 | + assert.strictEqual(addr.family, 'IPv6'); | ||
| 140 | + assert.ok(addr.port > 0); | ||
| 141 | + assert.deepStrictEqual(receiver.address(), addr); | ||
| 142 | + | ||
| 143 | + receiver.on('message', common.mustCall((msg) => { | ||
| 144 | + assert.strictEqual(msg.toString(), 'hello'); | ||
| 145 | + receiver.close(); | ||
| 146 | + })); | ||
| 147 | + | ||
| 148 | + const sender = dgram.createSocket('udp6'); | ||
| 149 | + sender.send('hello', addr.port, '::1', common.mustCall(() => { | ||
| 150 | + sender.close(); | ||
| 151 | + })); | ||
| 152 | + } | ||
| 153 | + | ||
| 154 | + // A zone-indexed (scoped) IPv6 literal is accepted as a numeric IP; no DNS | ||
| 155 | + // resolution occurs. Interface names are platform-specific, so this binds the | ||
| 156 | + // scoped loopback only where the interface name is known (Linux: 'lo'). | ||
| 157 | + if (common.hasIPv6 && process.platform === 'linux') { | ||
| 158 | + const sock = dgram.createSocket('udp6'); | ||
| 159 | + const addr = sock.bindSync({ address: '::1%lo', port: 0 }); | ||
| 160 | + assert.strictEqual(addr.address, '::1'); | ||
| 161 | + assert.strictEqual(addr.family, 'IPv6'); | ||
| 162 | + assert.ok(addr.port > 0); | ||
| 163 | + sock.close(); | ||
| 164 | + } | ||
| 165 | + | ||
| 166 | + // The ipv6Only flag is honored by the synchronous bind. | ||
| 167 | + if (common.hasIPv6) { | ||
| 168 | + const sock = dgram.createSocket({ type: 'udp6', ipv6Only: true }); | ||
| 169 | + const addr = sock.bindSync({ address: '::', port: 0 }); | ||
| 170 | + assert.strictEqual(addr.family, 'IPv6'); | ||
| 171 | + sock.close(); | ||
| 172 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments