Search code examples
javasocketsdataoutputstream

Stuck in write operation when reading from Socket


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


Solution

  • 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();
                }
            }
        }
    
    }