Hot questions for Using Transmission Control Protocol in ftp

Question:

im Trying to Write a Windows client-server Version control application . i've created two servers , one works with java socket ( java.net library ) to handling the requests ( login, signup and ... ) coming from client . and the other server is running on FTP protocol (using apache common net library) for serving my files . and client is able to communicate with the first server over tcp socket and download or upload files to the second server using FTP . but recently someone just told me that i should use HTTP instead of both . because HTTP in java in really easy to use for both communicating and FILE serving and most importantly is its able to traverse the NAT which now what im using is not able to do .

now im wondering is he right ? should i change my servers to use HTTP instead of TCP socket and FTP ? whats the benefit ?


Answer:

There's some advantages of changing your protocol stack to HTTP:

  • You can easily add security later (only a matter of a single 's')
  • You don't have to do two servers, you can do all-in-one.
  • At some point you could offer a browser-based access of you don't have the client installed / work of a decive where you cannot install it
  • HTTP webapps (even those in java) are proven to scale very, very well. So once you have a lot of users, you're still good to go.
  • There's a lot of helpful frameworks out there that can help you focus on the what instead of the how
  • Most companies that allow outside-access at all will have HTTP/HTTPS open. FTP is more limited in most places.
  • NAT traversal / Proxy traversal

and that's only the ones I came up with while typing :-)

Downsides:

  • You have to start over. But: If you run into trouble, Stack Overflow is there to help you out.

Question:

Im working on a simple ftp server, and the client must send multiples messages to the server, and for each message the server send back to the client a anwser. when the client sends one message it works perfectly and the server responds without any problem, for example, when the client sends "USER username" the server send back to the client "password needed".

But when the client sends another message "PASS password" (using the same socket) it doesnt work ! ONLY the first exchange works (for the username), when the first message is sent, the server anwser without any problem, but it block when it want to send the second message (for the password).

please anyone can help me ? thank you !!

here is my code :

 @Test
public void testProcessPASS() throws IOException{

    Socket socket = new Socket(server.getAddress(), server.getcmdPort());

    this.ClientReceiveMessage(socket); // to flush
    String cmd = "USER user_test\r\n";
    this.ClientSendMessage(socket, cmd);
    String anwser = this.ClientReceiveMessage(socket); 
    assertEquals("Response error.", Constants.MSG_331.replace("\r\n", ""), anwser);

    //PROBLEME STARTS HERE :/

    String cmd2 = "PASS pass_test\r\n";
    this.ClientSendMessage(socket, cmd2);
    String anwser2 = this.ClientReceiveMessage(socket); 
    assertEquals(Constants.MSG_230.replace("\r\n", ""), anwser2);
    socket.close();

}


public void ClientSendMessage(Socket skt, String msg) throws IOException{

    PrintWriter messageClient = new PrintWriter(new OutputStreamWriter(skt.getOutputStream()),true);
    messageClient.println(msg);
    messageClient.flush();

}

public String ClientReceiveMessage(Socket skt) throws IOException{
    BufferedReader br = new BufferedReader(new InputStreamReader(skt.getInputStream()));
    String res = br.readLine() ;
    return res; 
}

this is the server code :

 public class Server implements Runnable {

private ServerSocket cmdserverSocket;
private ServerSocket dataServerSocket;

private boolean running;

public Server() throws IOException {
    this.cmdserverSocket = new ServerSocket(1024);
    this.dataServerSocket = new ServerSocket(1025);
    this.running = false;
}

public boolean isRunning() {
    return this.running;
}

public InetAddress getAddress() {
    return this.cmdserverSocket.getInetAddress();
}

public int getcmdPort() {
    return this.cmdserverSocket.getLocalPort();
}

public int getDataPort() {
    return this.dataServerSocket.getLocalPort();
}

public void run() {
    // TODO Auto-generated method stub
    this.running = true;
    System.out.println("server started on port : " + this.getcmdPort());

    while (this.running) {
        try {
            Socket socket = this.cmdserverSocket.accept();
            new Thread(new FtpRequest(socket, this.dataServerSocket))
                    .start();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            System.out.println("server error : " + e.getMessage());
            this.running = false;
        }
    }
}

}

and this is the class that handles client requests and that sends messages to client and running on a new thread :

 public class FtpRequest implements Runnable {

private Socket cmdSocket;
private Socket dataSocket;
private BufferedReader cmdBufferedReader;
private DataOutputStream cmdDataOutputStream;
private ServerSocket dataServerSocket;
private boolean anonymous;
private boolean connected;
private String username;
private boolean processRunning;
private String directory;

public FtpRequest(Socket cmds, ServerSocket dts) throws IOException {

    this.cmdSocket = cmds;
    this.dataServerSocket = dts;
    this.cmdBufferedReader = new BufferedReader(new InputStreamReader(
            this.cmdSocket.getInputStream()));
    this.cmdDataOutputStream = new DataOutputStream(
            this.cmdSocket.getOutputStream());
    this.anonymous = true;
    this.connected = false;
    this.username = Constants.ANONYMOUS_USER;
    this.processRunning = true;
    this.directory = "/home";

}

/**
 * send a message on the socket of commands
 * 
 * @param msg
 *            the msg to send on the socket of commands
 * @throws IOException
 */
public void sendMessage(String msg) throws IOException {
    System.out.println("FtpRequest sendMessage : " + msg);
    PrintWriter messageClient = new PrintWriter(new OutputStreamWriter(
            this.cmdDataOutputStream), true);
    messageClient.println(msg);
    messageClient.flush();

    /*
     * this.cmdDataOutputStream.writeBytes(msg);
     * this.cmdDataOutputStream.flush(); this.cmdSocket.close();
     */
}

public void run() {
    // TODO Auto-generated method stub
    System.out.println("FtpRequest running ...");
    try {
        this.sendMessage(Constants.MSG_220); // service ready for new user
        this.handleRequest();

    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } // service ready for new user

}

/**
 * this method handle the request readen from cmd socket and run the
 * required method
 * 
 * @throws IOException
 */
private void handleRequest() throws IOException {

    String rqst = this.cmdBufferedReader.readLine();

    Request request = new Request(rqst);
    System.out.println("FtpRequest handleRequest" + rqst);

    switch (request.getType()) {
    case USER:
        this.processUSER(request);
        break;

    case PASS:
        this.processPASS(request);
        break;

    default:
        this.sendMessage(Constants.MSG_502); // Command not implemented.\r\n
        break;

    }

    /*
     * if (this.processRunning = true) this.handleRequest();
     * 
     * else { this.cmdSocket.close(); System.out.println("socket closed ");
     * }
     */

}

private void processUSER(Request rqst) throws IOException {

    System.out.println("FtpRequest processUSER");
    if (rqst.getArgument().equals(Constants.ANONYMOUS_USER)) {
        this.sendMessage(Constants.MSG_230); // user loged in
        this.connected = true;
        this.anonymous = true;
        this.username = Constants.ANONYMOUS_USER;
    } else if (rqst.getArgument().equals(Constants.USER_TEST)) {
        this.sendMessage(Constants.MSG_331); // User name okay, need
                                                // password.\r\n
        this.username = Constants.USER_TEST;
    } else
        this.sendMessage(Constants.MSG_332);
}

private void processPASS(Request rqst) throws IOException {
    System.out.println("FtpRequest processPASS");
    if (rqst.getArgument().equals(Constants.USER_TEST)
            && rqst.getArgument().equals(Constants.PASS_TEST)) {
        this.sendMessage(Constants.MSG_230);
        this.connected = true;
        this.anonymous = false;
    } else
        this.sendMessage(Constants.MSG_332); // au cas seulement le mot de
                                                // passe est fourni
}


}

Answer:

There are some problems with your code.

ClientSendMessage() is using PrintWriter.println(), which outputs a line break. But your input strings already have line breaks on them, so the println() is sending extra line breaks. Also, the line break println() outputs is platform-dependent, whereas FTP uses CRLF specifically. So you should not be using println() at all.

ClientReceiveMessage() does not account for multi-line responses. Per RFC 959, section 4.2 "FTP REPLIES":

A reply is defined to contain the 3-digit code, followed by Space
<SP>, followed by one line of text (where some maximum line length
has been specified), and terminated by the Telnet end-of-line
code.  There will be cases however, where the text is longer than
a single line.  In these cases the complete text must be bracketed
so the User-process knows when it may stop reading the reply (i.e.
stop processing input on the control connection) and go do other
things.  This requires a special format on the first line to
indicate that more than one line is coming, and another on the
last line to designate it as the last.  At least one of these must
contain the appropriate reply code to indicate the state of the
transaction.  To satisfy all factions, it was decided that both
the first and last line codes should be the same.

   Thus the format for multi-line replies is that the first line
   will begin with the exact required reply code, followed
   immediately by a Hyphen, "-" (also known as Minus), followed by
   text.  The last line will begin with the same code, followed
   immediately by Space <SP>, optionally some text, and the Telnet
   end-of-line code.

      For example:
                          123-First line
                          Second line
                            234 A line beginning with numbers
                          123 The last line

   The user-process then simply needs to search for the second
   occurrence of the same reply code, followed by <SP> (Space), at
   the beginning of a line, and ignore all intermediary lines.  If
   an intermediary line begins with a 3-digit number, the Server
   must pad the front to avoid confusion.

The server's initial greeting is likely to be multi-line, but any response to any command can potentially be multi-line, so you need to handle that.

But more importantly, when doing error checking, you need to look at only the 3-digit response code, not the text that accompanies it. Except for a few select commands, like PASV, MLST/MLSD, etc, the text is otherwise arbitrary, the server can send whatever it wants. So you need to ignore the text except for those cases where it is actually needed, or when reporting error messages to the user.

Try something more like this:

private Socket socket;
private BufferedReader br;

 @Test
public void testProcessPASS() throws IOException{    

    socket = new Socket(server.getAddress(), server.getcmdPort());
    br = new BufferedReader(new InputStreamReader(socket.getInputStream()));

    this.ClientReceiveMessage(220);
    this.ClientSendMessage("USER user_test", 331);
    this.ClientSendMessage("PASS pass_test", 230);
    this.ClientSendMessage("QUIT", 221);

    socket.close();

    br = null;
    socket = null;
}

public int ClientSendMessage(String msg, int ExpectedReplyCode) throws IOException{

    Writer bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
    bw.write(msg);
    bw.write("\r\n");
    bw.flush();

    return ClientReceiveMessage(ExpectedReplyCode);
}

public int ClientReceiveMessage(int ExpectedReplyCode) throws IOException{

    String line = br.readLine();
    String msgText = msgText.substring(4);

    if ((line.length() >= 4) && (line[3] == '-')) {
        String endStr = line.substring(0, 2) + " ";
        do {
            line = br.readLine();
            msgText += ("\r\n" + line.substring(4));
        }
        while (line.substring(0, 3) != endStr);
    }

    int actualReplyCode = Integer.parseInt(line.substring(0, 2));
    assertEquals("Response error. " + msgText, ExpectedReplyCode, actualReplyCode);

    // TODO: if the caller wants the msgText for any reason,
    // figure out a way to pass it back here...

    return actualReplyCode;
}

Question:

I am writing an application layer protocol in Java. It basically just transfers files. I would like it generate error messages if the transfer is unable to be completed or if the connection between the client and the server is interrupted.

Is there a way to get the information about the connection or status of the transfer from TCP? Or do I need to rely on what I'm working with at the application layer? If I can access information from TCP, how?


Answer:

TCP is not aware of the "status" of a transfer. That is an application protocol concern, not a transport layer concern.

As for the status of the TCP connection, there is no way to inquire directly. Instead, you find out the status when you attempt to read or write data. The outcome of the read(...) or `write(...) to gives some information about the connection status..

  • If read or write call indicates that it has read or written a non-zero number of bytes, then the connection is probably alive.

  • If a read that returns zero bytes indicates that the other end has closed their write side of the connection. However, we cannot tell if the remote application did that, the remote OS did that, or something in the network initiated the close.

  • If a read or write throws an exception, then we can infer that a problem occurred AND the connection is no longer viable. Exceptions that are possible in this context include:

    • SocketException - Typical messages "connection reset" or "pipe broken" which typically mean that the remote did something unexpected, or "socket closed" which means that the application has attempted to read or write on a connection that it has close.
    • SocketTimeoutException - Typically, this happens if a read has taken longer than the read timeout configured for the socket. Unfortunately, it cannot tell you anything about the underlying cause for the timeout.
    • ProtocolException - This is possible in theory for a socket i/o operation, but this exception is usually thrown by application protocol code.

Unfortunately, this means that providing clear and accurate information to your application's user about why a transfer has failed is difficult. But this is inherent to network connections. The real problem is that simple / clear diagnostic information is not directly available anywhere.