2

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");
        }
    }

}
1
  • 1
    Your problem is that you are using both a BufferedInputStream and a BufferedReader on the same socket. This will not work. You will lose data in one of them that you intended to read via the other one. See my answer in the duplicate for how to do this properly. NB java-stream is about java.util.stream.Stream, not java.io streams. Commented Jan 15, 2024 at 22:36

2 Answers 2

0

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();
  }
}
Sign up to request clarification or add additional context in comments.

7 Comments

Thank you for your fast response Oliver. I have implemented your code and it is better as the client receives the last buffer but it gets stuck when it reads on the last iteration. The fileFromSocket.read() doesn't return 0, it leaves the client blocked, waiting for input.
@MichałWesołowski In that case, you should probably give more code for us to test: I could just create a full client/server working code using basically all the code written here above. So my guess is that you wrongly copied/pasted the code above or you didn't give us exactly what is happening in your code.
@OlivierGrégoire I have added the whole code now. I would be grateful if you would have a look.
@MichałWesołowski Okay, now I see... You have an architectural issue. You transfer control instructions and data on the same stream. You're typically making an FTP client. And how does FTP work? By having two connections. So try to have two connections: one for data and one for control instructions. Also, for the data connection, just close it every time you dowload/upload a file. If you really can't wait, then yes, send the size on the control stream and enforce downloading that may bytes in total.
Jag is not right in his first assertion. Flushing before close is redundant.
|
0

In client code, you will need to call fos.flush() before closing it.

As a side note, min logic on client side is not really necessary. There are simpler way to achieve those.

1 Comment

No you won't. close() calls flush. See FilterOutputStream.close(), which BufferedOutputStream inherits.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.