dgram: support for setting socket buffer size
* setRecvBufferSize(int) and setSendBufferSize(int) * added docs for send/receive buffer sizes * Added options support to set buffer sizes in dgram.createSocket(). PR-URL: https://github.com/nodejs/node/pull/13623 Reviewed-By: Refael Ackermann <refack@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net>
This commit is contained in:
parent
fca7e49e44
commit
2ac7b433b4
@ -227,6 +227,20 @@ never have reason to call this.
|
||||
If `multicastInterface` is not specified, the operating system will attempt to
|
||||
drop membership on all valid interfaces.
|
||||
|
||||
### socket.getRecvBufferSize(size)
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* Returns {number} the `SO_RCVBUF` socket receive buffer size in bytes.
|
||||
|
||||
### socket.getSendBufferSize(size)
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* Returns {number} the `SO_SNDBUF` socket send buffer size in bytes.
|
||||
|
||||
### socket.ref()
|
||||
<!-- YAML
|
||||
added: v0.9.1
|
||||
@ -398,6 +412,26 @@ decremented to 0 by a router, it will not be forwarded.
|
||||
The argument passed to to `socket.setMulticastTTL()` is a number of hops
|
||||
between 0 and 255. The default on most systems is `1` but can vary.
|
||||
|
||||
### socket.setRecvBufferSize(size)
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `size` {number} Integer
|
||||
|
||||
Sets the `SO_RCVBUF` socket option. Sets the maximum socket receive buffer
|
||||
in bytes.
|
||||
|
||||
### socket.setSendBufferSize(size)
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `size` {number} Integer
|
||||
|
||||
Sets the `SO_SNDBUF` socket option. Sets the maximum socket send buffer
|
||||
in bytes.
|
||||
|
||||
### socket.setTTL(ttl)
|
||||
<!-- YAML
|
||||
added: v0.1.101
|
||||
@ -461,6 +495,9 @@ changes:
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/14560
|
||||
description: The `lookup` option is supported.
|
||||
- version: REPLACEME
|
||||
pr-url: https://github.com/nodejs/node/pull/13623
|
||||
description: `recvBufferSize` and `sendBufferSize` options are supported now.
|
||||
-->
|
||||
|
||||
* `options` {Object} Available options are:
|
||||
@ -469,6 +506,8 @@ changes:
|
||||
* `reuseAddr` {boolean} When `true` [`socket.bind()`][] will reuse the
|
||||
address, even if another process has already bound a socket on it. Optional.
|
||||
Defaults to `false`.
|
||||
* `recvBufferSize` {number} - Optional. Sets the `SO_RCVBUF` socket value.
|
||||
* `sendBufferSize` {number} - Optional. Sets the `SO_SNDBUF` socket value.
|
||||
* `lookup` {Function} Custom lookup function. Defaults to [`dns.lookup()`][].
|
||||
Optional.
|
||||
* `callback` {Function} Attached as a listener for `'message'` events. Optional.
|
||||
|
45
lib/dgram.js
45
lib/dgram.js
@ -98,15 +98,19 @@ function _createSocketHandle(address, port, addressType, fd, flags) {
|
||||
return handle;
|
||||
}
|
||||
|
||||
const kOptionSymbol = Symbol('options symbol');
|
||||
|
||||
function Socket(type, listener) {
|
||||
EventEmitter.call(this);
|
||||
var lookup;
|
||||
|
||||
this[kOptionSymbol] = {};
|
||||
if (type !== null && typeof type === 'object') {
|
||||
var options = type;
|
||||
type = options.type;
|
||||
lookup = options.lookup;
|
||||
this[kOptionSymbol].recvBufferSize = options.recvBufferSize;
|
||||
this[kOptionSymbol].sendBufferSize = options.sendBufferSize;
|
||||
}
|
||||
|
||||
var handle = newHandle(type, lookup);
|
||||
@ -141,6 +145,12 @@ function startListening(socket) {
|
||||
socket._bindState = BIND_STATE_BOUND;
|
||||
socket.fd = -42; // compatibility hack
|
||||
|
||||
if (socket[kOptionSymbol].recvBufferSize)
|
||||
bufferSize(socket, socket[kOptionSymbol].recvBufferSize, 'recv');
|
||||
|
||||
if (socket[kOptionSymbol].sendBufferSize)
|
||||
bufferSize(socket, socket[kOptionSymbol].sendBufferSize, 'send');
|
||||
|
||||
socket.emit('listening');
|
||||
}
|
||||
|
||||
@ -157,6 +167,20 @@ function replaceHandle(self, newHandle) {
|
||||
self._handle = newHandle;
|
||||
}
|
||||
|
||||
function bufferSize(self, size, buffer) {
|
||||
if (size >>> 0 !== size)
|
||||
throw new errors.TypeError('ERR_SOCKET_BAD_BUFFER_SIZE');
|
||||
|
||||
try {
|
||||
if (buffer === 'recv')
|
||||
return self._handle.bufferSize(size, 0);
|
||||
else
|
||||
return self._handle.bufferSize(size, 1);
|
||||
} catch (e) {
|
||||
throw new errors.Error('ERR_SOCKET_BUFFER_SIZE', e);
|
||||
}
|
||||
}
|
||||
|
||||
Socket.prototype.bind = function(port_, address_ /*, callback*/) {
|
||||
let port = port_;
|
||||
|
||||
@ -636,6 +660,27 @@ Socket.prototype.unref = function() {
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
Socket.prototype.setRecvBufferSize = function(size) {
|
||||
bufferSize(this, size, 'recv');
|
||||
};
|
||||
|
||||
|
||||
Socket.prototype.setSendBufferSize = function(size) {
|
||||
bufferSize(this, size, 'send');
|
||||
};
|
||||
|
||||
|
||||
Socket.prototype.getRecvBufferSize = function() {
|
||||
return bufferSize(this, 0, 'recv');
|
||||
};
|
||||
|
||||
|
||||
Socket.prototype.getSendBufferSize = function() {
|
||||
return bufferSize(this, 0, 'send');
|
||||
};
|
||||
|
||||
|
||||
module.exports = {
|
||||
_createSocketHandle,
|
||||
createSocket,
|
||||
|
@ -255,11 +255,14 @@ E('ERR_SERVER_ALREADY_LISTEN',
|
||||
'Listen method has been called more than once without closing.');
|
||||
E('ERR_SOCKET_ALREADY_BOUND', 'Socket is already bound');
|
||||
E('ERR_SOCKET_BAD_PORT', 'Port should be > 0 and < 65536');
|
||||
E('ERR_SOCKET_BAD_BUFFER_SIZE', 'Buffer size must be a positive integer');
|
||||
E('ERR_SOCKET_BAD_TYPE',
|
||||
'Bad socket type specified. Valid types are: udp4, udp6');
|
||||
E('ERR_SOCKET_CANNOT_SEND', 'Unable to send data');
|
||||
E('ERR_SOCKET_CLOSED', 'Socket is closed');
|
||||
E('ERR_SOCKET_DGRAM_NOT_RUNNING', 'Not running');
|
||||
E('ERR_SOCKET_BUFFER_SIZE',
|
||||
(reason) => `Could not get or set buffer size: ${reason}`);
|
||||
E('ERR_STDERR_CLOSE', 'process.stderr cannot be closed');
|
||||
E('ERR_STDOUT_CLOSE', 'process.stdout cannot be closed');
|
||||
E('ERR_STREAM_WRAP', 'Stream has StringDecoder set or is in objectMode');
|
||||
|
@ -46,6 +46,7 @@ using v8::Object;
|
||||
using v8::PropertyAttribute;
|
||||
using v8::PropertyCallbackInfo;
|
||||
using v8::String;
|
||||
using v8::Uint32;
|
||||
using v8::Undefined;
|
||||
using v8::Value;
|
||||
|
||||
@ -134,6 +135,7 @@ void UDPWrap::Initialize(Local<Object> target,
|
||||
env->SetProtoMethod(t, "setMulticastLoopback", SetMulticastLoopback);
|
||||
env->SetProtoMethod(t, "setBroadcast", SetBroadcast);
|
||||
env->SetProtoMethod(t, "setTTL", SetTTL);
|
||||
env->SetProtoMethod(t, "bufferSize", BufferSize);
|
||||
|
||||
env->SetProtoMethod(t, "ref", HandleWrap::Ref);
|
||||
env->SetProtoMethod(t, "unref", HandleWrap::Unref);
|
||||
@ -222,6 +224,43 @@ void UDPWrap::Bind6(const FunctionCallbackInfo<Value>& args) {
|
||||
}
|
||||
|
||||
|
||||
void UDPWrap::BufferSize(const FunctionCallbackInfo<Value>& args) {
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
UDPWrap* wrap;
|
||||
ASSIGN_OR_RETURN_UNWRAP(&wrap,
|
||||
args.Holder(),
|
||||
args.GetReturnValue().Set(UV_EBADF));
|
||||
|
||||
CHECK(args[0]->IsUint32());
|
||||
CHECK(args[1]->IsUint32());
|
||||
int size = static_cast<int>(args[0].As<Uint32>()->Value());
|
||||
|
||||
if (size != args[0].As<Uint32>()->Value()) {
|
||||
if (args[1].As<Uint32>()->Value() == 0)
|
||||
return env->ThrowUVException(EINVAL, "uv_recv_buffer_size");
|
||||
else
|
||||
return env->ThrowUVException(EINVAL, "uv_send_buffer_size");
|
||||
}
|
||||
|
||||
int err;
|
||||
if (args[1].As<Uint32>()->Value() == 0) {
|
||||
err = uv_recv_buffer_size(reinterpret_cast<uv_handle_t*>(&wrap->handle_),
|
||||
&size);
|
||||
} else {
|
||||
err = uv_send_buffer_size(reinterpret_cast<uv_handle_t*>(&wrap->handle_),
|
||||
&size);
|
||||
}
|
||||
|
||||
if (err != 0) {
|
||||
if (args[1].As<Uint32>()->Value() == 0)
|
||||
return env->ThrowUVException(err, "uv_recv_buffer_size");
|
||||
else
|
||||
return env->ThrowUVException(err, "uv_send_buffer_size");
|
||||
}
|
||||
args.GetReturnValue().Set(size);
|
||||
}
|
||||
|
||||
|
||||
#define X(name, fn) \
|
||||
void UDPWrap::name(const FunctionCallbackInfo<Value>& args) { \
|
||||
UDPWrap* wrap = Unwrap<UDPWrap>(args.Holder()); \
|
||||
|
@ -55,6 +55,7 @@ class UDPWrap: public HandleWrap {
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void SetBroadcast(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void SetTTL(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void BufferSize(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
||||
static v8::Local<v8::Object> Instantiate(Environment* env, AsyncWrap* parent);
|
||||
uv_udp_t* UVHandle();
|
||||
|
@ -39,3 +39,23 @@ validTypes.forEach((validType) => {
|
||||
socket.close();
|
||||
});
|
||||
});
|
||||
|
||||
// Ensure buffer sizes can be set
|
||||
{
|
||||
const socket = dgram.createSocket({
|
||||
type: 'udp4',
|
||||
recvBufferSize: 10000,
|
||||
sendBufferSize: 15000
|
||||
});
|
||||
|
||||
socket.bind(common.mustCall(() => {
|
||||
// note: linux will double the buffer size
|
||||
assert.ok(socket.getRecvBufferSize() === 10000 ||
|
||||
socket.getRecvBufferSize() === 20000,
|
||||
'SO_RCVBUF not 1300 or 2600');
|
||||
assert.ok(socket.getSendBufferSize() === 15000 ||
|
||||
socket.getSendBufferSize() === 30000,
|
||||
'SO_SNDBUF not 1800 or 3600');
|
||||
socket.close();
|
||||
}));
|
||||
}
|
||||
|
74
test/parallel/test-dgram-socket-buffer-size.js
Normal file
74
test/parallel/test-dgram-socket-buffer-size.js
Normal file
@ -0,0 +1,74 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const dgram = require('dgram');
|
||||
|
||||
{
|
||||
// Should throw error if the socket is never bound.
|
||||
const errorObj = {
|
||||
code: 'ERR_SOCKET_BUFFER_SIZE',
|
||||
type: Error,
|
||||
message: /^Could not get or set buffer size:.*$/
|
||||
};
|
||||
|
||||
const socket = dgram.createSocket('udp4');
|
||||
|
||||
assert.throws(() => {
|
||||
socket.setRecvBufferSize(8192);
|
||||
}, common.expectsError(errorObj));
|
||||
|
||||
assert.throws(() => {
|
||||
socket.setSendBufferSize(8192);
|
||||
}, common.expectsError(errorObj));
|
||||
|
||||
assert.throws(() => {
|
||||
socket.getRecvBufferSize();
|
||||
}, common.expectsError(errorObj));
|
||||
|
||||
assert.throws(() => {
|
||||
socket.getSendBufferSize();
|
||||
}, common.expectsError(errorObj));
|
||||
}
|
||||
|
||||
{
|
||||
// Should throw error if invalid buffer size is specified
|
||||
const errorObj = {
|
||||
code: 'ERR_SOCKET_BAD_BUFFER_SIZE',
|
||||
type: TypeError,
|
||||
message: /^Buffer size must be a positive integer$/
|
||||
};
|
||||
|
||||
const badBufferSizes = [-1, Infinity, 'Doh!'];
|
||||
|
||||
const socket = dgram.createSocket('udp4');
|
||||
|
||||
socket.bind(common.mustCall(() => {
|
||||
badBufferSizes.forEach((badBufferSize) => {
|
||||
assert.throws(() => {
|
||||
socket.setRecvBufferSize(badBufferSize);
|
||||
}, common.expectsError(errorObj));
|
||||
|
||||
assert.throws(() => {
|
||||
socket.setSendBufferSize(badBufferSize);
|
||||
}, common.expectsError(errorObj));
|
||||
});
|
||||
socket.close();
|
||||
}));
|
||||
}
|
||||
|
||||
{
|
||||
// Can set and get buffer sizes after binding the socket.
|
||||
const socket = dgram.createSocket('udp4');
|
||||
|
||||
socket.bind(common.mustCall(() => {
|
||||
socket.setRecvBufferSize(10000);
|
||||
socket.setSendBufferSize(10000);
|
||||
|
||||
// note: linux will double the buffer size
|
||||
const expectedBufferSize = common.isLinux ? 20000 : 10000;
|
||||
assert.strictEqual(socket.getRecvBufferSize(), expectedBufferSize);
|
||||
assert.strictEqual(socket.getSendBufferSize(), expectedBufferSize);
|
||||
socket.close();
|
||||
}));
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user