How to communicate locally between a Node.js (pref. Express module) server and a C++ application using IPC (Unix Domain Sockets)

Viewed 721

I have one machine running simultaniously some C++ application and a Node.js server.

Use-case: I want to be able to trigger my C++ application and make it pass some data (lets say a string) into a socket file. Then my Node.js server shall fetch that data from the socket and print it on some web page via a TCP-port (Code not included here/yet). The same should happen the other way around.

What I've done so far: I was able to write some strings from my Node.js server into to the socket file with the following code: server.js

var net = require('net');
var fs = require('fs');
var socketPath = '/tmp/sock';

fs.stat(socketPath, function(err) {
    if (!err) fs.unlinkSync(socketPath);
    var unixServer = net.createServer(function(localSerialConnection) {
        localSerialConnection.on('data', function(data) {
            // data is a buffer from the socket
                    console.log('Something happened!');
        });
        // write to socket with localSerialConnection.write()
                localSerialConnection.write('HELLO\n');
                localSerialConnection.write('I\'m\n');
                localSerialConnection.write('DOING something!\n');
                localSerialConnection.write('with the SOCKS\n');
    });
unixServer.listen(socketPath);
});

reading the content with nc -U /tmp/sock and with the following output https://i.stack.imgur.com/ye2Dx.png.

When I run my C++ code:

cpp_socket.cpp

#include <boost/asio.hpp>
#include <iostream>

int main() {
    using boost::asio::local::stream_protocol;
    boost::system::error_code ec;

    ::unlink("/tmp/sock"); // Remove previous binding.
    boost::asio::io_service service;
    stream_protocol::endpoint ep("/tmp/sock");
    stream_protocol::socket s(service);

    std::cout << "passed setup section" << std::endl;
    
    s.connect(ep);

    std::cout << "passed connection" << std::endl;

    std::string message = "Hello from C++!";
    
    std::cout << "before sending" << std::endl;
    boost::asio::write(s, boost::asio::buffer(message), boost::asio::transfer_all());
    /* s.write_some(boost::asio::buffer("hello world!"), ec); */
    std::cout << "after sending" << std::endl;

I get the following output:

/cpp_socket 
passed setup section
terminate called after throwing an instance of 'boost::wrapexcept<boost::system::system_error>'
  what():  connect: No such file or directory
Aborted (core dumped)

Even though the /tmp/sock file still exists.

When I remove ::unlink("/tmp/sock"); // Remove previous binding. with comments it runs through, but my Node.js server stops running and nc -U /tmp/sock looses its connection. Neither the .write() nor the .write_some() function seems to work.

I assume that I miss something trivial or I'm not following basic concepts of unix socket communication.

Questions:

  1. Is it even possible to listen with one Node.js server application to a TCP-port and a UNIX-socket at the same time?
  2. Am I understanding the concept of unix socket communication correctly, judging from my input?
  3. How can I read or write from C++ from/into a socket, preferably with C++ boost/asio library. But not necessarily necessary :-)
  4. Am I asking the right questions?

As you might see, I'm not too experienced with these subjects. If I haven't addressed my issues accordingly and not precisely enough,it's due to my lack of experience.

Thanks a lot in advance. Lets have a fruitful discussion.

2 Answers

Oh oops. The error was in plain sight:

::unlink("/tmp/sock"); // Remove previous binding.

Removes the socket. That's not good if you wanted to connect to it.

Removing that line made it work:

passed setup section
passed connection: Success
before sending
after sending

And on the listener side:

enter image description here

Which is, I guess, to be expected because the client isn't complete yet.

Disclaimer: I made it work with TCP sockets, but I would like to see how its possible with unix sockets. One more open port could lead to potential security threats (correct me if I'm wrong). So if you (sehe) or someone knows how to achieve this, please feel free to share. Since I wasn't able to find this in my searches over the internet, it could be helpful for others, too.

What I did now:

  • Creating a NodeJS server which is listening to two ports. One port for the web-browser and one for the C++ application
  • Connect the C++ application with one port
  • Sending strings using telnet

server.js

const net = require('net');
const express = require('express');
const app = express();
const c_port = 6666;
const si_port = 8888;

//------------- From here Browser stream is handled -------------//

app.get('/', (req, res)=>{
  res.send('Hello from Node!');
});

app.get('/index.html', (req, res) => {
  res.sendFile(__dirname + "/" + "index.html");
});

app.listen(si_port,(req, res)=>{
  console.log(`Listening on http://localhost:${si_port}`);
});


//------------- From here C++ stream is handled -------------//

var server = net.createServer(function(c) { //'connection' listener
  console.log('client connected');
    c.on('end', function() {
    console.log('client disconnected');
  });
  c.write('hello\r\n');

  c.on('data', function(data){
  var read = data.toString();
  console.log(read);
    // var message = c.read();
    // console.log(message);
  })
  // c.pipe(c);
  c.write('Hello back to C++'); // But only if you shut down the server
});

server.listen(c_port, function() { //'listening' listener
  console.log(`Listening for input from C++ application on port:${c_port}`);
});

client.cpp

#include <iostream>
#include <boost/asio.hpp>

int main(int argc, char* argv[]) 
{

    if(argc != 4){
        std::cout<<"Wrong parameter\n"<<"Example usage ./client 127.0.0.1 1234 hello"<<std::endl;   
        return -1;
    }

    auto const address = boost::asio::ip::make_address(argv[1]);
    auto const port = std::atoi(argv[2]);
    std::string msg = argv[3];
    
    msg = msg + '\n';

    boost::asio::io_service io_service;
    
    //socket creation
    boost::asio::ip::tcp::socket socket(io_service);
    
    //connection
    boost::system::error_code ec;
    socket.connect( boost::asio::ip::tcp::endpoint( address, port ),ec);
    if(ec){std::cout<<ec.message()<<std::endl; return 1;}
    
    // request/message from client
    //const string msg = "Hello from Client!\n";
    boost::system::error_code error;
    boost::asio::write( socket, boost::asio::buffer(msg), error );

    if(error){
        std::cout << "send failed: " << error.message() << std::endl;
    }
    
    // getting response from server
    boost::asio::streambuf receive_buffer;
    boost::asio::read(socket, receive_buffer, boost::asio::transfer_all(), error);
    
    if( error && error != boost::asio::error::eof ){
        std::cout << "receive failed: " << error.message() << std::endl;
    }
    else{
        const char* data = boost::asio::buffer_cast<const char*>(receive_buffer.data());
        std::cout << data << std::endl;
  }
    return 0;
}

With telnet localhost 6666 I can easily on that port and send random strings. Executing my binary with additional arguments and a string I was able to send some data from my C++: ./clientcpp 127.0.0.1 6666 "HELLO from C++". And here is the output: enter image description here

Thanks a lot again.

Related