⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 ftpserver.java

📁 实现FTP服务器与客户端的功能,主要解决得了同时上传数据时的冲突问题
💻 JAVA
📖 第 1 页 / 共 2 页
字号:
import java.io.*;
import java.net.*;
import java.util.*;

public class FtpServer 
{
	private Socket socketClient;
	private int counter;
	public static String initDir;	//保存服务器线程运行时所在的工作目录
	public static ArrayList users = new ArrayList();
	public static ArrayList usersInfo = new ArrayList();
	
	public FtpServer()
	{
		FtpConsole fc = new FtpConsole();
		fc.start();/*Java 虚拟机调用线程 run 方法,结果是两个线程同时运行: 
					当前线程 (方法 start 返回的线程) 和另一个线程 (执行方法 
					run 的线程)*/
		loadUsersInfo();		//加载
		counter = 1;		
		int i = 0;
		try
		{

			//监听21号端口,21口用于控制,20口用于传数据
			ServerSocket s = new ServerSocket(21);
			for(;;)
			{
				//接受客户端请求
				Socket incoming = s.accept();
				BufferedReader in = new BufferedReader(new InputStreamReader(incoming.getInputStream()));
			    PrintWriter out = new PrintWriter(incoming.getOutputStream(),true);//文本文本输出流
				out.println("220 准备为您服务"+",你是当前第  "+counter+" 个登陆者!");//命令正确的提示

				//创建服务线程
				FtpHandler h = new FtpHandler(incoming,i);
				h.start();
				users.add(h);   //将此用户线程加入到这个 ArrayList 中
				counter++;
				i++;
			}
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
		
	}
	
	//loadUsersInfo方法
	//把文件中的用户信息(用户名,密码,路径)加载到UserInfo 这个 ArrayList 中
	public void loadUsersInfo()
	{
		String s = getClass().getResource("user.cfg").toString();/*getResource()按指
						定名查找资源,返回一资源的 URL 值;getClass()返回一个对象的运
						行时间类;*/
		s = s.substring(6,s.length());//子串开始6,扩展到 s.length()的位置。 ///???
		int p1 = 0;		//放 | 的索引
		int p2 = 0;		//放 | 后一位的索引
		if(new File(s).exists())//测试当前 File 是否存在
		{
			try
			{
				BufferedReader fin = new BufferedReader(new InputStreamReader(new FileInputStream(s)));
				String line;  //从文件中取一行存于此
				String field; //放 | 前 line 的子串
				int i = 0;
				//第一个while 作用为读所有行
				while((line = fin.readLine())!=null)//达流尾则为 null
				{
					UserInfo tempUserInfo = new UserInfo();
					p1 = 0;
					p2 = 0;
					i = 0;
					if(line.startsWith("#"))//如以#开始,返回ture
						continue;
					//第二个while 作用为load 文件中一行的信息
					while((p2 = line.indexOf("|",p1))!=-1)//从p1开始查,返回 | 第一次出现的索引,没有返回-1
					{
						field = line.substring(p1,p2);//从p1 ~ p2-1
						p2 = p2 +1; 
						p1 = p2;   //新p2
						switch(i)
						{
							case 0:
								tempUserInfo.user = field;
								break;
							case 1:
								tempUserInfo.password = field;
								break;
							case 2:
								tempUserInfo.workDir = field;
								break;
						}
						i++;
					} 
					usersInfo.add(tempUserInfo);
				}
				fin.close();
			}
			catch(Exception e)
			{
				e.printStackTrace();
			}
		}
	}
	
	//main方法
	/*主函数完成服务器端口的侦听和服务线程的创建,服务器的初始工作目录是由
 	 *程序运行时用户输入的,缺省为C盘的根目录*/
	public static void main(String[] args)
	{
		if(args.length != 0) 
		{
			initDir = args[0];
		}
		else
		{ 
			initDir = "c:/";	
		}
		FtpServer ftpServer = new FtpServer();
		
	} 
		
}
/******************************************************************************/
/* 	FTP 处理机       														 **/
/******************************************************************************/
class FtpHandler extends Thread
{
	Socket ctrlSocket;		//用于控制的套接字
	Socket dataSocket;		//用于传输的套接字
	int id;
	String cmd = "";		//存放指令(空格前)
	String param = "";		//放当前指令之后的参数(空格后)
	String user;
	String remoteHost = " ";	   //客户IP
	int remotePort = 0;			   //客户TCP 端口号
	String dir = FtpServer.initDir;//当前目录
	String rootdir = "c:/";	       //默认根目录,在checkPASS中设置
	int state = 0 ;				   //用户状态标识符,在checkPASS中设置
	String reply;				   //返回报告
	PrintWriter ctrlOutput; 
	int type = 0;				   //文件类型(ascII 或 bin)
	String requestfile = "";
	boolean isrest = false;
	
	//FtpHandler方法
	//构造方法
	public FtpHandler(Socket s,int i)
	{
		ctrlSocket = s;
		id = i;	
	}

	//run 方法
	public void run()
	{
		String str = "";
		int parseResult;							//与cmd 一一对应的号
		
		try
		{
			BufferedReader ctrlInput = new BufferedReader
								(new InputStreamReader(ctrlSocket.getInputStream()));
			ctrlOutput = new PrintWriter(ctrlSocket.getOutputStream(),true);
			state  = FtpState.FS_WAIT_LOGIN;  		//0
			boolean finished = false;
			while(!finished)	
			{
				str = ctrlInput.readLine();			///
				if(str == null) finished = true;	//跳出while
				else
				{
					parseResult = parseInput(str);  //指令转化为指令号
					System.out.println("指令:"+cmd+" 参数:"+param);
					System.out.print("->");
					switch(state)					//用户状态开关
					{
						case FtpState.FS_WAIT_LOGIN:
								finished = commandUSER();
								break;
						case FtpState.FS_WAIT_PASS:
								finished = commandPASS();
								break;
						case FtpState.FS_LOGIN:
						{
							switch(parseResult)//指令号开关,决定程序是否继续运行的关键
							{
								case -1:
									errCMD();					//语法错
									break;
								case 4:
									finished = commandCDUP();   //到上一层目录
									break;
								case 6:
									finished = commandCWD();	//到指定的目录
									break;
								case 7:
									finished = commandQUIT();	//退出
									break;
								case 9:
									finished = commandPORT();	//客户端IP:地址+TCP 端口号
									break;
								case 11:
									finished = commandTYPE();	//文件类型设置(ascII 或 bin)
									break;
								case 14:
									finished = commandRETR();	//从服务器中获得文件
									break;
								case 15:
									finished = commandSTOR();	//向服务器中发送文件
									break;
								case 22:
									finished = commandABOR();	//关闭传输用连接dataSocket
									break;
								case 23:
									finished = commandDELE();	//删除服务器上的指定文件
									break;
								case 25:
									finished = commandMKD();	//建立目录
									break;
								case 27:
									finished = commandLIST();	//文件和目录的列表
									break;
								case 26:
								case 33:
									finished = commandPWD();	//"当前目录" 信息
									break;
								case 32:
									finished = commandNOOP();	//"命令正确" 信息
									break;
								
							}
						}
							break;
						

					}
				} 
				ctrlOutput.println(reply);
				ctrlOutput.flush();////////////////////////////////////
				
			} 
			ctrlSocket.close();
		} 
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}

	//parseInput方法	
	int parseInput(String s)
	{
		int p = 0;
		int i = -1;
		p = s.indexOf(" ");
		if(p == -1) 				 //如果是无参数命令(无空格)
			cmd = s;
		else 
			cmd = s.substring(0,p);  //有参数命令,过滤参数
		
		if(p >= s.length() || p ==-1)//如果无空格,或空格在读入的s串最后或之外
			param = "";
		else
			param = s.substring(p+1,s.length());
		cmd = cmd.toUpperCase();     //转换该 String 为大写
		
	  	if(cmd.equals("CDUP"))
				i = 4;
		if(cmd.equals("CWD"))
				i = 6;
		if(cmd.equals("QUIT"))
				i = 7;
		if(cmd.equals("PORT"))
				i = 9;
		if(cmd.equals("TYPE"))
				i = 11;
		if(cmd.equals("RETR"))
				i = 14;
		if(cmd.equals("STOR"))
				i = 15;
		if(cmd.equals("ABOR"))
				i = 22;
		if(cmd.equals("DELE"))
				i = 23;
		if(cmd.equals("MKD"))
				i = 25;
		if(cmd.equals("PWD"))
				i = 26;
		if(cmd.equals("LIST"))
				i = 27;
	  	if(cmd.equals("NOOP"))
				i = 32;
		if(cmd.equals("XPWD"))
				i = 33;
	 return i;
	}
	
	//validatePath方法
	//判断路径的属性,返回 int 
	int validatePath(String s)
	{
		File f = new File(s);		//相对路径
		if(f.exists() && !f.isDirectory())
		{
			String s1 = s.toLowerCase();
			String s2 = rootdir.toLowerCase();
			if(s1.startsWith(s2))	
				return 1;			//文件存在且不是路径,且以rootdir 开始
			else
				return 0;			//文件存在且不是路径,不以rootdir 开始
		}
		f = new File(addTail(dir)+s);//绝对路径
		if(f.exists() && !f.isDirectory())
		{
			String s1 = (addTail(dir)+s).toLowerCase();
			String s2 = rootdir.toLowerCase();
			if(s1.startsWith(s2))
				return 2;			//文件存在且不是路径,且以rootdir 开始
			else 
				return 0;			//文件存在且不是路径,不以rootdir 开始
		}
		return 0;					//其他情况
	}
	
	boolean checkPASS(String s) //检查密码是否正确,从文件中找
	{
		for(int i = 0; i<FtpServer.usersInfo.size();i++)
		{
			if(((UserInfo)FtpServer.usersInfo.get(i)).user.equals(user) && 
				((UserInfo)FtpServer.usersInfo.get(i)).password.equals(s))
			{
				rootdir = ((UserInfo)FtpServer.usersInfo.get(i)).workDir;
				dir = ((UserInfo)FtpServer.usersInfo.get(i)).workDir;
				return true;
			}
		}
		return false;
	}

	//commandUSER方法
	//用户名是否正确
	boolean commandUSER()
	{
		if(cmd.equals("USER"))
		{
			reply = "331 用户名正确,需要口令";
			user = param;
		  	state = FtpState.FS_WAIT_PASS;
			return false;
		}
		else
		{
			reply = "501 参数语法错误,用户名不匹配";
			return true;
		}

	}

	//commandPASS 方法
	//密码是否正确
	boolean commandPASS()
	{
		if(cmd.equals("PASS"))
		{
			if(checkPASS(param))
			{
				reply = "230 用户登录了";
				state = FtpState.FS_LOGIN;
				System.out.println("新消息: 用户: "+user+" 来自于: "+ remoteHost +"登录了");
				System.out.print("->");
				return false;
			}
			else
			{
				reply = "530 没有登录";
				return true;
			}
		}
		else
		{
			reply = "501 参数语法错误,密码不匹配";
			return true;
		}

	}

	void errCMD()
	{
		reply = "500 语法错误";
	}	
	
	boolean commandCDUP()//到上一层目录
	{					 
		dir = FtpServer.initDir;	
		File f = new File(dir);
		if(f.getParent()!=null &&(!dir.equals(rootdir)))//有父路径 && 不是根路径
		{
			dir = f.getParent();
			reply = "200 命令正确";
		}
		else
		{
			reply = "550 当前目录无父路径";
		}
		
		return false;
	}// commandCDUP() end

	boolean commandCWD()// CWD (CHANGE WORKING DIRECTORY)
	{					//该命令改变工作目录到用户指定的目录
		File f = new File(param);
		String s = "";
		String s1 = "";
		if(dir.endsWith("/"))
			s = dir;
		else
			s = dir + "/";
		File f1 = new File(s+param);
		
		if(f.isDirectory() && f.exists())
		{
			if(param.equals("..") || param.equals("..\\"))
			{
				if(dir.compareToIgnoreCase(rootdir)==0)
				{
					reply = "550 此路径不存在";
					//return false;
				}
				else
				{
					s1 = new File(dir).getParent();
					if(s1!=null)
					{
						dir = s1;
						reply = "250 请求的文件处理结束, 当前目录变为: "+dir;
					}
					else
						reply = "550 此路径不存在";
				}
			}
			else if(param.equals(".") || param.equals(".\\"))
			{}
			else 
			{
				dir = param;
				reply = "250 请求的文件处理结束, 工作路径变为 "+dir;
			}		
		}
		else if(f1.isDirectory() && f1.exists())
		{
			dir = s+param;
			reply = "250 请求的文件处理结束, 工作路径变为 "+dir;
		}
		else
			reply = "501 参数语法错误";
		
		return false;
	} // commandCDW() end

	boolean commandQUIT()
	{
		reply = "221 服务关闭连接";
		return true;
	}// commandQuit() end
	
/*使用该命令时,客户端必须发送客户端用于接收数据的32位IP 地址和16位 的TCP 端口号。
 *这些信息以8位为一组,使用十进制传输,中间用逗号隔开。
 */
	boolean commandPORT()
	{
		int p1 = 0;
		int p2 = 0;
		int[] a = new int[6];//存放ip+tcp
		int i = 0;			 //
		try
		{
			while((p2 = param.indexOf(",",p1))!=-1)//前5位
			{
				 a[i] = Integer.parseInt(param.substring(p1,p2));
				 p2 = p2+1;
				 p1 = p2;
				 i++;
			}
			a[i] = Integer.parseInt(param.substring(p1,param.length()));//最后一位
		}
		catch(NumberFormatException e)
		{
			reply = "501 参数语法错误";
			return false;
		}
		
		remoteHost = a[0]+"."+a[1]+"."+a[2]+"."+a[3];
		remotePort = a[4] * 256+a[5];
		reply = "200 命令正确";
		return false;
	}//commandPort() end
		
	/*LIST 命令用于向客户端返回服务器中工作目录下的目录结构,包括文件和目录的列表。
	 *处理这个命令时,先创建一个临时的套接字向客户端发送目录信息。这个套接字的目的
	 *端口号缺省为,然后为当前工作目录创建File 对象,利用该对象的list()方法得到一
	 *个包含该目录下所有文件和子目录名称的字符串数组,然后根据名称中是否含有文件名
	 *中特有的"."来区别目录和文件。最后,将得到的名称数组通过临时套接字发送到客户端。
	 **/
	boolean commandLIST()//文件和目录的列表
	{
		try
		{
			dataSocket = new Socket(remoteHost,remotePort,InetAddress.getLocalHost(),20);
			PrintWriter dout = new PrintWriter(dataSocket.getOutputStream(),true);
			if(param.equals("") || param.equals("LIST"))
			{
				ctrlOutput.println("150 文件状态正常,ls以 ASCII 方式操作");
				File f = new File(dir);
				String[] dirStructure = f.list();//指定路径中的文件名数组,不包括当前路径或父路径
				String fileType;
				for(int i =0; i<dirStructure.length;i++)
				{
					if(dirStructure[i].indexOf(".")!=-1)
					{
						fileType = "- ";		//父目录(在linux下)
					}
					else
					{
						fileType = "d ";		//本目录的文件和子目录
					}
					dout.println(dirStructure[i]);//(fileType+dirStructure[i]);
				}
			} 
			dout.close();
			dataSocket.close();
			reply = "226 传输数据连接结束";
		}
		catch(Exception e)
		{
			e.printStackTrace();
			reply = "451 Requested action aborted: local error in processing";
			return false;
		}
		
		return false;
	}// commandLIST() end

	boolean commandTYPE()	//TYPE 命令用来完成类型设置
	{
		if(param.equals("A"))
		{
			type = FtpState.FTYPE_ASCII;//0
			reply = "200 命令正确 ,转 ASCII 模式";
		}
		else if(param.equals("I"))
		{
			type = FtpState.FTYPE_IMAGE;//1
			reply = "200 命令正确 转 BINARY 模式";

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -