How to stream data from one part of a Java program to another?

Viewed 96

I've learned in Java how to stream data over a network connection using ServerSocket and Socket, such as:

Client.java:

Socket socket = new Socket(address, port);

int i;

while ((i = System.in.read()) != -1)
  socket.getOutputStream().write(i);

Server.java:

  ServerSocket server = new ServerSocket(port);
  Socket socket = server.accept();

  int i;

  while ((i = socket.getInputStream().read()) != -1)
    System.out.println(i);

This would simply have Client blocking on System.in.read() at one end, and Server blocking on socket.getInputStream().read() at the other, and the bytes get passed when ENTER is pressed in the Client program.

How would I accomplish something similar within a single program, without using Sockets? For example, if I had Thread A waiting on keyboard input which is then streamed to Thread B which is able to "consume" the bytes at an arbitrary time in the future, just as Server (above) is able to consume bytes from socket.getInputStream() at some arbitrary time?

Is PipedInput/OutputStream the right solution for this, or ByteArrayInput/OutputStream, or something else? Or am I overthinking it?

2 Answers

This is mostly an extension of the answer @VGR gave in the comments.

If the entirety of your "Network" exists within the same, single JVM, then you don't need anything like sockets at all - you can just use Objects and methods.

The entire point of Sockets was to allow the JVM to perform actions outside of itself (typically with another JVM somewhere in the outside world).

So unless you are trying to interact with objects outside of your current JVM, it is as simple as this.

public class ClientServerExample
{

   public static void main(String[] args)
   {
   
      Server server = new Server();
      Client client = new Client();
      
      client.sendMessage("Hello Server", server);
   
   }
   
   static class Server
   {
   
      String respond(String input)
      {
      
         String output = "";
         
         System.out.println("Server received the following message -- {" + input + "}");
         
         //do something
         
         return output;
      
      }
   
   }
   
   static class Client
   {
   
      void sendMessage(String message, Server server)
      {
      
         System.out.println("Client is about to send the following message to the server -- {" + message + "}");
      
         String response = server.respond(message);
         
         System.out.println("Client received the following response from the server -- {" + response + "}");
      
         //maybe do stuff with the response
      
      }
   
   }

}

Here is the result from running it.

Client is about to send the following message to the server -- {Hello Server}
Server received the following message -- {Hello Server}
Client received the following response from the server -- {}

Note that server doesn't return anything because I didn't do anything in the server. Replace that comment with some code of your own and you will see the results.

EDIT - to better explain a real world example, where a server will respond to requests in FIFO, here is a modified version of the above example.


import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;

public class ClientServerExample
{

   public static void main(String[] args)
   {
   
      System.out.println("===========STARTING SYNCHRONOUS COMMUNICATION============");
      synchronousCommunication();
      System.out.println("===========FINISHED SYNCHRONOUS COMMUNICATION============");
   
      System.out.println("===========STARTING ASYNCHRONOUS COMMUNICATION============");
      asynchronousCommunication();
      System.out.println("===========FINISHED ASYNCHRONOUS COMMUNICATION============");
   
   }
   
   public static void synchronousCommunication()
   {
   
      Server server = new Server();
      Client client = new Client();
   
      String response = "";
   
      response = client.sendMessage("Good morning Server!", server).join();
      
      System.out.println("Client received the following response from the server -- {" + response + "}");
      
      response = client.sendMessage("Good evening Server!", server).join();
   
      System.out.println("Client received the following response from the server -- {" + response + "}");
   
   }
   
   public static void asynchronousCommunication()
   {
   
      Server server = new Server();
      Client client = new Client();
   
      List<CompletableFuture<String>> responses = new ArrayList<>();
   
      responses.add(client.sendMessage("Good morning Server!", server));
      responses.add(client.sendMessage("Good evening Server!", server));
   
      for (CompletableFuture<String> eachResponse : responses)
      {
      
         System.out.println("Client received the following response from the server -- {" + eachResponse.join() + "}");
      
      }
   
   
   }
   
   static class Server
   {
   
      CompletableFuture<String> respond(final String input)
      {
      
         System.out.println("Server received the following message -- {" + input + "}");
         
         return 
            CompletableFuture.supplyAsync(
               () -> 
               {
               
                  try
                  {
                  
                  //sleep for 2 seconds, to represent arbitrary delay in receiver processing
                     Thread.sleep(2000);
                  
                     return input.contains("morning") ? "Good morning to you too!" : "Good evening to you too!";
                  
                  }
                  
                  catch (Exception e)
                  {
                  
                     throw new IllegalStateException("What happened?", e);
                  
                  }
               
               });
      
      }
   
   }
   
   static class Client
   {
   
      CompletableFuture<String> sendMessage(String message, Server server)
      {
      
         System.out.println("Client is about to send the following message to the server -- {" + message + "}");
      
         return server.respond(message);
         
      }
   
   }

}

Both of these examples are performing a FIFO approach to data processing. They receive the request, calculate a response, and then send back a CompletableFuture, which is basically an Object that contains the response that will arrive once the Server gets around to it, sort of like a Promise in Javascript.

For the synchronous example, we see that a client message is sent, and then processed before the next one is sent. As a result, we have a minor delay between the 2 (about 2 seconds).

For the asynchronous example, we see that both client messages are sent, and their CompletableFutures are put into a batch list, which is converted to normal strings once all requests have been sent.

The synchronous example takes around 10 seconds. The asynchronous example takes around 5 seconds.

Both of these are different ways of performing FIFO in the way that you described. They both are examples where multiple clients send a request to the server, and then the server finishes them when they get around to it. That 5 seconds delay is meant to represent the idea of "getting around to it". In reality, getting around to it usually means that the server has so much on it's plate that it will take a long time before it has a chance to give a full response.

Let me know if you need another example to better help you understand.

Yes, you can use PipedInputStream/PipedOutputStream for "streaming" data "locally" in your JVM. You create one PipedInputStream and one PipedOutputStream instance, connect them with the connect() method and start sending/receiving bytes. Check the following example:

PipedInputStream pipedIn = new PipedInputStream();
PipedOutputStream pipedOut = new PipedOutputStream();
pipedIn.connect(pipedOut);

Thread keyboardReadingThread = new Thread() {
    @Override
    public void run() {
        System.out.println("Enter some data:");
        Scanner s = new Scanner(System.in);
        String line = s.nextLine();
        System.out.println("Entered line: "+line);
        byte[] bytes = line.getBytes(StandardCharsets.UTF_8);
        try {
            pipedOut.write(bytes);
            pipedOut.flush();
            pipedOut.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("Keyboard reading thread terminated");
    }
};
keyboardReadingThread.start();

Thread streamReadingThread = new Thread() {
    @Override
    public void run() {
        try {
            int bytesRead = 0;
            byte[] targetBytes = new byte[100];
            System.out.println("Read data from the PipedInputStream instance");
            while ((bytesRead = pipedIn.read(targetBytes)) != -1) {
                System.out.println("read "+bytesRead+" bytes");
                String s = new String(targetBytes, 0, bytesRead, StandardCharsets.UTF_8);
                System.out.println("Received string: "+s);
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        System.out.println("Streaming reading thread terminated");
    }
};
streamReadingThread.start();

keyboardReadingThread.join();
streamReadingThread.join();

First the two piped stream instances are connected. After that two threads will read from the keyboard and read from the PipedInputStream instance. When you run your application you will get an output similar to this (with Some example input for testing being the keyboard input):

Enter some data:
Read data from the PipedInputStream instance
Some example input for testing
Entered line: Some example input for testing
Keyboard reading thread terminated
read 30 bytes
Received string: Some example input for testing
Streaming reading thread terminated

Also notice that the threads are not synchronized in any way, so the System.out.println() statements might get executed in a different order.

Related