简单的ftp服务器实现 (java)

学习了计算机网络之后,利用java写了一个ftp服务器。

一、实现的ftp命令

       实现了基本的user,pass,list,port,quit,retr,cwd,stor等命令

二、以上命令所对应的功能

        对应的功能是:下载,上传,获取服务器目录,切换目录等


三、用于测试的ftp客户端:windows自带的ftp客户端


四、实现的思想

       1、使用ServerSocket进行监听,每个控制连接的请求到来之后,开启一个线程进行处理(这里使用的java bio,效率较差,对于控制连接最好使用NIO处理,之后会再写个

              nio的实现)

       2、 对于命令使用工厂方法模式进行设计,当需要添加新的命令的时候,只需要添加一个新的命令类,实现相应接口,修改工厂产生逻辑,而不用修改其他的程序代码。可

              扩展性较好,同时符合开闭原则。

五、实现过程中碰到的问题

       1、对于tcp与socket的关系理解错误,以为所有的数据的输入都是要经过serverSocket().accept()方法。其实,ServerSocket.accept()所对应的是tcp里面的三次握手建

              立连接的阶段,之后的tcp的连接由客户端和服务器端的一对socket来维护,是属于establish阶段,在这个阶段,通信是全双工的,任何一方都能够发送数据。

              socket.close()对应的阶段是断开连接(四次挥手)的阶段。

        2、刚开始对于ftp协议不是很理解,不知道他的工作方式是怎样的,后来在看了tcp协议卷里面的ftp的内容之后,才知道ftp命令和应答码是关键。eg:刚开始测试时,在

              输入用户名之后,不会提示输入密码的。原因:没有返回对应的应答码:331.   另外要注意的是:返回的数据要以换行回车作为结束--\r\n.

六、代码列表


简单说明:

ftpServer:是服务器的主程序,入口,同时负责监听本地的21号端口。

ControllerThread.java:用于处理控制连接的线程(每一个控制连接请求对应一个线程)ps:实在很浪费(流量小,连接多)。

Share:一些全局性数据的维护。

Command:是命令接口,定义了一个所有命令都要实现的方法。

CommandFactory:命令工厂,通过传人的参数,决定生成的命令对象。

UserCommand,PortCommand等:是具体ftp命令的实现


七、详细代码

1、FtpServer:

package ftpServer;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class FtpServer {
	
	private int port;
	
	ServerSocket serverSocket;
	
	public FtpServer(int port) throws IOException {
		 
		serverSocket = new ServerSocket(port);
		//初始化系统信息
		  Share.init();
	}
	
	public void listen() throws IOException {
		 
		  Socket socket = null;
	      while(true) {
	    	//这个是建立连接,三次握手的过程,当连接建立了之后,两个socket之间的通讯是直接通过流进行的,不用再通过这一步
	    	  socket = serverSocket.accept();
	    	  ControllerThread thread = new ControllerThread(socket);
	    	  thread.start();
	      } 
	}
	
	public static void main(String args[]) throws IOException {
		FtpServer ftpServer = new FtpServer(21);
		ftpServer.listen();
	}
	
	
}

2、ControllerThread
package ftpServer;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.Socket;

/**
 * @author onroadrui
 * 用于处理控制连接数据请求的线程
 * 控制连接:在创建之后,直到socket.close()(四次挥手的过程),
 * 都是tcp里面的establish的阶段。可以自由地传输数据(全双工的)
 * */
public class ControllerThread extends Thread{
	
	private int count = 0;
	
	//客户端socket与服务器端socket组成一个tcp连接
	private Socket socket;
		
	//当前的线程所对应的用户
	public static final ThreadLocal<String> USER = new ThreadLocal<String>();

	//数据连接的ip
	private String dataIp;
	
	//数据连接的port
	private  String dataPort;
	
	//用于标记用户是否已经登录
	private boolean isLogin = false;
	
	//当前目录
	private String nowDir = Share.rootDir;
	
	
	
	public String getNowDir() {
		return nowDir;
	}

	public void setNowDir(String nowDir) {
		this.nowDir = nowDir;
	}

	public void setIsLogin(boolean t) {
		isLogin = t;
	}
	
	public boolean getIsLogin() {
		return isLogin;
	}
	
	public Socket getSocket() {
		return socket;
	}
	
	public String getDataIp() {
		return dataIp;
	}

	public void setDataIp(String dataIp) {
		this.dataIp = dataIp;
	}

	public String getDataPort() {
		return dataPort;
	}

	public void setDataPort(String dataPort) {
		this.dataPort = dataPort;
	}

	public ControllerThread(Socket socket) {
		this.socket = socket; 
	}

	
	public void run() {
		 System.out.println("hello");
		 BufferedReader reader;
		try {
			  reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));	
			  Writer writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
			  while(true) {
				  //第一次访问,输入流里面是没有东西的,所以会阻塞住
				  if(count == 0) 
				  {
					  writer.write("220");
					  writer.write("\r\n");
					  writer.flush();
				      count++;
				  }
				  else {
					  //两种情况会关闭连接:(1)quit命令 (2)密码错误
					  if(!socket.isClosed()) {
						  //进行命令的选择,然后进行处理,当客户端没有发送数据的时候,将会阻塞
						  String command = reader.readLine();  
						  if(command !=null) {
						      String[] datas = command.split(" ");
						      Command commandSolver = CommandFactory.createCommand(datas[0]); 
						      //登录验证,在没有登录的情况下,无法使用除了user,pass之外的命令
						      if(loginValiate(commandSolver)) {
							      if(commandSolver == null)
							      {
							    	  writer.write("502  该命令不存在,请重新输入");
							      }
							      else 
							      {
								      String data = "";
								      if(datas.length >=2) {
								    	  data = datas[1];
								      }
								      commandSolver.getResult(data, writer,this);
							      }
						      }
						      else 
						      {
						    	  writer.write("532 执行该命令需要登录,请登录后再执行相应的操作\r\n");
						    	  writer.flush();
						      }
						  }   	
					  }
					  else {
						  //连接已经关闭,这个线程不再有存在的必要
						  break;
					  }
				  }
			  }
		    
		} catch (IOException e) {
			e.printStackTrace();
		}  
		finally {
			 System.out.println("结束tcp连接");
		}
	     
	}
	
	
	public  boolean loginValiate(Command command) {
		if(command instanceof UserCommand || command instanceof PassCommand) {
			return true;
		}
		else 
		{
			return isLogin;
		}
	}
	
	
	
}

3、Share

package ftpServer;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;

/**
 * 所有线程共享的变量
 * */
public class Share {
	
	/*根目录的路径*/
	public static  String rootDir = "C:"+File.separator;
	
	/*允许登录的用户*/
	public static Map<String,String> users = new HashMap<String,String>();
		
	/*已经登录的用户*/
	public static HashSet<String> loginedUser = new HashSet<String>();
	
	/*拥有权限的用户*/
	public static HashSet<String> adminUsers = new HashSet<String>();
	
	//初始化根目录,权限用户,能够登录的用户信息
	public static void init(){
		String path = System.getProperty("user.dir") + "/bin/server.xml";
		
		File file = new File(path);
		SAXBuilder builder = new SAXBuilder();
		try {
			Document parse = builder.build(file);
			Element root = parse.getRootElement();
			
			//配置服务器的默认目录
			rootDir = root.getChildText("rootDir");
			System.out.print("rootDir is:");
			System.out.println(rootDir);
			
			//允许登录的用户
			Element usersE = root.getChild("users");
			List<Element> usersEC = usersE.getChildren();
			String username = null;
			String password = null;
			System.out.println("\n所有用户的信息:");
			for(Element user : usersEC) {
				username = user.getChildText("username");
				password = user.getChildText("password");
				System.out.println("用户名:"+username);
				System.out.println("密码:"+password);
				users.put(username,password);
			}
			
			/*
			//拥有put权限和delete权限的用户
			System.out.println("\n管理员用户:");
			Element adminUsersE = root.getChild("adminUsers");
			for(Element adminUserTemp: (List<Element>)adminUsersE.getChildren()) {
				username = adminUserTemp.getText();
				//System.out.println("用户名:"+username);
				adminUsers.add(username);
			}	*/
		} catch (JDOMException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}	
	}

}

4、Command

package ftpServer;

import java.io.Writer;

interface Command {

	/**
	 * @param data    从ftp客户端接收的除ftp命令之外的数据
	 * @param writer  网络输出流
	 * @param t       控制连接所对应的处理线程
	 * */
	public void getResult(String data,Writer writer,ControllerThread t);
	
}


5、CommandFactory

package ftpServer;

public class CommandFactory {

	public static Command createCommand(String type) {
		
		type = type.toUpperCase();
		switch(type)
		{
			case "USER":return new UserCommand();
			
			case "PASS":return new PassCommand();
			
			case "LIST":return new DirCommand();
			
			case "PORT":return new PortCommand();
			
			case "QUIT":return new QuitCommand();
			
			case "RETR":return new RetrCommand();
			
			case "CWD":return new CwdCommand();
			
			case "STOR":return new StoreCommand();
			
			default :return null;
		}
		
	}
}

6、UserCommand
package ftpServer;

import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;

public class UserCommand implements Command{
	
	/**
	 * 检验是否有这个用户名存在
	 * */
	@Override
	public void getResult(String data,Writer writer,ControllerThread t) {
		String response = "";
		if(Share.users.containsKey(data)) {
			ControllerThread.USER.set(data);
			response = "331";
		}
		else {
			response = "501";
		}
		
		try {
			writer.write(response);
			writer.write("\r\n");
			writer.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		
	}

}

7、PassComand

package ftpServer;

import java.io.IOException;
import java.io.Writer;

public class PassCommand implements Command{
	
	@Override
	public void getResult(String data, Writer writer,ControllerThread t) {
		
		System.out.println("execute the pass command");
		System.out.println("the data is "+data);
		//获得用户名
		String key = ControllerThread.USER.get();
		String pass = Share.users.get(key);
		
		String response = null;
		if(pass.equals(data)) {
			System.out.println("登录成功");
			Share.loginedUser.add(key);
			t.setIsLogin(true);
			response = "230 User "+key+" logged in";
		}
		else {
			System.out.println("登录失败,密码错误");
			response = "530   密码错误";
		}
		try {
			writer.write(response);
			writer.write("\r\n");
			writer.flush();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}

}

9、PortCommand

package ftpServer;

import java.io.IOException;
import java.io.Writer;

public class PortCommand implements Command{

	@Override
	public void getResult(String data, Writer writer,ControllerThread t) {
		String response = "200 the port an ip have been transfered";
		try {
			
			String[] iAp =  data.split(",");
			String ip = iAp[0]+"."+iAp[1]+"."+iAp[2]+"."+iAp[3];
			String port = Integer.toString(256*Integer.parseInt(iAp[4])+Integer.parseInt(iAp[5]));
			System.out.println("ip is "+ip);
			System.out.println("port is "+port);
			t.setDataIp(ip);
			t.setDataPort(port);
			writer.write(response);
			writer.write("\r\n");
			writer.flush();
		} catch (IOException e) {
			
			e.printStackTrace();
		}
	}

}

10、DirComand:这个对应的是List命令。。。没改回来

package ftpServer;

import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.Socket;
import java.net.UnknownHostException;

public class DirCommand implements Command{

	/**
	 * 获取ftp目录里面的文件列表
	 * */
	@Override
	public void getResult(String data, Writer writer,ControllerThread t) {
		String desDir = t.getNowDir()+data;
		System.out.println(desDir);
		File dir = new File(desDir);
		if(!dir.exists()) {
			try {
				writer.write("210  文件目录不存在\r\n");
				writer.flush();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		else 
		{
			StringBuilder dirs = new StringBuilder();
			System.out.println("文件目录如下:");
			dirs.append("文件目录如下:\n");
			String[] lists= dir.list();
			String flag = null;
			for(String name : lists) {
				System.out.println(name);
				File temp = new File(desDir+File.separator+name);
				if(temp.isDirectory()) {
					flag = "d";
				}
				else {
					flag = "f";
				}
				dirs.append("\t");
				dirs.append(flag);
				dirs.append("  ");
				dirs.append(name);
				dirs.append("\n");
				
			}
			
			//开启数据连接,将数据发送给客户端,这里需要有端口号和ip地址
			 Socket s;
			try {
				 writer.write("150 open ascii mode...\r\n");
				 writer.flush();
				 s = new Socket(t.getDataIp(), Integer.parseInt(t.getDataPort()));
				 BufferedWriter dataWriter = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));
				 dataWriter.write(dirs.toString());
				 dataWriter.flush();
				 s.close();
				 writer.write("220 transfer complete...\r\n");
				 writer.flush();
			} catch (NumberFormatException e) {
				
				e.printStackTrace();
			} catch (UnknownHostException e) {
			
				e.printStackTrace();
			} catch (IOException e) {
				
				e.printStackTrace();
			}
		}
		
	}

}

11、CwdCommand

package ftpServer;

import java.io.File;
import java.io.IOException;
import java.io.Writer;

/**
 * 改变工作目录
 * */
public class CwdCommand implements Command{

	@Override
	public void getResult(String data, Writer writer, ControllerThread t) {
		String dir = t.getNowDir() +File.separator+data;
		File file = new File(dir);
		try {
			if((file.exists())&&(file.isDirectory())) {
				String nowDir =t.getNowDir() +File.separator+data;
				t.setNowDir(nowDir);
				writer.write("250 CWD command succesful");	
			}
			else 
			{
				writer.write("550 目录不存在");
			}
			writer.write("\r\n");
			writer.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	
}

12、RetrCommand
package ftpServer;

import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.Socket;

/**
 * 处理文件的发送
 * */
public class RetrCommand implements Command{

	@Override
	public void getResult(String data, Writer writer, ControllerThread t) {
		Socket s;
		String desDir = t.getNowDir()+File.separator+data;
		File file = new File(desDir);
		System.out.println(desDir);
		if(file.exists())
		{
			try {
				 writer.write("150 open ascii mode...\r\n");
				 writer.flush();
				 s = new Socket(t.getDataIp(), Integer.parseInt(t.getDataPort()));
				 BufferedOutputStream dataOut = new BufferedOutputStream(s.getOutputStream());
				 byte[] buf = new byte[1024];
				 InputStream is = new FileInputStream(file); 
				 while(-1 != is.read(buf)) {
					 dataOut.write(buf);
				 }
				 dataOut.flush();
				 s.close();
				 writer.write("220 transfer complete...\r\n");
				 writer.flush();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		else {
			try {
				writer.write("220  该文件不存在\r\n");
				writer.flush();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

}

13、StoreCommand

package ftpServer;

import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.io.Writer;
import java.net.Socket;



public class StoreCommand implements Command{

	@Override
	public void getResult(String data, Writer writer, ControllerThread t) {
			try{ 
				writer.write("150 Binary data connection\r\n"); 
				writer.flush();
				RandomAccessFile inFile = new 
				RandomAccessFile(t.getNowDir()+"/"+data,"rw");
				//数据连接
				Socket tempSocket = new Socket(t.getDataIp(),Integer.parseInt(t.getDataPort())); 
				InputStream inSocket 
				= tempSocket.getInputStream(); 
				byte byteBuffer[] = new byte[1024]; 
				int amount; 
				//这里又会阻塞掉,无法从客户端输出流里面获取数据?是因为客户端没有发送数据么
				while((amount =inSocket.read(byteBuffer) )!= -1){ 
					inFile.write(byteBuffer, 0, amount); 
				} 
				System.out.println("传输完成,关闭连接。。。");
				inFile.close();
				inSocket.close();
				tempSocket.close();
				//断开数据连接
				
				writer.write("226 transfer complete\r\n"); 
				writer.flush();
			} 
			catch(IOException e){
				e.printStackTrace();
			} 
			
		
	
	}
}


14、QuitStore

package ftpServer;

import java.io.IOException;
import java.io.Writer;

public class QuitCommand implements Command{

	@Override
	public void getResult(String data, Writer writer, ControllerThread t) {
		
		try {
			writer.write("221 goodbye.\r\n");
			writer.flush();
			writer.close();
			t.getSocket().close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}





评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值