1
votes

I'm trying to manage a bunch of socket connections. My app is basically an http server that receives posts and passes these along to a socket. When clients open a socket connection, they send a connect message with an id:

{"m":"connect","id":"1"}

The app then saves this id and socket in the id2socket and socket2id maps. On disconnect, the socket/id pair is deleted from the maps.

A post will also contain an id, which indicates the post data should be sent to the socket with that id.

That's great, and this works fine for a single open socket. However, when I have more than one socket open, and then I close a socket, that disconnect wipes everything from the map. I think my understanding of sockets in node is incomplete- is there only a single socket object that is used in the callback? Is there a better way to manage my open socket connections and ids?

start server:

>>node server.js 
TCP server listening on 127.0.0.1:5280
HTTP server listening on 127.0.0.1:9002

telnet in:

>>telnet localhost 5280
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
{"m":"connect","id":"123"}
{"m":"connect","id":"123","success":"true"}

server after connection:

>>Connection from 127.0.0.1:57572
received data: {"m":"connect","id":"123"}

id: 1
m: connect
associating uid 1 with socket [object Object]

do a post:

python post.py {"foo":"bar"}

So this works fine for several open sockets (as long as 1 device is id 123, server has this hardwired for now). However, as soon as you close one connection all the socket connections are removed from the map.

Here's my code:

python script to do post:

import sys
import json
import httplib, urllib, urllib2

values = json.loads('{"foo":"bar"}')
headers = {"Content-type": "application/json"}

conn = httplib.HTTPConnection('127.0.0.1', 9002)
headers = {"Content-type": "application/json"}
conn.request("POST", "", json.dumps(values), headers)
response = conn.getresponse()

print "response.status: "+response.status
print "response.reason: "+response.reason
print "response.read: "+response.read()
conn.close()

node server (http and tcp), hardwired to send data to device '123' on post:

var net = require('net'); // tcp-server
var http = require("http"); // http-server
var qs = require('querystring'); // http-post

// Map of sockets to devices
var id2socket = new Object;
var socket2id = new Object;

// Setup a tcp server
var server_plug = net.createServer(function(socket) {

    // Event handlers
    socket.addListener("connect", function(conn) {
        console.log("Connection from " + socket.remoteAddress + ":" + socket.remotePort );  
    });

    socket.addListener("data", function(data) {
        console.log("received data: " + data);
        try {
            request = JSON.parse(data);

            response = request;
            if(request.m !== undefined && request['id'] !== undefined){ // hack on 'id', id is js obj property
                console.log("id: "+request['id']);
                console.log("m: "+request.m);
                if(request.m == 'connect'){
                    console.log("associating uid " + request['id'] + " with socket " + socket);
                    id2socket[request['id']] = socket;
                    socket2id[socket] = request['id'];
                    response.success = 'true';
                } else {
                    response.success = 'true';
                }
            }
            socket.write(JSON.stringify(response));
        } catch (SyntaxError) {
            console.log('Invalid JSON:' + data);
            socket.write('{"success":"false","response":"invalid JSON"}');
        }
    });

    socket.on('end', function() {
        id = socket2id[socket]
        console.log("socket disconnect by id " + id);

        // wipe out the stored info
        console.log("removing from map socket:"+socket+" id:"+id);
        delete id2socket[id];
        delete socket2id[socket];
    });

    socket.on('timeout', function() {
        console.log('socket timeout');
    });

});

// Setup http server
var server_http = http.createServer(
    // Function to handle http:post requests, need two parts to it
    // http://jnjnjn.com/113/node-js-for-noobs-grabbing-post-content/
    function onRequest(request, response) {
        request.setEncoding("utf8");

        request.addListener("data", function(chunk) {
            request.content += chunk;
        });

        request.addListener("end", function() {
            console.log("post received!");
            //console.log("Request received: "+request.content);


            if (request.method == 'POST') {
                //var json = qs.parse(request.content);
                //console.log("Post: "+json);

                // HACK TO TEST STUFF:
                // send a message to one of the open sockets
                try {
                    var socket = id2socket['123']; //hardwired 
                    socket.write('{"m":"post"}');
                } catch (Error) {
                    console.log("Cannot find socket with id "+'123');
                }
            }
        });
    }
);


// Fire up the servers
var HOST = '127.0.0.1';
var PORT = 5280;
var PORT2 = 9002;

server_plug.listen(PORT, HOST);
console.log("TCP server listening on "+HOST+":"+PORT);

server_http.listen(PORT2);
console.log("HTTP server listening on "+HOST+":"+PORT2);
1

1 Answers

1
votes

Objects only take strings as keys for their properties. As your log shows, a socket object is converted into the string "[object Object]". As a result, socket #2 overwrites the id from socket #1 in the object, because all sockets are converted into the same string key. So, there is only one property in the object at all times, because all sockets come down to the same key. When you try to remove the id for socket #2, the single property is deleted and the object is empty.

You seem to want a custom property for each separate socket when used as a key. You can use WeakMaps for this. WeakMaps do allow objects as keys (as opposed to string-only keys), but as they're relatively new they may contain bugs at the moment.

(Note that the id2socket map can just be a plain object, because numbers are converted into strings just fine, and each number has its own, distinct string representation*.)

Using WeakMaps is as follows:

var socket2id = new WeakMap; // as if you were doing: var socket2id = {};
socket2id.set(socket, id);   // as if you were doing: socket2id[socket] = id;
socket2id.get(socket);       // as if you were doing: socket2id[socket];
socket2id.delete(socket);    // as if you were doing: delete socket2id[socket];

Make sure to run with node --harmony (>= 0.7) or node --harmony_weakmaps (<= 0.6).


*0 and -0 are exceptions, but you shouldn't be using -0 anyway because 0 === -0, so it's difficult to differ between them.