Search code examples
javabashnio

Communication between Bash and Java with channels: sending end-of-transmission


I'd like to set up a Java application that works as a server, accepting (blocking) queries from a Bash script. The Java part is written using nio, and has a main loop that looks like this:

ServerSocketChannel ssc = ...; // bound to localhost:8011
Charset charset = ...;

// Waits for connections forever.
while(true) {
  SocketChannel sc = ssc.accept();
  StringBuffer sb = new StringBuffer();
  int read = 0;
  // Builds up a string representing the query.
  while(true) {
    ByteBuffer bb = ByteBuffer.allocate(1024);
    read = sc.read(bb);
    if(read == -1) break;
    bb.flip();
    CharBuffer cb = charset.decode(bb);
    sb.append(cb.toString());
  }
  // Do something with the query.
  sc.write(charset.encode(CharBuffer.wrap(sb.toString())));
  sc.close();
}

The Bash part relies on the /dev/tcp/ magic:

exec 3<> /dev/tcp/localhost/8011
echo "message" 1>&3

I can see that the message sent from Bash does reach the Java part (if I add a System.out.println(cb); in the inner loop, I can see the parts), but the inner loop doesn't terminate unless I kill the Bash script.

My question is really quite simple: how can the Bash script signal to the Java server that its communication has come to an end? I've tried adding

echo -en "\004" 1>&3 

in my Bash script, but that didn't help.


Solution

  • Try closing the file descriptor. This should be seen by Java as a closed stream, and allow the inner loop to terminate.

    exec 3>&-
    

    It is possible for a socket can be "half-open" (that is, shut down in one direction but still open in the other). A Socket instance has methods to detect this state.

    I haven't tested whether the pipe-socket hybrid created by bash supports this or not. If it doesn't, you'll have to design a protocol with some internal length-encoding or delimiting sequences to indicate message boundaries.