Search code examples
javafile-transferjava-ioflushbufferedoutputstream

Java BufferedOutputStream.flush() doesn't flush


I am trying to send a file from "server" to "client" using BufferedOutputStream and BufferedInputStream on both ends. The problem is that although I flush() the BufferedOutputStream on server on every write(right side), the data arrives to client socket once every couple flushes(left side). program output

As you can see the file above sent over just fine, but if I use a different file or different buffer size like below ...

program output fail

... it "breaks". The read on client is blocked because there is nothing in the stream and there should be. This whole thing confuses me, there is clearly something that I am missing. Here is my code:

CLIENT

import java.io.*;
import java.net.*;
import java.util.Scanner;
import java.lang.Math.*;

/**
 * Client application which allows the user to connect with server
 * and execute simple file transfers.
 *
 */
public class Client
{
    private BufferedReader textFromSocket;
    private PrintWriter textToSocket;
    private BufferedInputStream fileFromSocket;
    private BufferedOutputStream fileToSocket;

    private Socket connection;

    private static final int port = 8888;
    private static final String host = "localhost";
    private static final String filesFolder = "client/clientFiles/";

    /**
     * Initializes all the streams and the socket
     *
     * @throws IOException
     */
    public Client() throws IOException {

            // Try to open up a connection with cslin152, port number 4242. If cslin152 is unavailable,
            // run the this on the same machine as the client, and use the hostname host.
            connection = new Socket( host, port);

            // Buffer the reading stream for performance.
            textFromSocket = new BufferedReader( new InputStreamReader( connection.getInputStream()));

            // Writing stream
            textToSocket = new PrintWriter(
                            new BufferedWriter(
                            new OutputStreamWriter(connection.getOutputStream())), true);

            // Data input (incoming) stream
            fileFromSocket = new BufferedInputStream( new DataInputStream(connection.getInputStream()),8*1024);

            // Data output (outgoing) stream
            fileToSocket = new BufferedOutputStream( new DataOutputStream(connection.getOutputStream()));
    }

    /**
     * Sends a request to server
     *
     * @param cmd request to send
     * @throws IOException
     */
    public void askServer(String cmd) throws IOException {
        if (cmd != null) {
            // write to this
            textToSocket.println(cmd);
        }

        String[] command = cmd.split(" ");
        switch (command[0]) {
            case "list":
                this.readList();
                break;
            case "get":
                if (command.length < 2){
                    System.out.println("Filename not specified");
                }else{
                    this.getFile(command[1]);
                }
                break;
            case "put":
                if (command.length < 2){
                    System.out.println("Filename not specified");
                }else{
                    this.putFile(command[1]);
                }
                break;
            case "bye":
                this.byeBye();
                break;
            default:
                System.out.println(textFromSocket.readLine());
                break;
        }
    }

    /**
     * Executes client-side commands for "list" command
     * "list" command, lists all files available on the server
     */
    public void readList(){
        try {
            String line;
            while ( !(line = textFromSocket.readLine()).equals("")) {
                System.out.println(line);
            }
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Executes client-side commands for "get" command
     * "get" command, downloads a file from server
     *
     * @param filename the name of the file where data will be saved
     */
    public void getFile(String fileName) throws IOException {
        File downloadFile = new File(filesFolder, fileName);
        try (FileOutputStream fos = new FileOutputStream(downloadFile)) {
            copy(fileFromSocket, fos);
            fos.flush();
        }
    }

    public long copy(InputStream in, OutputStream out) throws IOException {
        byte[] buffer = new byte[8 * 1024];
        long total = 0L;
        while (true) {
            int read = in.read(buffer);
            if (read < 0) {
                break;
            }
            out.write(buffer, 0, read);
        total += read;
        }
    return total;
    }


    /**
     * Executes client-side commands for "put" command
     * "put" command, uploads a file to server
     *
     * @param filename the name of the file to upload
     */
    public void putFile(String filename){

        File sendFile = new File(filesFolder + filename);
        try {
            BufferedInputStream bis = new BufferedInputStream(new FileInputStream(sendFile));
            int fileSize = (int)sendFile.length();
            if (sendFile.exists()) {
                textToSocket.println(fileSize);
                byte[] myBuffer = new byte[fileSize];
                bis.read(myBuffer, 0, fileSize);
                fileToSocket.write(myBuffer);
                fileToSocket.flush();
                bis.close();
            }else {
                System.out.println("Error: File not found");
            }
        }
        catch (FileNotFoundException e) {
            System.out.println("Error: File doesn't exist");
        }
        catch (IOException e){
            System.out.println("Error: Couldn't read the file");
        }

    }

    /**
     * Closes all the streams, connection and terminates the client
     */
    public void byeBye(){
        try {
            connection.close();
            fileFromSocket.close();
            fileToSocket.close();
            textToSocket.close();
            textFromSocket.close();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        System.exit(0);
    }

    /**
     * Connects with the server and continuously scans for user input
     *
     */
    public static void main(String[] args)
    {
        Client client = null;
        Scanner keyboardInput = new Scanner(System.in);

        while (client == null){
            try {
                client = new Client();
                System.out.println("Connected to server at : "+ host +":"+ port);
            }
            catch (IOException e) {
                System.out.println("ERROR: Couldn't connect to server, press ENTER to try again");
                // Wait for ENTER
                keyboardInput.nextLine();
            }
        }
        while (true) {
            System.out.printf("client>");
            String cmd = keyboardInput.nextLine();
            try {
                client.askServer(cmd);
            }
            catch (IOException e) {
                System.out.println("Error: Server didn't reply");
            }

        }
    }
}

CLIENT_HANDLER for each thread (connected client)

import java.io.*;
import java.net.*;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * Client handler application which is run as a separate thread for each connected client
 *
 */

public class ClientHandler extends Thread
{
    private BufferedReader textFromSocket;
    private PrintWriter textToSocket;

    private BufferedInputStream fileFromSocket;
    private BufferedOutputStream fileToSocket;

    private Socket connection;

    private static final String filesFolder = "server/serverFiles/";
    private static final String logFile = "server/log.txt";

    /**
     * Initializes all the streams and the socket
     *
     * @throws IOException
     */

    public ClientHandler(Socket client){
        try {
            connection = client;

            // Buffer the reading stream for performance.
            textFromSocket = new BufferedReader( new InputStreamReader( connection.getInputStream()));

            // Writing stream
            textToSocket = new PrintWriter( new BufferedWriter( new OutputStreamWriter(connection.getOutputStream())), true);

            // Data input (incoming) stream
            fileFromSocket = new BufferedInputStream( new DataInputStream(connection.getInputStream()));

            // Data output (outgoing) stream
            fileToSocket = new BufferedOutputStream( new DataOutputStream(connection.getOutputStream()),8*1024);
        }
        catch( IOException e )
        {
            System.out.println( e );
        }
    }

    /**
     * Reads a request from client
     *
     * @return client request
     */

    public String getLine(){
        try {
            return textFromSocket.readLine();
        }
        catch (IOException e) {
            System.out.println("error: Couldn't read from client (Connection Lost)");
            return null;
        }
    }

    /**
     * Executes server-side commands for "list" command
     * "list" command, lists all files available on the server
     */
    public void list(){
        File serverDir = new File(filesFolder);
        File[] fileList = serverDir.listFiles();

        for (int i=0;i<fileList.length;i++){
            textToSocket.println(fileList[i].getName());
        }
        textToSocket.println("");
        textToSocket.flush();
    }

    /**
     * Executes server-side commands for "get" command
     * "get" command, downloads a file from server
     *
     * @param filename the name of the file to be sent to client
     */
    public void get(String filename) throws IOException {
        File fileToSend = new File(filesFolder, filename);
        try (FileInputStream in = new FileInputStream(fileToSend)) {
            copy(in, fileToSocket);
            fileToSocket.flush();
        }
    }

    public long copy(InputStream in, OutputStream out) throws IOException {
        byte[] buffer = new byte[8 * 1024];
        long total = 0L;
        while (true) {
            int read = in.read(buffer);
            if (read < 0) {
                break;
            }
            out.write(buffer, 0, read);
        total += read;
        }
    return total;
    }

    /**
     * Executes client-side commands for "put" command
     * "put" command, uploads a file to server
     *
     * @param filename the name of the file to be received from client
     */
    public void put(String filename){
        try{
            String text = textFromSocket.readLine();
            if (text.indexOf("ERROR") == -1) {
                int fileSize = Integer.parseInt(text);
                File downloadFile = new File(filesFolder + filename);
                FileOutputStream fos = new FileOutputStream(downloadFile);

                byte[] myBuffer = new byte[16 * 1024];
                int readBytes = 0;
                int currentBytes = 0;
                while (readBytes < fileSize) {
                    currentBytes = fileFromSocket.read(myBuffer, 0, myBuffer.length);
                    readBytes += currentBytes;
                    fos.write(myBuffer, 0, currentBytes);
                }
                fos.close();
                System.out.println("File downloaded");
            }else{
                System.out.println(text);
            }
        }
        catch (IOException e) {
            System.out.println("ERROR: Couldn't download\\save the file");
        }
    }

    /**
     * Closes all the streams and connection
     */
    public void byeBye(){
        try {
            connection.close();
            fileFromSocket.close();
            fileToSocket.close();
            textToSocket.close();
            textFromSocket.close();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Stores a request from client in a log.txt file
     * each request is stored in a form: date:time:client IP address:request
     *
     * @param request the request made by client
     */
    public void logRequest(String request){
        File file = new File (logFile);
        try {
            PrintWriter log = new PrintWriter(new BufferedWriter(new FileWriter(file,true)));
            // Get all the details
            Date today = new Date();
            SimpleDateFormat dateFormatter = new SimpleDateFormat("dd.MM.yyyy:hh:mm:ss");
            String date = dateFormatter.format(today);
            String address = connection.getInetAddress().toString();
            System.out.println("log: " + date+":"+address+":"+request);
            // Save request with details to file
            log.println(date + ":" + address + ":" + request);
        }
        catch (FileNotFoundException e) {
            System.out.println("error: log.txt couldn't be created");
        }
        catch (IOException e){
            System.out.println("File writer");
        }

    }

    /**
     * Sends an error message to a client
     *
     * @param text content of the message
     */
    public void error(String text){
        textToSocket.println("ERROR Server: "+text);
        textToSocket.flush();
    }

    /**
     * Continuously reads requests from clients and executes them
     */
    public void run() {
        String read;
        while (( read = (this.getLine())) != null) {
            // log the request
            this.logRequest(read);
            // split command for potential arguments
            String[] command = read.split(" ");
            if (command.length > 2) {
                this.error("Too many arguments");
            } else {
                switch (command[0]) {
                    case "list":
                        this.list();
                        break;
                    case "get":
                        if (command.length < 2){
                            System.out.printf("Filename not provided");
                        }else {
                            try {
                                this.get(command[1]);
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                        break;
                    case "put":
                        if (command.length < 2){
                            System.out.printf("Filename not provided");
                        }else {
                            this.put(command[1]);
                        }
                        break;
                    case "bye":
                        this.byeBye();
                        break;
                    default:
                        this.error("illegal command");
                        break;
                }
            }
        }
    }
}

SERVER

import java.net.*;
import java.io.*;
import java.util.concurrent.*;

/**
 * Server application which allows multiple clients to connect and execute simple file transfers.
 *
 * Manages a fixed thread pool of maximum 10 threads (connections)
 *
 */
public class Server {

    private static final int port = 8888;
    // As a demonstration, put everything into main(); obviously you would probably want
    // to use instance variables and break this up into separate methods for a real application.
    public static void main(String[] args) throws IOException {

        ServerSocket server = null;
        ExecutorService service = null;
        File file = new File("server/log.txt");

        // Try to open up the listening port
        try {
            server = new ServerSocket(port);
        }
        catch (IOException e) {
            System.err.println("Could not listen on port: "+ port);
            System.exit(-1);
        }
        System.out.println("Server Running");

        // Initialise the executor.
        service = Executors.newFixedThreadPool(10);

        // Clear/Delete log.txt from last execution
        if (file.exists()){
            if(file.delete()){
                System.out.println("Log cleared");
            }else{
                System.out.println("Log couldn't be cleared");
            }
        }else{
            System.out.println("Log empty, nothing to clear");
        }

        // For each new client, submit a new handler to the thread pool.
        while( true )
        {
            Socket client = server.accept();
            service.submit( new ClientHandler(client) );
            System.out.println("Client connected");
        }
    }

}

Solution

  • Jags is right in both his assertions, but they didn't wrote down how to make your code simpler. So here is it. It's probably a method that you'll often use so keep it in a handy place.

    public long copy(InputStream in, OutputStream out) throws IOException {
      byte[] buffer = new byte[8 * 1024];
      long total = 0L;
      while (true) {
        int read = in.read(buffer);
        if (read < 0) {
          break;
        }
        out.write(buffer, 0, read);
        total += read;
      }
      return total;
    }
    

    Then your client and server just become really small:

    Client

    public void getFile(String filename) throws IOException {
      String text = textFromSocket.readLine(); // Don't
      if (text.contains("ERROR")) {            // know
        return;                                // why
      }                                        // it's
      int fileSize = Integer.parseInt(text);   // needed.
      File downloadFile = new File(filesFolder, fileName);
      try (FileOutputStream fos = new FileOutputStream(downloadFile)) {
        copy(fileFromSocket, fos);
        fos.flush();
      }
    }
    

    Server

    public void get(String filename) throws IOException {
      File fileToSend = new File(filesFolder, filename);
      try (FileInputStream in = new FileInputStream(fileToSend)) {
        textToSocket.println(fileToSend.length()); // Not really needed
        copy(in, fileToSocket);
        fileToSocket.flush();
      }
    }