Home > Enterprise >  SO_REUSEADDR in NodeJs using net package
SO_REUSEADDR in NodeJs using net package

Time:09-17

I have two backends. Backend A and Backend B.
Backend B sends and receives info using a socket server running at port 4243.

Then, with Backend A, I need to catch that info and save it. But I have to also have a socket server on Backend A running at port 4243.

The problem is that, when I run Backend A after running Backend B I receive the error "EADDRINUSE", because I'm using the same host:port on both apps.

If, for Backend A I use Python, the problem dissapear because I have a configuration for sockets that's called SO_REUSEADDR.
Here we have some examples:

  • enter image description here

    But, with the Python code, the socket connects successfully:

    enter image description here

    I don't know what to do :(
    I hope I get some help here.


    Edit 1

    By the way, the lsof command, throws me this output for the JavaScript backend:

    enter image description here

    And this other output for the Python backend:

    enter image description here


    Edit 2

    It really seems to be a problem with JavaScript. I also found this snippet:

    var net = require('net');
    
    function startServer(port, host, callback) {
        var server = net.createServer();
        server.listen(port, host, function() {
            callback(undefined, server);
        });
        server.on('error', function(error) {
            console.error('Ah damn!', error);
            callback(error);
        });
    }
    
    startServer(4000, '0.0.0.0', function(error, wildcardServer) {
        if (error) return;
        startServer(4000, '127.0.0.1', function(error, localhostServer) {
            if (error) return;
            console.log('Started both servers!');
        });
    });
    

    From this post: https://medium.com/@eplawless/node-js-is-a-liar-sometimes-8a28196d56b6

    As the author says:

    Well, that prints “Started both servers!” which is exactly what we don’t want.

    But for me, instead of printing that, I get an error:

    Ah damn! Error: listen EADDRINUSE: address already in use 127.0.0.1:4000
        at Server.setupListenHandle [as _listen2] (node:net:1319:16)
        at listenInCluster (node:net:1367:12)
        at doListen (node:net:1505:7)
        at processTicksAndRejections (node:internal/process/task_queues:84:21) {
      code: 'EADDRINUSE',
      errno: -98,
      syscall: 'listen',
      address: '127.0.0.1',
      port: 4000
    }
    

    I really cannot make it to run and print "Started both servers!".
    Because that's what I want my code to do.


    Edit 3

    This is the Python server socket: https://gitlab.com/d3tn/ud3tn/-/blob/master/tools/aap/aap_receive.py

    This is the important part:

    addr = (args.tcp[0], int(args.tcp[1])) # args.tcp[0] = "localhost", args.tcp[1] = "4243"
    with AAPTCPClient(address=addr) as aap_client:
      aap_client.register(args.agentid) # args.agentid = "bundlesink"
      run_aap_recv(aap_client, args.count, args.verify_pl)
    

    It creates an AAPTCPClient, and the only thing that AAPTCPClient does, is the following:

    def __init__(self, socket, address):
      self.socket = socket
      self.address = address
      self.node_eid = None
      self.agent_id = None
    
    def register(self, agent_id=None):
      """Attempt to register the specified agent identifier.
    
      Args:
        agent_id: The agent identifier to be registered. If None,
            uuid.uuid4() is called to generate one.
      """
      self.agent_id = agent_id or str(uuid.uuid4())
      logger.info(f"Sending REGISTER message for '{agent_id}'...")
      msg_ack = self.send(
        AAPMessage(AAPMessageType.REGISTER, self.agent_id)
      )
      assert msg_ack.msg_type == AAPMessageType.ACK
      logger.info("ACK message received!")
    
    def send(self, aap_msg):
      """Serialize and send the provided `AAPMessage` to the AAP endpoint.
    
      Args:
          aap_msg: The `AAPMessage` to be sent.
      """
      self.socket.send(aap_msg.serialize())
      return self.receive()
    
    def receive(self):
      """Receive and return the next `AAPMessage`."""
      buf = bytearray()
      msg = None
      while msg is None:
        data = self.socket.recv(1)
        if not data:
          logger.info("Disconnected")
          return None
        buf  = data
        try:
          msg = AAPMessage.parse(buf)
        except InsufficientAAPDataError:
          continue
      return msg
    
    

    I don't see any bind, and I don't understand why the python code can call "socket.recv", but in my JavaScript code I can't do "netServer.listen". I think it should be the same.


    CodePudding user response:

    There are things to clarify. 1.) The client uses the bind syscall where the kernel selects the source port automatically.

    It does so by checking sys local_portrange sysctl settings.

    1.) If you want to bind the client to a static source port, be sure to select a TCP port outside the local_portrange range !

    2.) You cannot subscribe to event "*", instead you've to subscribe to the event "data" to receive messages.

    For best practice you should also subscribe to the "error" event in case of errors !

    These links will get you started right away:

    How do SO_REUSEADDR and SO_REUSEPORT differ?

    https://idea.popcount.org/2014-04-03-bind-before-connect/

    So, for all beginners, who want to dig deeper into networking using node.js…

    A working server example:

        // Step 0: Create the netServer and the netClient
    //
    
    var HOST = 'localhost';
    var PORT = 4243;
    var AGENT_ID = 'SO_REUSEADDR DEMO';
    var net = require('net');
    
    console.log(`[DEBUG] Server will listen to: ${HOST}:${PORT}`);
    console.log(`[DEBUG] Server will register with: ${AGENT_ID}`);
    
    const netServer = net.createServer((c) => {
      console.log('[netServer] Client connected');
    
      c.on('data', (msg) => {
        console.log('[netServer] Received `message`, MSG:', msg.toString());
      });
    
      c.on('end', () => {
        console.log('client disconnected');
      });
    
      c.on('error', function (e) {
        console.log('Error: '   e.code);
      });
    
      c.write('hello\r\n');
      c.pipe(c);
    
    }).listen({
      host: HOST,
      port: PORT,
      family: 4, // ipv4, same as socket.AF_INET for python
    });
    
    // Code copied from nodejs documentation page (doesn't make any difference)
    netServer.on('error', function (e) {
       console.log('Error: '   e.code);
      if (e.code == 'EADDRINUSE') {
        console.log('Address in use, retrying...');
        setTimeout(function () {
          netServer.close();
          netServer.listen(HOST, PORT);
        }, 1000);
      }
      if ( e.code = 'ECONNRESET' ){
        console.log('Connection reset by peer...');
          setTimeout(function () {
            netServer.close();
            netServer.listen(HOST, PORT);
          }, 1000);
      }
    
    });
    

    The Client:

    /* Or use this example tcp client written in node.js.  (Originated with
    example code from
    http://www.hacksparrow.com/tcp-socket-programming-in-node-js.html.) */
    
    var net = require('net');
    var HOST = 'localhost';
    var PORT = 4243;
    
    var client = new net.Socket();
    client.setTimeout(3000);
    client.connect(PORT, HOST, function() {
        console.log("Connected to "   client.address().address   " Source Port: "   client.address().port   " Family: "   client.address().family);
        client.write('Hello, server! Love, Client.');
    });
    
    client.on('data', function(data) {
        console.log('Received: '   data);
        client.end();
    });
    
    client.on('error', function(e) {
        console.log('Error: '   e.code);
    });
    
    client.on('timeout', () => {
      console.log('socket timeout');
      client.end();
    });
    
    client.on('close', function() {
        console.log('Connection closed');
    });
    

    Best Hannes

    CodePudding user response:

    Steffen Ullrich was completely right.

    In my JavaScript code, I was trying to create a server to listen to the port 4243.
    But you don't need to have a server in order to listen to some port, you can listen with a client too! (At least that's what I understood)

    You can create a client connection as following:

    const netClient = net.createConnection(PORT, HOST, () => {
      console.log('[netClient] Connected');
    });
    
    netClient.on('data', (data) => {
      console.log('[netClient] Received data:', data.toString('utf8'));
    });
    

    And with "client.on", then you can receive messages as well, as if it were a server.

    I hope this is useful to someone else.

  • Related