2

I'm sending a file and its name through Socket to a ServerSocket. It works "partially" -- the server gets the file and saves it to disk but it does not exit the loop in the copy() method in the ClientSession class.

public class Client{
   DataOutputStream dos =null;
   DataInputStream dis=null; 
   File f =new File("c:/users/supernatural.mp4");
  public static void main(String[]ar) throws Exception{
    try {
          System.out.println("File upload started");
          Socket socc = new Socket("localhost",8117);
          dos = new DataOutputStream(socc.getOutputStream());
          //send file name
          dos.writeUTF(f.getName());
          //send the file
          write(f,dos);
          //Files.copy(f.toPath(),dos);
          //this prints
          System.out.println("Data has been sent...waiting for server to respond ");
          dis = new DataInputStream(socc.getInputStream());
          //this never reads; stuck here
          String RESPONSE = dis.readUTF();
          //this never prints prints
          System.out.println("Server sent: "+RESPONSE);
        } catch(Exception ex) {
            ex.printStackTrace();
        } finally {
          //close the exceptions
       clean();
        }
  }

  private static void write(File f,DataOutputStream d) throws Exception{
                int count;
                DataInputStream din = new DataInputStream(new BufferedInputStream(new FileInputStream(f)));
                byte array[] = new byte[1024*4];
                while((count =din.read(array)) >0){
                    d.write(array,0,count);
                }
                d.flush();
        //this prints
                System.out.println(" done sending...");
                din.close();    
    }
    }

    //Server
    public class MySocket implements Runnable{

        int worker_thread=2;
        volatile boolean shouldRun =false;
        ServerSocket server;
        String port = "8117";
        //ExecutorService services;
        static ExecutorService services;

    public MySocket() {
            this.server = new ServerSocket(Integer.valueOf(port));
            services = Executors.newFixedThreadPool(this.worker_thread);
        }
       //A METHOD TO RUN SERVER THREAD
        @Override
       public void run(){
           while(this.shouldRun){
               Socket client =null;
               try{
               client = server.accept();
               }catch(Exception ex){
                   ex.printStackTrace();
               }
               //hand it over to be processed
               this.services.execute(new ClientSessions(client));
           }
       }   

    public static void main(String[]ar) throws Exception{
        Thread t = new Thread(new MySocket());
            t.start();
    }
    }

    //the ClientSession
    public class ClientSessions implements Runnable{

        Socket s;

        public ClientSessions(Socket s){
        this.s = s;    
        }

        DataInputStream dis=null;
        DataOutputStream dos=null;
        boolean success =true;

        @Override
        public void run(){
            //get the data
            try{
            //get inside channels    
            dis = new DataInputStream(this.s.getInputStream());
            //get outside channels
            dos = new DataOutputStream(this.s.getOutputStream());
         //read the name
        //this works
            String name=dis.readUTF();
            String PATH_TO_SAVE ="c://folder//"+name;
                    //now copy file to disk
                   File f = new File(PATH_TO_SAVE);
                    copy(f,dis);
                    //Files.copy(dis,f.toPath());
        //this doesnt print, stuck in the copy(f,dis) method
                    System.out.println("I am done");
                    success =true;
            }catch(Exception ex){
                ex.printStackTrace();
            }finally{
                //clean resources...
               clean();
            }
        }
       //copy from the stream to the disk 
        private void copy(File f,DataInputStream d)throws Exception{
                    f.getParentFile().mkdirs();
                    f.createNewFile();
                    int count =-1;
                    DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(f)));
                    byte array[] = new byte[1024*8];
                    count =d.read(array);
                    while(count >0){
                        out.write(array,0,count);
                        count =d.read(array);
                        System.out.println("byte out: "+count);
                    }
        //this never prints
                    System.out.println("last read: "+count);
                    out.flush();
                    out.close();
     if(success)dos.writeUTF("Succesful");
                else dos.writeUTF("error");
        }
    } 

//for the clean method i simply have
void clean(){
  if(dis!=null)dis.close();
  if(dos!=null)dos.close();
}

I commented this //Files.copy(dis,f.toPath()); from server because it does not go to next line after writing file to disk, sometimes even stuck there.

Could some pls point me in the right path, I believe i am doing something very wrong here dont know if this is helpful but the client runs in eclipse and server in netbeans

aloy
  • 53
  • 6
  • I only removed that to save the typing time. I'd update the question to include them, thanks. One more thing, do u mean I put the .flush() inside the copy() method in the server or in the write() in the client or both? – aloy Dec 18 '18 at 18:51
  • What do you have at `//clean resources...` ? – rustyx Dec 18 '18 at 19:09
  • @rustyx I try to close the dataInputStream and DataOutpitStreams. The copy(); method in the ClientSession doesn't exit after transferring the data! I've included flushes as suggested by the first reply and nothing worked. – aloy Dec 18 '18 at 19:18

2 Answers2

0

Think about your procotol:

  • The Client sends the file name, then sends the binary file, then waits for the server response.
  • The Server reads the file name, then the binary file until the stream is closed, then sends the success message.

But the stream is never closed since the client is waiting for the response, hence you have a deadlock in your protocol.

This is usually solved by sending the file size first and having the server read exactly that many bytes.

Alternatively you can use the TCP's one-way shutdown feature to send a signal to the server that the output stream of the socket is closed. That can be done with socc.shutdownOutput();

And please use try-with-resources to avoid resource leaks (you must close the Socket, too).

Fixed Client:

    try {
        System.out.println("File upload started");
        try (Socket socc = new Socket("localhost", 8117);
                DataOutputStream dos = new DataOutputStream(socc.getOutputStream());
                DataInputStream dis = new DataInputStream(socc.getInputStream())) {
            // send file name
            dos.writeUTF(f.getName());
            // send the file
            Files.copy(f.toPath(), dos);
            dos.flush();
            System.out.println("Data has been sent...waiting for server to respond ");
            // signal to server that sending is finished
            socc.shutdownOutput();
            String RESPONSE = dis.readUTF();
            // this never prints prints
            System.out.println("Server sent: " + RESPONSE);
        }
    } catch (Exception ex) {
        ex.printStackTrace();
    }

Server:

public class MySocket implements Runnable {

    int worker_thread = 2;
    volatile boolean shouldRun = true;
    ServerSocket server;
    int port = 8117;
    ExecutorService services;

    public MySocket() throws IOException {
        this.server = new ServerSocket(port);
        services = Executors.newFixedThreadPool(this.worker_thread);
    }

    // A METHOD TO RUN SERVER THREAD
    @Override
    public void run() {
        while (this.shouldRun) {
            Socket client = null;
            try {
                client = server.accept();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            // hand it over to be processed
            this.services.execute(new ClientSessions(client));
        }
    }

    public static void main(String[] ar) throws Exception {
        new MySocket().run();
    }
}

class ClientSessions implements Runnable {
    Socket s;
    public ClientSessions(Socket s) {
        this.s = s;
    }
    @Override
    public void run() {
        // get the data
        try (DataInputStream dis = new DataInputStream(this.s.getInputStream());
                DataOutputStream dos = new DataOutputStream(this.s.getOutputStream())) {
            // read the name
            // this works
            String name = dis.readUTF();
            String PATH_TO_SAVE = name;
            // now copy file to disk
            File f = new File("c://folder", PATH_TO_SAVE);
            Files.copy(dis, f.toPath());
            dos.writeUTF("Succesful");
            System.out.println("I am done");
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            try {
                s.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}
rustyx
  • 80,671
  • 25
  • 200
  • 267
  • Okay, I ve done that! So any suggestion or correction to why the server is not sending back a message to client after writing the file sent to it? The server does not print the "last byte" meaning it's somehow stuck in the read and write operation. Also, I should not be closing the socket, cuz if I do it will abort and won't wait to read from server – aloy Dec 18 '18 at 19:37
  • Thank you very much brother. You're awesome. – aloy Dec 18 '18 at 21:58
0

The issue with your code is, that you read from an input stream of a socket, that is never closed.

DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(f)));
byte array[] = new byte[1024*8];
count =d.read(array);
while(count >0){
    out.write(array,0,count);
    count =d.read(array);
    System.out.println("byte out: "+count);
}
//this never prints
System.out.println("last read: "+count);

d.read(array) is actively trying to read from the socket, blocking until it receives something. Since the InputStream is actively blocking, it never returns a value less than or equal to 0. This is because the stream awaits the next package from the other end of the Socket.

Closing the Socket after sending the File should help you. In that case, the end of the Stream is reached and the InputStream returns.

Note: The InputStream you are reading from will (if the socket is closed) return a -1, as you can see within the JavaDoc.

In your case, this however might not be viable!

You want to answer the Client with "okay", or "error". If you close the socket, you cannot answer through the same Socket. The solution to this can be complex.

This situation is a bit tricky. Most frameworks out there have a Thread that reads from the SocketInputStream and passes the return value to some sort of handler (in blocking IO). Your while loop basically is this main reading loop inside the Thread. This loop will only exit, if the connection is lost and therefor the System.out.println("last read: "+count); could be changed to System.out.println("disconnected");

To keep it simple: You could give an estimation on how big the file will be and (just for testing purposes) write something like this:

DataOutputStream out = new DataOutputStream(new 
BufferedOutputStream(new FileOutputStream(f)));
byte array[] = new byte[/* Big enough */ 1024 * 1024 * 8];
d.read(array); // Read the file content
out.write(array); // Write to the file
//this never prints
System.out.println("last read: "+count);

I have left out every error check here! This means that you only read one package from the server, which has to be the File.

Thorben Kuck
  • 1,092
  • 12
  • 25