【问题标题】:multiple messages through the same socket doesn't work通过同一个套接字的多条消息不起作用
【发布时间】:2017-02-14 03:24:37
【问题描述】:

我在一个简单的 ftp 服务器上工作,客户端必须向服务器发送多条消息,并且对于每条消息,服务器都会向客户端发送一个 anwser。当客户端发送一条消息时,它可以正常工作并且服务器响应没有任何问题,例如,当客户端发送“用户用户名”时,服务器将“需要密码”发送回客户端。

但是当客户端发送另一条消息“PASS 密码”(使用相同的套接字)时它不起作用!只有第一个交换有效(对于用户名),当发送第一条消息时,服务器 anwser 没有任何问题,但是当它要发送第二条消息(对于密码)时它会阻塞。

请任何人都可以帮助我?谢谢你 !!

这是我的代码:

 @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; 
}

这是服务器代码:

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

}

这是处理客户端请求并将消息发送到客户端并在新线程上运行的类:

 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
}


}

【问题讨论】:

  • @Remy Lebeau 谢谢 :) 多行回复我该怎么办?

标签: java sockets tcp ftp ftp-server


【解决方案1】:

您的代码存在一些问题。

ClientSendMessage() 正在使用PrintWriter.println(),它输出一个换行符。但是您的输入字符串已经上有换行符,因此println() 正在发送额外的换行符。此外,换行符println() 输出取决于平台,而 FTP 专门使用CRLF。所以你根本不应该使用println()

ClientReceiveMessage() 不考虑多行响应。根据RFC 959,第 4.2 节“FTP 回复”:

回复定义为包含 3 位代码,后跟空格 ,后跟一行文本(其中一些最大行长 已指定),并由 Telnet end-of-line 终止 代码。但是,在某些情况下,文本长于 单行。在这些情况下,完整的文本必须用括号括起来 所以用户进程知道它什么时候可以停止阅读回复(即 停止处理控制连接上的输入)并去做其他 事物。这需要在第一行使用特殊格式 表示多条线路即将到来,而另一条线路在 最后一行将其指定为最后一行。至少其中一项必须 包含适当的回复代码以指示状态 交易。为了满足所有派系,决定两者 第一行和最后一行代码应该相同。 因此,多行回复的格式是第一行 将从所需的确切回复代码开始,然后是 紧接着是连字符“-”(也称为减号),然后是 文本。最后一行将以相同的代码开头,然后是 立即由空格 、可选的一些文本和 Telnet 行尾代码。 例如: 123-第一行 第二行 234 以数字开头的一行 123 最后一行 然后用户进程只需要搜索第二个 出现相同的回复代码,后跟 (空格),在 一行的开头,忽略所有中间行。如果 中间线路以 3 位数字开头,服务器 必须垫在前面,以免混淆。

服务器的初始问候语可能是多行的,但对任何命令的任何响应都可能是多行的,因此您需要处理它。

但更重要的是,在进行错误检查时,您需要查看 3 位响应代码,而不是随附的文本。除了一些选择命令,如PASVMLST/MLSD 等,文本是其他任意,服务器可以发送任何它想要的。因此,您需要忽略文本,除非在实际需要的情况下,或者向用户报告错误消息时。

试试这样的:

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

【讨论】:

  • 它一次读取一行,但是对于每一行它对应于服务器的响应,我错了吗?
  • @AymaneBo:是的,你错了。有时,单个响应中可能有多行行。 FTP 定义了一种非常具体的格式来检测这种情况,以便您知道何时停止阅读。
  • 您必须在套接字的整个生命周期中使用相同的BufferedReader,并且这样做后您也可以使用相同的PrintWriter
  • @Remy Lebeau 实际上我的 ftp 服务器非常简单,它用于大学课程,所以服务器回复总是只有一行。我尝试了您的解决方案,但仍然是同样的问题:/
  • 即使这是真的,您仍然应该只比较响应代码,而不是全文。你做出那个改变了吗?另外,PrintWriter.println() 是个问题,你删除了吗?您使用的是我回答中的最新代码更新吗?
猜你喜欢
  • 2020-03-07
  • 1970-01-01
  • 1970-01-01
  • 2016-10-13
  • 1970-01-01
  • 1970-01-01
  • 2015-01-10
  • 2015-11-17
  • 2014-01-09
相关资源
最近更新 更多