Search code examples
cnode.jspipestdoutstdin

How to send to stdin and receive from stdout of C program from Node.js?


A colleague and I need to interface his C program and my Node.js script. I'm attempting to prove the concept of using stdio to pass messages between the two and so far, the concept isn't proven! The node script starts the executable as a child process on initialisation but then fails to send or receive data.

I have written a very simple C program that echos to stdout whatever it receives on stdin. I'm not a C programmer so hopefully I haven't done anything too shocking in 16 lines :p :

stream_echo.c:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define BUFFER_SIZE 255

int main(void) {

    char * buffer = calloc(BUFFER_SIZE, 1);

    while(fgets(buffer, BUFFER_SIZE, stdin) != NULL) {

        //Echo back on stdout:
        fprintf(stdout, "echo: %s", buffer);
    }
}

And testing in the console shows it working as expected: stream_echo.c running in terminal window

The node.js script that calls this executable is as follows:

child_stdio.js

(function() {
    "use strict";

    var jcp = require("child_process").spawn("./stream_echo", [], {
            detached: false,
            stdio: "pipe"
        }),

        counter = 0,

        timer,

        sendData = function() {
            console.log("In sendData. Connected: " + jcp.connected);

            counter += 1;
            jcp.stdin.write("This is data " + counter + "\r\n");

            timer = setTimeout(sendData, 2000);
        };


    //******** STDIN ********
    jcp.stdin.on("close", (data) => {
        console.log("stdin closed.");
        if (timer) {
            clearTimeout(timer);
        }
    });


    //******** STDOUT ********
    jcp.stdout.on("data", (data) => {
        console.log("stdout: " + data);
    });

    jcp.stdout.on("close", (data) => {
        console.log("stdout closed.");
    });


    //******** STDERR ********
    jcp.stderr.on("data", (data) => {
        console.log("stderr: " + data);
    });

    jcp.stderr.on("close", (data) => {
        console.log("stderr closed.");
    });


    //******** PROCESS ********
    jcp.on("close", (code, signal) => {
        console.log("Child process closed.\nCode: " + code + "\nSignal: " + signal);
    });

    jcp.on("exit", (code, signal) => {
        console.log("Child process exited.\nCode: " + code + "\nSignal: " + signal);
    });

    jcp.on("disconnect", () => {
        console.log("Child process disconnected.");
    });

    console.log("jcp.pid: " + jcp.pid);
    console.log("Connected: " + jcp.connected);
    console.log("Calling sendData for the first time....");
    sendData();

}());

When I run this script, multiple attempts to send data to the child process are made and no errors are reported (despite the connected property of the process being false) but no messages are received back either. When I kill the child process, the stream close events and process close events are triggered and the script gracefully terminates.

Execution of child_stdio.js

I don't understand why connected is false but the close events trigger. I feel this is a major clue.

Can anyone tell me what many hours trawling the Internet cannot?


Solution

  • When stdout is not connected to a terminal it is block-buffered. That means the output is not going to be flushed until the buffer fills up or the application terminates.

    Add fflush(stdout) after fprintf in you C application.