Reputation: 55
I'm attempting to write a small relay script in node.js that listens for incoming TCP connections on a local socket, and when it gets one, forwards the traffic to a 3rd party. It must also take any returned data from that 3rd party and send it back to the original local socket. I've tried code like http://delog.wordpress.com/2011/07/19/a-tcp-relay-mechanism-with-node-js/ and it does work, but it requires the sender be a server that is listening on a socket itself, and my utility is intended to work with any program that tries to create an outbound TCP connection. Unfortunately, the problem I'm running into is that everything works great the first time with the client sending the data to the "router" program, and the router forwarding it to another server, and then returning the data from the client. However, when the client program ends or is terminated and attempts to reconnect, I get this:
events.js:72
throw er; // Unhandled 'error' event
^
Error: This socket has been ended by the other party
at Socket.writeAfterFIN [as write] (net.js:275:12)
at Socket.<anonymous> (/root/tcp_loop.js:37:17)
at Socket.emit (events.js:117:20)
at Socket.<anonymous> (_stream_readable.js:748:14)
at Socket.emit (events.js:92:17)
at emitReadable_ (_stream_readable.js:410:10)
at emitReadable (_stream_readable.js:406:5)
at readableAddChunk (_stream_readable.js:168:9)
at Socket.Readable.push (_stream_readable.js:130:10)
at TCP.onread (net.js:528:21)
I ripped out all of the logic and distilled the test case into a small bit of code: one server that acts as both the router (listening on port 8124) as well as the "remote" server (on port 9999), though my testing indicates it makes no difference weather the remote server is on the same machine, on the Internet, etc. Here is the server code:
var net = require('net'),
util = require('util')
;
// The loop_server simulates a remote service.
// The error occurs whether using it here, or actually forwarding
// the data to a remote host.
var loop_server = net.createServer(function(loop) {
console.log("Loop server connected");
loop.on("end", function() {
console.log("Loop server disconnected");
});
loop.on("data", function(data) {
console.log("Loop got data: " + data);
loop.write(data);
});
}).listen(9999, function() {
console.log("Loop server bound");
});
var remote_socket = net.connect(9999, function() {
console.log("Remote connected");
var local_server = net.createServer(function(local_socket) { //'connection' listener
console.log('Local server connected');
local_socket.on('end', function() {
console.log('Local server disconnected');
// local_socket.destroy();
});
local_socket.on('data', function(ldata) {
console.log("Local socket got data: " + ldata);
remote_socket.write(ldata);
});
remote_socket.on('data', function(rdata) {
console.log("Remote socket got data: " + rdata);
local_socket.write(rdata);
});
local_socket.write('hello\r\n');
}).listen(8124, function() { //'listening' listener
console.log('Local server bound');
});
}); // remote_socket
The thing that's failing is the local_socket.write(rdata);
in the remote_socket.on('data', ...
handler. It works the first time the router is started and the client connects, but never again.
For reference, here is the code for the little client app that I've been using. I get the same result with a perl script, telnet, etc.:
var net = require('net');
var client = new net.Socket();
client.connect(8124, function() {
console.log('CONNECTED TO: localhost:8124');
client.write('Single text message from the client app');
});
client.on('data', function(data) {
console.log('DATA: ' + data);
});
client.on('close', function() {
sconsole.log('Connection closed');
});
Any insight would be greatly appreciated. I feel like I must be missing something extremely simple here...
Update:
Nitzin's solution below is a better way to do this, but in my particular example below, the solution is to remove old remote_socket.on('data')
listeners before creating new ones, e.g.:
var remote_socket = net.connect(9999, function() {
console.log("Remote connected");
var local_server = net.createServer(function(local_socket) { //'connection' listener
console.log('Local server connected');
remote_socket.removeAllListeners('data');
...
remote_socket.on('data', function(rdata) {
console.log("Remote socket got data: " + rdata);
local_socket.write(rdata);
});
Upvotes: 2
Views: 6073
Reputation: 13598
You should not destroy
the socket. It closes both ends of the socket. You should only .end()
it, which closes your writing end.
EDIT
Destroying the socket is bad, as I originally wrote, but your real problem is something completely different: you got your proxy (what you call "local") and echo (what you call "remote") servers backwards: the proxy server should make a new connection to the echo server for each new connection the proxy server gets, not the other way around as you have it now.
The only end()
needed is in the client, to let the server know you're done writing.
Here is client.js
:
var net = require('net');
var client = new net.Socket();
client.connect(8124, function() {
console.log('CLIENT: CONNECTED: localhost:8124');
client.write('single text message from the client app');
client.end();
});
client.on('data', function(data) {
console.log('CLIENT: GOT DATA: ' + data);
});
client.on('close', function() {
console.log('CLIENT: CONNECTION CLOSED');
});
And here is servers.js
:
var net = require('net'),
util = require('util');
net.createServer(function(conn) {
console.log('ECHO_SERVER: CONN: new connection');
conn.on('end', function() {
console.log('ECHO_SERVER: CONN: disconnected');
});
conn.on('data', function(data) {
console.log('ECHO_SERVER: CONN: GOT DATA: ' + data);
conn.write(data);
});
}).listen(9999, function() {
console.log('ECHO_SERVER STARTED');
});
net.createServer(function(conn) {
console.log('PROXY_SERVER: CONN: new connection');
var remote = net.connect(9999, function() {
console.log('PROXY_SERVER: CONNECTED TO ECHO_SERVER');
conn.on('end', function() {
console.log('PROXY_SERVER: CONN: disconnected');
remote.end();
});
conn.on('data', function(data) {
console.log('PROXY_SERVER: CONN: GOT DATA FOR ECHO_SERVER: ' + data);
remote.write(data);
});
remote.on('data', function(data) {
console.log('PROXY_SERVER: CONN: GOT DATA FROM ECHO_SERVER: ' + data);
conn.write(data);
});
});
}).listen(8124, function() {
console.log('PROXY_SERVER STARTED');
});
As you can see, for each conn
to the proxy server, there is a new remote
going to the echo server.
Upvotes: 3