mirror of https://github.com/lukechilds/node.git
Browse Source
- Adds new dgram module, for all data-gram type transports - Supports both UDP client and servers - Supports Unix Daemon sockets in DGRAM mode too (think syslog) - Uses a shared Buffer and slices that as needed to be reasonably performant. - One supplied test program so far, test-dgram-pingpong - Passes test cases on osx 10.6 and ubuntu 9.10uv0.7.4-release
Paul Querna
15 years ago
committed by
Ryan Dahl
4 changed files with 425 additions and 0 deletions
@ -0,0 +1,184 @@ |
|||
var sys = require("sys"); |
|||
var fs = require("fs"); |
|||
var events = require("events"); |
|||
var dns = require('dns'); |
|||
|
|||
var Buffer = require('buffer').Buffer; |
|||
var IOWatcher = process.IOWatcher; |
|||
var binding = process.binding('net'); |
|||
var socket = binding.socket; |
|||
var bind = binding.bind; |
|||
var recvfrom = binding.recvfrom; |
|||
var sendto = binding.sendto; |
|||
var close = binding.close; |
|||
var ENOENT = binding.ENOENT; |
|||
|
|||
function isPort (x) { return parseInt(x) >= 0; } |
|||
var pool = null; |
|||
|
|||
function getPool() { |
|||
/* TODO: this effectively limits you to 8kb maximum packet sizes */ |
|||
var minPoolAvail = 1024 * 8; |
|||
|
|||
var poolSize = 1024 * 64; |
|||
|
|||
if (pool === null || (pool.used + minPoolAvail > pool.length)) { |
|||
pool = new Buffer(poolSize); |
|||
pool.used = 0; |
|||
} |
|||
|
|||
return pool; |
|||
} |
|||
|
|||
function Socket (listener) { |
|||
events.EventEmitter.call(this); |
|||
var self = this; |
|||
|
|||
if (listener) { |
|||
self.addListener('message', listener); |
|||
} |
|||
|
|||
self.watcher = new IOWatcher(); |
|||
self.watcher.host = self; |
|||
self.watcher.callback = function () { |
|||
while (self.fd) { |
|||
var p = getPool(); |
|||
var rinfo = recvfrom(self.fd, p, p.used, p.length - p.used, 0); |
|||
|
|||
if (!rinfo) return; |
|||
|
|||
self.emit('message', p.slice(p.used, p.used + rinfo.size), rinfo); |
|||
|
|||
p.used += rinfo.size; |
|||
} |
|||
}; |
|||
} |
|||
|
|||
sys.inherits(Socket, events.EventEmitter); |
|||
exports.Socket = Socket; |
|||
|
|||
exports.createSocket = function (listener) { |
|||
return new Socket(listener); |
|||
}; |
|||
|
|||
Socket.prototype.bind = function () { |
|||
var self = this; |
|||
if (self.fd) throw new Error('Server already opened'); |
|||
|
|||
if (!isPort(arguments[0])) { |
|||
/* TODO: unix path dgram */ |
|||
self.fd = socket('unix_dgram'); |
|||
self.type = 'unix_dgram'; |
|||
var path = arguments[0]; |
|||
self.path = path; |
|||
// unlink sockfile if it exists
|
|||
fs.stat(path, function (err, r) { |
|||
if (err) { |
|||
if (err.errno == ENOENT) { |
|||
bind(self.fd, path); |
|||
process.nextTick(function() { |
|||
self._startWatcher(); |
|||
}); |
|||
} else { |
|||
throw r; |
|||
} |
|||
} else { |
|||
if (!r.isFile()) { |
|||
throw new Error("Non-file exists at " + path); |
|||
} else { |
|||
fs.unlink(path, function (err) { |
|||
if (err) { |
|||
throw err; |
|||
} else { |
|||
bind(self.fd, path); |
|||
process.nextTick(function() { |
|||
self._startWatcher(); |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
} else if (!arguments[1]) { |
|||
// Don't bind(). OS will assign a port with INADDR_ANY.
|
|||
// The port can be found with server.address()
|
|||
self.type = 'udp4'; |
|||
self.fd = socket(self.type); |
|||
bind(self.fd, arguments[0]); |
|||
process.nextTick(function() { |
|||
self._startWatcher(); |
|||
}); |
|||
} else { |
|||
// the first argument is the port, the second an IP
|
|||
var port = arguments[0]; |
|||
dns.lookup(arguments[1], function (err, ip, addressType) { |
|||
if (err) { |
|||
self.emit('error', err); |
|||
} else { |
|||
self.type = addressType == 4 ? 'udp4' : 'udp6'; |
|||
self.fd = socket(self.type); |
|||
bind(self.fd, port, ip); |
|||
process.nextTick(function() { |
|||
self._startWatcher(); |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
}; |
|||
|
|||
Socket.prototype._startWatcher = function () { |
|||
this.watcher.set(this.fd, true, false); |
|||
this.watcher.start(); |
|||
this.emit("listening"); |
|||
}; |
|||
|
|||
Socket.prototype.address = function () { |
|||
return getsockname(this.fd); |
|||
}; |
|||
|
|||
Socket.prototype.send = function(port, addr, buffer, offset, length) { |
|||
var self = this; |
|||
|
|||
if (!isPort(arguments[0])) { |
|||
if (!self.fd) { |
|||
self.type = 'unix_dgram'; |
|||
self.fd = socket(self.type); |
|||
} |
|||
sendto(self.fd, buffer, offset, length, 0, port, addr); |
|||
} |
|||
else { |
|||
dns.lookup(arguments[1], function (err, ip, addressType) { |
|||
if (err) { |
|||
self.emit('error', err); |
|||
} else { |
|||
if (!self.fd) { |
|||
self.type = addressType == 4 ? 'udp4' : 'udp6'; |
|||
self.fd = socket(self.type); |
|||
process.nextTick(function() { |
|||
self._startWatcher(); |
|||
}); |
|||
} |
|||
sendto(self.fd, buffer, offset, length, 0, port, ip); |
|||
} |
|||
}); |
|||
} |
|||
}; |
|||
|
|||
Socket.prototype.close = function () { |
|||
var self = this; |
|||
|
|||
if (!self.fd) throw new Error('Not running'); |
|||
|
|||
self.watcher.stop(); |
|||
|
|||
close(self.fd); |
|||
self.fd = null; |
|||
|
|||
if (self.type === "unix_dgram") { |
|||
fs.unlink(self.path, function () { |
|||
self.emit("close"); |
|||
}); |
|||
} else { |
|||
self.emit("close"); |
|||
} |
|||
}; |
@ -0,0 +1,83 @@ |
|||
require("../common"); |
|||
var Buffer = require('buffer').Buffer; |
|||
var dgram = require("dgram"); |
|||
|
|||
var tests_run = 0; |
|||
|
|||
function pingPongTest (port, host) { |
|||
var N = 500; |
|||
var count = 0; |
|||
var sent_final_ping = false; |
|||
|
|||
var server = dgram.createSocket(function (msg, rinfo) { |
|||
puts("connection: " + rinfo.address + ":"+ rinfo.port); |
|||
|
|||
puts("server got: " + msg); |
|||
|
|||
if (/PING/.exec(msg)) { |
|||
var buf = new Buffer(4); |
|||
buf.write('PONG'); |
|||
server.send(rinfo.port, rinfo.address, buf, 0, buf.length); |
|||
} |
|||
|
|||
}); |
|||
|
|||
server.addListener("error", function (e) { |
|||
throw e; |
|||
}); |
|||
|
|||
server.bind(port, host); |
|||
|
|||
server.addListener("listening", function () { |
|||
puts("server listening on " + port + " " + host); |
|||
|
|||
var buf = new Buffer(4); |
|||
buf.write('PING'); |
|||
|
|||
var client = dgram.createSocket(); |
|||
|
|||
client.addListener("message", function (msg, rinfo) { |
|||
puts("client got: " + msg); |
|||
assert.equal("PONG", msg.toString('ascii')); |
|||
|
|||
count += 1; |
|||
|
|||
if (count < N) { |
|||
client.send(port, host, buf, 0, buf.length); |
|||
} else { |
|||
sent_final_ping = true; |
|||
client.send(port, host, buf, 0, buf.length); |
|||
process.nextTick(function() { |
|||
client.close(); |
|||
}); |
|||
} |
|||
}); |
|||
|
|||
client.addListener("close", function () { |
|||
puts('client.close'); |
|||
assert.equal(N, count); |
|||
tests_run += 1; |
|||
server.close(); |
|||
}); |
|||
|
|||
client.addListener("error", function (e) { |
|||
throw e; |
|||
}); |
|||
|
|||
client.send(port, host, buf, 0, buf.length); |
|||
count += 1; |
|||
}); |
|||
|
|||
} |
|||
|
|||
/* All are run at once, so run on different ports */ |
|||
pingPongTest(20989, "localhost"); |
|||
pingPongTest(20990, "localhost"); |
|||
pingPongTest(20988); |
|||
pingPongTest(20997, "::1"); |
|||
//pingPongTest("/tmp/pingpong.sock");
|
|||
|
|||
process.addListener("exit", function () { |
|||
assert.equal(4, tests_run); |
|||
puts('done'); |
|||
}); |
Loading…
Reference in new issue