| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -447,6 +447,47 @@ will be used by default. Once the connection is complete, a `'connect'` event | |||
| 447 | 447 | is emitted and the optional `callback` function is called. In case of failure, | |
| 448 | 448 | the `callback` is called or, failing this, an `'error'` event is emitted. | |
| 449 | 449 | ||
| 450 | + ### `socket.connectSync(port[, address])` | ||
| 451 | + | ||
| 452 | + <!-- YAML | ||
| 453 | + added: REPLACEME | ||
| 454 | + --> | ||
| 455 | + | ||
| 456 | + * `port` {integer} | ||
| 457 | + * `address` {string} A numeric IP address to connect to. Unlike | ||
| 458 | + [`socket.connect()`][], no DNS resolution is performed, so a host name is not | ||
| 459 | + accepted. If omitted, `'127.0.0.1'` (for `udp4` sockets) or `'::1'` (for | ||
| 460 | + `udp6` sockets) is used. | ||
| 461 | + | ||
| 462 | + The synchronous counterpart of [`socket.connect()`][]. For a UDP socket | ||
| 463 | + `connect(2)` only records the default peer address and is a local, non-blocking | ||
| 464 | + system call, so the association is performed inline. Any error raised by the | ||
| 465 | + call itself (for example `EAFNOSUPPORT` for a mismatched address family) is | ||
| 466 | + thrown synchronously rather than reported via the `'error'` event. Because | ||
| 467 | + `connect(2)` does not probe reachability, errors such as `ECONNREFUSED` are | ||
| 468 | + still surfaced asynchronously on a later send or receive, exactly as for | ||
| 469 | + [`socket.connect()`][]: | ||
| 470 | + | ||
| 471 | + ```js | ||
| 472 | + const dgram = require('node:dgram'); | ||
| 473 | + | ||
| 474 | + const socket = dgram.createSocket('udp4'); | ||
| 475 | + socket.connectSync(41234, '127.0.0.1'); | ||
| 476 | + console.log(socket.remoteAddress()); // { address: '127.0.0.1', family: 'IPv4', port: 41234 } | ||
| 477 | + ``` | ||
| 478 | + | ||
| 479 | + If the socket is still unbound it is bound synchronously first. After | ||
| 480 | + `connectSync()` returns, [`socket.remoteAddress()`][] is valid synchronously | ||
| 481 | + and the `'connect'` event is emitted on the next tick. Trying to call | ||
| 482 | + `connectSync()` on an already connected socket throws an | ||
| 483 | + [`ERR_SOCKET_DGRAM_IS_CONNECTED`][] exception, and calling it while an | ||
| 484 | + asynchronous [`socket.bind()`][] is still in progress throws an | ||
| 485 | + [`ERR_SOCKET_ALREADY_BOUND`][] exception. | ||
| 486 | + | ||
| 487 | + `address` must be a numeric IP literal; `connectSync()` never performs DNS | ||
| 488 | + resolution (asynchronous name resolution being the only genuinely blocking part | ||
| 489 | + of connecting). | ||
| 490 | + | ||
| 450 | 491 | ### `socket.disconnect()` | |
| 451 | 492 | ||
| 452 | 493 | <!-- YAML | |
@@ -1054,6 +1095,7 @@ and `udp6` sockets). The bound address and port can be retrieved using | |||
| 1054 | 1095 | [RFC 4007]: https://tools.ietf.org/html/rfc4007 | |
| 1055 | 1096 | [`'close'`]: #event-close | |
| 1056 | 1097 | [`'message'`]: #event-message | |
| 1098 | + [`ERR_SOCKET_ALREADY_BOUND`]: errors.md#err_socket_already_bound | ||
| 1057 | 1099 | [`ERR_SOCKET_BAD_PORT`]: errors.md#err_socket_bad_port | |
| 1058 | 1100 | [`ERR_SOCKET_BUFFER_SIZE`]: errors.md#err_socket_buffer_size | |
| 1059 | 1101 | [`ERR_SOCKET_DGRAM_IS_CONNECTED`]: errors.md#err_socket_dgram_is_connected | |
@@ -1070,4 +1112,6 @@ and `udp6` sockets). The bound address and port can be retrieved using | |||
| 1070 | 1112 | [`socket.address()`]: #socketaddress | |
| 1071 | 1113 | [`socket.bind()`]: #socketbindport-address-callback | |
| 1072 | 1114 | [`socket.close()`]: #socketclosecallback | |
| 1115 | + [`socket.connect()`]: #socketconnectport-address-callback | ||
| 1116 | + [`socket.remoteAddress()`]: #socketremoteaddress | ||
| 1073 | 1117 | [byte length]: buffer.md#static-method-bufferbytelengthstring-encoding | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -221,6 +221,12 @@ function emitListeningNT(socket) { | |||
| 221 | 221 | socket.emit('listening'); | |
| 222 | 222 | } | |
| 223 | 223 | ||
| 224 | + function emitConnectNT(socket) { | ||
| 225 | + // Ensure the socket was not closed before the next tick. | ||
| 226 | + if (socket[kStateSymbol].handle) | ||
| 227 | + socket.emit('connect'); | ||
| 228 | + } | ||
| 229 | + | ||
| 224 | 230 | function replaceHandle(self, newHandle) { | |
| 225 | 231 | const state = self[kStateSymbol]; | |
| 226 | 232 | const oldHandle = state.handle; | |
@@ -501,6 +507,54 @@ Socket.prototype.connect = function(port, address, callback) { | |||
| 501 | 507 | FunctionPrototypeCall(_connect, this, port, address, callback); | |
| 502 | 508 | }; | |
| 503 | 509 | ||
| 510 | + // Synchronous counterpart of connect(). connect(2) on a UDP socket only records | ||
| 511 | + // the default peer locally, so it runs inline and throws on errors raised by | ||
| 512 | + // the call itself; unreachable-peer errors stay asynchronous as for connect(). | ||
| 513 | + // The address must be a numeric IP literal since DNS resolution is async. | ||
| 514 | + Socket.prototype.connectSync = function(port, address) { | ||
| 515 | + healthCheck(this); | ||
| 516 | + port = validatePort(port, 'Port', false); | ||
| 517 | + | ||
| 518 | + const state = this[kStateSymbol]; | ||
| 519 | + | ||
| 520 | + if (state.connectState !== CONNECT_STATE_DISCONNECTED) | ||
| 521 | + throw new ERR_SOCKET_DGRAM_IS_CONNECTED(); | ||
| 522 | + | ||
| 523 | + // Validate arguments before mutating state so a bad argument leaves the | ||
| 524 | + // socket reusable. | ||
| 525 | + if (address == null || address === '') { | ||
| 526 | + address = this.type === 'udp4' ? '127.0.0.1' : '::1'; | ||
| 527 | + } else { | ||
| 528 | + validateString(address, 'address'); | ||
| 529 | + if (isIP(address) === 0) { | ||
| 530 | + throw new ERR_INVALID_ARG_VALUE( | ||
| 531 | + 'address', address, | ||
| 532 | + 'must be a numeric IP address; connectSync does not perform DNS resolution'); | ||
| 533 | + } | ||
| 534 | + } | ||
| 535 | + | ||
| 536 | + if (state.bindState === BIND_STATE_UNBOUND) | ||
| 537 | + this.bindSync(); | ||
| 538 | + else if (state.bindState !== BIND_STATE_BOUND) | ||
| 539 | + throw new ERR_SOCKET_ALREADY_BOUND(); | ||
| 540 | + | ||
| 541 | + state.connectState = CONNECT_STATE_CONNECTING; | ||
| 542 | + | ||
| 543 | + if (state.sendBlockList?.check(address, `ipv${isIP(address)}`)) { | ||
| 544 | + state.connectState = CONNECT_STATE_DISCONNECTED; | ||
| 545 | + throw new ERR_IP_BLOCKED(address); | ||
| 546 | + } | ||
| 547 | + | ||
| 548 | + const err = state.handle.connect(address, port); | ||
| 549 | + if (err) { | ||
| 550 | + state.connectState = CONNECT_STATE_DISCONNECTED; | ||
| 551 | + throw new ExceptionWithHostPort(err, 'connect', address, port); | ||
| 552 | + } | ||
| 553 | + | ||
| 554 | + state.connectState = CONNECT_STATE_CONNECTED; | ||
| 555 | + process.nextTick(emitConnectNT, this); | ||
| 556 | + }; | ||
| 557 | + | ||
| 504 | 558 | ||
| 505 | 559 | function _connect(port, address, callback) { | |
| 506 | 560 | const state = this[kStateSymbol]; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,166 @@ | |||
| 1 | + 'use strict'; | ||
| 2 | + const common = require('../common'); | ||
| 3 | + const assert = require('assert'); | ||
| 4 | + const dgram = require('dgram'); | ||
| 5 | + const net = require('net'); | ||
| 6 | + | ||
| 7 | + // connectSync() connects synchronously, binding the socket first when needed, | ||
| 8 | + // and remoteAddress() is valid immediately. | ||
| 9 | + { | ||
| 10 | + const sock = dgram.createSocket('udp4'); | ||
| 11 | + sock.connectSync(12345, '127.0.0.1'); | ||
| 12 | + | ||
| 13 | + const peer = sock.remoteAddress(); | ||
| 14 | + assert.strictEqual(peer.address, '127.0.0.1'); | ||
| 15 | + assert.strictEqual(peer.family, 'IPv4'); | ||
| 16 | + assert.strictEqual(peer.port, 12345); | ||
| 17 | + | ||
| 18 | + // The socket was bound synchronously as part of connecting. | ||
| 19 | + assert.ok(sock.address().port > 0); | ||
| 20 | + | ||
| 21 | + // The 'connect' event still fires on the next tick. | ||
| 22 | + sock.on('connect', common.mustCall(() => sock.close())); | ||
| 23 | + } | ||
| 24 | + | ||
| 25 | + // Closing synchronously after connectSync() suppresses the deferred 'connect'. | ||
| 26 | + { | ||
| 27 | + const sock = dgram.createSocket('udp4'); | ||
| 28 | + sock.connectSync(12345, '127.0.0.1'); | ||
| 29 | + sock.on('connect', common.mustNotCall()); | ||
| 30 | + sock.close(); | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + // Defaults the address to the udp4 loopback when omitted. | ||
| 34 | + { | ||
| 35 | + const sock = dgram.createSocket('udp4'); | ||
| 36 | + sock.connectSync(12345); | ||
| 37 | + assert.strictEqual(sock.remoteAddress().address, '127.0.0.1'); | ||
| 38 | + sock.close(); | ||
| 39 | + } | ||
| 40 | + | ||
| 41 | + // Works on a socket already bound with bindSync(). | ||
| 42 | + { | ||
| 43 | + const sock = dgram.createSocket('udp4'); | ||
| 44 | + sock.bindSync({ address: '127.0.0.1', port: 0 }); | ||
| 45 | + const boundPort = sock.address().port; | ||
| 46 | + sock.connectSync(12345, '127.0.0.1'); | ||
| 47 | + assert.strictEqual(sock.address().port, boundPort); | ||
| 48 | + assert.strictEqual(sock.remoteAddress().port, 12345); | ||
| 49 | + sock.close(); | ||
| 50 | + } | ||
| 51 | + | ||
| 52 | + // Datagrams flow to the connected peer after a synchronous connect. | ||
| 53 | + { | ||
| 54 | + const receiver = dgram.createSocket('udp4'); | ||
| 55 | + const addr = receiver.bindSync({ address: '127.0.0.1', port: 0 }); | ||
| 56 | + | ||
| 57 | + receiver.on('message', common.mustCall((msg) => { | ||
| 58 | + assert.strictEqual(msg.toString(), 'hello'); | ||
| 59 | + receiver.close(); | ||
| 60 | + })); | ||
| 61 | + | ||
| 62 | + const sender = dgram.createSocket('udp4'); | ||
| 63 | + sender.connectSync(addr.port, '127.0.0.1'); | ||
| 64 | + sender.send('hello', common.mustCall(() => sender.close())); | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + // disconnect() works after a synchronous connect, allowing a reconnect. | ||
| 68 | + { | ||
| 69 | + const sock = dgram.createSocket('udp4'); | ||
| 70 | + sock.connectSync(12345, '127.0.0.1'); | ||
| 71 | + sock.disconnect(); | ||
| 72 | + sock.connectSync(12346, '127.0.0.1'); | ||
| 73 | + assert.strictEqual(sock.remoteAddress().port, 12346); | ||
| 74 | + sock.close(); | ||
| 75 | + } | ||
| 76 | + | ||
| 77 | + // Throws synchronously on a non-numeric address (no DNS resolution). | ||
| 78 | + { | ||
| 79 | + const sock = dgram.createSocket('udp4'); | ||
| 80 | + assert.throws(() => { | ||
| 81 | + sock.connectSync(12345, 'localhost'); | ||
| 82 | + }, { | ||
| 83 | + code: 'ERR_INVALID_ARG_VALUE', | ||
| 84 | + name: 'TypeError', | ||
| 85 | + }); | ||
| 86 | + sock.close(); | ||
| 87 | + } | ||
| 88 | + | ||
| 89 | + // Rejects a non-string address. | ||
| 90 | + { | ||
| 91 | + const sock = dgram.createSocket('udp4'); | ||
| 92 | + assert.throws(() => sock.connectSync(12345, 12345), { | ||
| 93 | + code: 'ERR_INVALID_ARG_TYPE', | ||
| 94 | + }); | ||
| 95 | + sock.close(); | ||
| 96 | + } | ||
| 97 | + | ||
| 98 | + // A rejected argument leaves the socket unbound and reusable. | ||
| 99 | + { | ||
| 100 | + const sock = dgram.createSocket('udp4'); | ||
| 101 | + assert.throws(() => sock.connectSync(-1, '127.0.0.1'), { | ||
| 102 | + code: 'ERR_SOCKET_BAD_PORT', | ||
| 103 | + }); | ||
| 104 | + sock.connectSync(12345, '127.0.0.1'); | ||
| 105 | + assert.strictEqual(sock.remoteAddress().port, 12345); | ||
| 106 | + sock.close(); | ||
| 107 | + } | ||
| 108 | + | ||
| 109 | + // Throws when already connected. | ||
| 110 | + { | ||
| 111 | + const sock = dgram.createSocket('udp4'); | ||
| 112 | + sock.connectSync(12345, '127.0.0.1'); | ||
| 113 | + assert.throws(() => sock.connectSync(12346, '127.0.0.1'), { | ||
| 114 | + code: 'ERR_SOCKET_DGRAM_IS_CONNECTED', | ||
| 115 | + }); | ||
| 116 | + sock.close(); | ||
| 117 | + } | ||
| 118 | + | ||
| 119 | + // Throws synchronously for a blocked address, leaving the socket reusable. | ||
| 120 | + { | ||
| 121 | + const blockList = new net.BlockList(); | ||
| 122 | + blockList.addAddress('127.0.0.1'); | ||
| 123 | + const sock = dgram.createSocket({ type: 'udp4', sendBlockList: blockList }); | ||
| 124 | + assert.throws(() => sock.connectSync(12345, '127.0.0.1'), { | ||
| 125 | + code: 'ERR_IP_BLOCKED', | ||
| 126 | + }); | ||
| 127 | + sock.connectSync(12345, '127.0.0.2'); | ||
| 128 | + assert.strictEqual(sock.remoteAddress().address, '127.0.0.2'); | ||
| 129 | + sock.close(); | ||
| 130 | + } | ||
| 131 | + | ||
| 132 | + // Throws when an asynchronous bind() is still in progress. | ||
| 133 | + { | ||
| 134 | + const sock = dgram.createSocket('udp4'); | ||
| 135 | + sock.bind(0, '127.0.0.1'); | ||
| 136 | + assert.throws(() => sock.connectSync(12345, '127.0.0.1'), { | ||
| 137 | + code: 'ERR_SOCKET_ALREADY_BOUND', | ||
| 138 | + }); | ||
| 139 | + sock.on('listening', common.mustCall(() => sock.close())); | ||
| 140 | + } | ||
| 141 | + | ||
| 142 | + // udp6 loopback default. | ||
| 143 | + if (common.hasIPv6) { | ||
| 144 | + const sock = dgram.createSocket('udp6'); | ||
| 145 | + sock.connectSync(12345); | ||
| 146 | + const peer = sock.remoteAddress(); | ||
| 147 | + assert.strictEqual(peer.address, '::1'); | ||
| 148 | + assert.strictEqual(peer.family, 'IPv6'); | ||
| 149 | + assert.strictEqual(peer.port, 12345); | ||
| 150 | + sock.close(); | ||
| 151 | + } | ||
| 152 | + | ||
| 153 | + // udp6 datagrams flow to the connected peer after a synchronous connect. | ||
| 154 | + if (common.hasIPv6) { | ||
| 155 | + const receiver = dgram.createSocket('udp6'); | ||
| 156 | + const addr = receiver.bindSync({ address: '::1', port: 0 }); | ||
| 157 | + | ||
| 158 | + receiver.on('message', common.mustCall((msg) => { | ||
| 159 | + assert.strictEqual(msg.toString(), 'hello'); | ||
| 160 | + receiver.close(); | ||
| 161 | + })); | ||
| 162 | + | ||
| 163 | + const sender = dgram.createSocket('udp6'); | ||
| 164 | + sender.connectSync(addr.port, '::1'); | ||
| 165 | + sender.send('hello', common.mustCall(() => sender.close())); | ||
| 166 | + } | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments