免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 821 | 回复: 0
打印 上一主题 下一主题

Java构建Ftp服务器程序段 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2008-01-09 16:49 |只看该作者 |倒序浏览

下面我们要处理用户连接,也就是FtpConnection类。Ftp连接本质上是一个状态机,当FtpConnection接收到用户命令后,根据当前状态决定响应及下一个状态。不过我们不需要考虑实现一个复杂的状态机,只须监听/接收/处理/响应即可:
package jftp;
import java.net.*;
import java.io.*;
import java.util.*;
import java.text.*;
public class FtpConnection extends Thread {
    /** 主目录 */
    static public String root = null;
    private String currentDir = "/"; // 当前目录
    private Socket socket;
    private BufferedReader reader = null;
    private BufferedWriter writer = null;
    private String clientIP = null;
    private Socket tempSocket = null; // tempSocket用于传送文件
    private ServerSocket pasvSocket = null; // 用于被动模式
    private String host = null;
    private int port = (-1);
    public FtpConnection(Socket socket) {
        this.socket = socket;
        this.clientIP = socket.getInetAddress().getHostAddress();
    }
    public void run
() {
        String command;
        try {
            System.out.println(clientIP + " connected.");
            socket.setSoTimeout(60000); // ftp超时设定
            reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            response("220-欢迎消息......");
            response("220-欢迎消息......");
            response("220 注意最后一行欢迎消息没有“-”");
            for(;;) {
                command = reader.readLine();
                if(command == null)
                    break;
                System.out.println("command from " + clientIP + " : " + command);
                parseCommand(command);
                if(command.equals("QUIT")) // 收到QUIT命令
                    break;
            }
        }
        catch(Exception e) { e.printStackTrace(); }
        finally {
            try {
                if(reader!=null) reader.close();
            }catch(Exception e) {}
            try {
                if(writer!=null) writer.close();
            }catch(Exception e) {}
            try {
                if(this.pasvSocket!=null) pasvSocket.close();
            }catch(Exception e) {}
            try {
                if(this.tempSocket!=null) tempSocket.close();
            }catch(Exception e) {}
            try {
                if(this.socket!=null) socket.close();
            }catch(Exception e) {}
        }
        System.out.println(clientIP + " disconnected.");
    }
}

FtpConnection在run()方法中仅仅是获得用户命令/处理命令,当收到QUIT时,关闭连接,结束Ftp会话。
先准备几个辅助方法:
private void response(String s) throws Exception {
    // System.out.println("  [RESPONSE] "+s);
    writer.write(s);
    writer.newLine();
    writer.flush(); // 注意要flush否则响应仍在缓冲区
}
// 生成一个字符串
private static String pad(int length) {
    StringBuffer buf = new StringBuffer();
    for (int i = 0; i USER
")) {
        response("331 need password");
    }
    else if(s.startsWith("PASS ")) {
        response("230 welcome to my ftp!");
    }
    else if(s.equals("QUIT")) {
        response("221 欢迎再来!");
    }
    else if(s.equals("TYPE A")) {
        response("200 TYPE set to A.");
    }
    else if(s.equals("TYPE I")) {
        response("200 TYPE set to I.");
    }
    else if(s.equals("NOOP")) {
        response("200 NOOP OK.");
    }
    else if(s.startsWith("CWD")) { // 设置当前目录,注意没有检查目录是否有效
        this.currentDir = getParam(s, "CWD ");
        response("250 CWD command successful.");
    }
    else if(s.equals("PWD")) { // 打印当前目录
        response("257 \"" + this.currentDir + "\" is current directory.");
    }
    else if(s.startsWith("PORT ")) {
        // 记录端口
        String[] params = getParam(s, "PORT ").split(",");
        if(params.length=7)
            response("500 command param error.");
        else {
            this.host = params[0] + "." + params[1] + "." + params[2] + "." + params[3];
            String port1 = null;
            String port2 = null;
            if(params.length == 6) {
                port1 = params[4];
                port2 = params[5];
            }
            else {
                port1 = "0";
                port2 = params[4];
            }
            this.port = Integer.parseInt(port1) * 256 + Integer.parseInt(port2);
            response("200 command successful.");
        }
    }
    else if(s.equals("PASV")) { // 进入被动模式
        if(pasvSocket!=null)
            pasvSocket.close();
        try {
            pasvSocket = new ServerSocket(0);
            int pPort = pasvSocket.getLocalPort();
            String s_port;
            if(pPortRETR
")) { // 传文件
        String file = currentDir + (currentDir.endsWith("/") ? "" : "/") + getParam(s, "RETR");
        System.out.println("download file: " + file);
        Socket dataSocket;
        // 根据上一次的PASV或PORT命令决定使用哪个socket
        if(pasvSocket!=null)
            dataSocket = pasvSocket.accept();
        else
            dataSocket = new Socket(this.host, this.port);
        OutputStream dos = null;
        InputStream fis = null;
        response("150 Opening ASCII mode data connection.");
        try {
            fis = new BufferedInputStream(new FileInputStream(translatePath(file)));
            dos = new DataOutputStream(new BufferedOutputStream(dataSocket.getOutputStream()));
        
    // 开始正式发送数据:
            byte[] buffer = new byte[20480]; // 发送缓冲 20k
        
    int num = 0; // 发送一次读取的字节数
            do {
                num = fis.read(buffer);
                if(num!=(-1)) {
                    // 发送:
                    dos.write(buffer, 0, num);
                    dos.flush();
                }
            } while(num!=(-1));
            fis.close();
            fis = null;
            dos.close();
            dos = null;
            dataSocket.close();
            dataSocket = null;
            response("226 transfer complete."); // 响应一个成功标志
        }
        catch(Exception e) {
            response("550 ERROR: File not found or access denied.");
        }
        finally {
            try {
                if(fis!=null) fis.close();
                if(dos!=null) dos.close();
                if(dataSocket!=null) dataSocket.close();
            }
            catch(Exception e) {}
        }
    }
    else if(s.equals("LIST")) { // 列当前目录文件
        Socket dataSocket;
        // 根据上一次的PASV或PORT命令决定使用哪个socket
        if(pasvSocket!=null)
            dataSocket = pasvSocket.accept();
        else
            dataSocket = new Socket(this.host, this.port);
        PrintWriter writer = new PrintWriter(new BufferedOutputStream(dataSocket.getOutputStream()));
        response("150 Opening ASCII mode data connection.");
        try {
            responseList(writer, this.currentDir);
            writer.close();
            dataSocket.close();
            response("226 transfer complete.");
        }
        catch(IOException e) {
            writer.close();
            dataSocket.close();
            response(e.getMessage());
        }
        dataSocket = null;
    }
    else {
        response("500 invalid command"); // 没有匹配的命令,输出错误信息
    }
}
// 响应LIST命令
private void responseList(PrintWriter writer, String path) throws IOException {
    File dir = new File(translatePath(path));
    if(!dir.isDirectory())
        throw new IOException("550 No such file or directory");
  
  File[] files = dir.listFiles();
    String dateStr;
    for(int i=0; i
基本上我们的Ftp已经可以运行了,注意到我们在FtpConnection中处理USER和PASS命令,直接返回200 OK,如果需要验证用户名和口令,还需要添加相应的代码。
如何调试Ftp服务器?
有个最简单的方法,便是使用现成的Ftp客户端,推荐
CuteFtp
,因为它总是把客户端发送的命令和服务器响应打印出来,我们可以非常方便的看到服务器的输出结果。
另外一个小Bug,文件列表在CuteFtp中可以正常显示,在其他Ftp客户端不一定能正常显示,这说明输出响应的“兼容性”还不够好,有空了看看Ftp的RFC再改进!:


本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u1/45066/showart_460388.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP