用Java构建稳定的Ftp服务器 (3)

下面我们要处理用户连接,也就是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 < length; i++)
buf.append((char)' ');
return buf.toString();
}

// 获取参数
private String getParam(String cmd, String start) {
String s = cmd.substring(start.length(), cmd.length());
return s.trim();
}

// 获取路径
private String translatePath(String path) {
if(path==null) return root;
if(path.equals("")) return root;
path = path.replace('/', '\\');
return root + path;
}

// 获取文件长度,注意是一个字符串
private String getFileLength(long length) {
String s = Long.toString(length);
int spaces = 12 - s.length();
for(int i=0; i<spaces; i++)
s = " " + s;
return s;
}

接下来便是处理用户命令,这个方法有点长,需要重构一下,我只是把LIST命令单独挪了出来:

private void parseCommand(String s) throws Exception {
if(s==null || s.equals(""))
return;
if(s.startsWith("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<=4 || 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(pPort<=255)
s_port = "255";
else{
int p1 = pPort / 256;
int p2 = pPort - p1*256;
s_port = p1 + "," + p2;
}
pasvSocket.setSoTimeout(60000);
response("227 Entering Passive Mode ("
+ InetAddress.getLocalHost().getHostAddress().replace('.', ',')
+ "," + s_port + ")");
}
catch(Exception e) {
if(pasvSocket!=null) {
pasvSocket.close();
pasvSocket = null;
}
}
}
else if(s.startsWith("RETR")) { // 传文件
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<files.length; i++) {
dateStr = new SimpleDateFormat("MMM dd hh:mm").format(new Date(files[i].lastModified()));
if(files[i].isDirectory()) {
writer.println("drwxrwxrwx 1 ftp System 0 "
+ dateStr + " " + files[i].getName());
}
else {
writer.println("-rwxrwxrwx 1 ftp System "
+ getFileLength(files[i].length()) + " " + dateStr + " " + files[i].getName());
}
}

String file_header = "-rwxrwxrwx 1 ftp System 0 Aug 5 19:59 ";
String dir_header = "drwxrwxrwx 1 ftp System 0 Aug 15 19:59 ";
writer.println("total " + files.length);
writer.flush();
}

待续

Xlight FTP 2.8官方中文岚雨绿化曲线破解版 岚雨主页:http://hi.baidu.com/y21song 本版特点:基于官方2.8中文版(完美中文),没有改动任何文件,理论上运行稳定性要好,官方中文的帮助文件,好好学习哦。在网上看到菜鸟说这说那的,所以把安装版的四个快捷功能做成了批处理,放在了“管理 Xlight FTP 系统服务”文件夹里面了,郁闷:网上菜鸟太多(本来都可以在软件里面实现)。瑕疵的地方就是在主界面上依然显示“没有注册”。本人测试了Xlight FTP 2.8和2.4版的中文版,都可以破解成功,由此,可以得出结论,以后的新版本也可能可以使用此破解办法破解哦。大家感谢我吧,我可是用了好几天的时间才搞定的哦。gei钱给qian! 最近要给朋友传一些文件,所以要建个FTP,试用了近20个国内国外的FTP服务器软件,由于本人对软件有“洁癖”,总是不满意,鬼火死。又觉得如果用Serv-U有点儿小题大做了,最后锁定了Xlight FTP,有点点瑕疵都不太受得了,网络上虽然有众多的此软件破解,可是大多都是基于英文的破解加语言文件,语言文件的翻译不是十分ok(太挑剔o(∩_∩)o...),唯一的最新基于中文版的破解是2.4,可是版本太老(严重挑剔--鸡蛋里面找骨头啦),在尝试了众多的方法之后,终于有了自己的破解版,噢噢!错了错了,应该是曲线破解版——严格说,不是对软件的直接破解——不过,我保证是全功能永久免费使用。哇哇——我真是人才啊,自恋一下!21世纪什么最贵————想说是人才吧——错,是猪肉!^_^继续说破解吧,本人不懂编程,所以破解如果有问题请大家不要骂我。(自信满满:不会有问题的)在xp、2k3下测试无问题,其它系统没有条件测试。 此破解方法的理论依据之一: Xlight FTP 服务器有三个版本:个人版本,标准版本,专业版本。 个人版本只对个人使用是免费的,如果是非个人使用,需要注册标准版或专业版。试用期的程序在30天试用期内,将会以专业版本的功能运行。 30天试用期后, 如果没有注册,FTP服务器自动变成个人版本。试用期内是全功能啊,所以,成功!永远的试用专业版! 破解的具体步骤:把文件解压到硬盘,运行“xlight2.24.exe”,这是Xlight FTP2.24英文版,此版本已经破解,点击软件右上角的图标(像个小房子的那个),输入任意的用户名和注册码注册(请用字母,别加空格,免得出错,谢谢合作),在这里注册之后就可以退出此程序了,然后使用xlight.exe使用了,试用的时间就会永远的停留在30天,成功! 注意事项:不使用破解,也可以免费的全功能试用30天,30试用期结束之后,会自动变为个人版,一些高级功能无法使用。 另:本人有一种更简便快捷的破解方式,不过为了给菜鸟们多多学习,多多操作的一个机会,所以就只公布此一种破解办法了,而高手们也不会看得上我的另外一种破解办法,所以留起来自己用啦,(可能是自私了一些,表骂我)。这也是学习嘛。 其实对于个人用户来说,个人版本已经够用了,无需破解! 本软件发布仅仅是为了学习,请在下载12小时内测试之后删除,请支持正版!也请作者降降价!感谢大家!感谢作者提供如此好软! 个人给一个此软件与Serv-U的个人经验对比(本人菜鸟,如有错误,请高手不吝赐教): Serv-U:功能强大,适合老鸟大虾和企业试用,好像没有官方中文,只有汉化版,破解版很多,容易下载 Xlight FTP:功能不比Serv-U弱,入手方便,几乎适合所有人群使用(不包括超级菜鸟),官方提供中文版,有中文帮助文件,容易学习,破解版也多,不过基于官方中文的破解较少!(不过本破解就是啦,还是系列破解,应该2.X的各版本适用,如果以后有了新版,应该也是可以破解的) 再次提示:请尊重软件原作者的劳动
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值