前几节我们完成了ftp协议的主要讲解,同时使用wireshark抓包了解ftp数据协议包的特征,本节我们使用代码完成ftp协议,代码将模仿ftp客户端,它与服务器建立连接后,使用用户名和密码登陆服务器,然后获得服务器的当前目录内容,继而通过数据连接获取服务器推送目录具体信息,最后客户端关闭,下面我们看看具体的代码实现,首先在工程目录下新建名为FTPClient的类,相关实现如下:

package Application;

import java.net.InetAddress;

import utils.IFTPDataReceiver;
import utils.ITCPHandler;

public class FTPClient implements ITCPHandler, IFTPDataReceiver{
	private  TCPThreeHandShakes  tcp_socket = null;  
	private  int data_port = 0;
	private FTPDataReceiver data_receiver = null;
	private String server_ip;
	@Override
	public void connect_notify(boolean connect_res) {
		 if (connect_res == true) {
			 System.out.println("connect ftp server ok!");
		 }
	}

	@Override
	public void send_notify(boolean send_res, byte[] packet_send) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void recv_notify(byte[] packet_recv) {
		try {
			String server_return = new String(packet_recv, "ASCII");
			System.out.println("receive info from ftp server: " +  server_return);
			String return_code = server_return.substring(0, 3);
			String return_str = server_return.substring(3);
			if (return_code.equals("220")) {
				System.out.println("receive code 220: " + return_str);
				send_command("USER chenyi\r\n");
			}
			if (return_code.equals("331")) {
				System.out.println("receive code 331: " + return_str);
				//服务器请求用户名密码
				send_command("PASS 1111\r\n");
			}
			if (return_code.equals("230")) {
				System.out.println("receive code 230: " + return_str);
				//用户登录成功
				send_command("PWD\r\n"); //获取服务器文件目录
			}
			if (return_code.equals("257")) {
				System.out.println("receive code 257: " + return_str);
				send_command("PASV\r\n");
			}
			if (return_code.equals("227")) {
				System.out.println("receive code 227: " + return_str);
				int ip_port_index = return_str.indexOf("("); 
				String port_str = return_str.substring(ip_port_index);
				int ip_count = 4; //经过4个逗号就能找到端口
				while (ip_count > 0) {
					int idx = port_str.indexOf(',');
					ip_count--;
					port_str = port_str.substring(idx + 1);
				}
				int idx = port_str.indexOf(',');
				String p1 = port_str.substring(0, idx);
				port_str = port_str.substring(idx + 1);
				idx = port_str.indexOf(')');
				String p2 = port_str.substring(0, idx);
				int port = Integer.parseInt(p1) * 256 + Integer.parseInt(p2);
				System.out.println("get data port : " + port);
				data_port = port;
				send_command("TYPE A\r\n"); //通知服务器以ASCII的方式传输数据
			}
			if  (return_code.equals("200")) { //服务器同意使用ASCII方式传递数据
				System.out.println("receive code 200: " + return_str);
				send_command("LIST\r\n");//要求服务器传输当前目录下的文件信息
				data_receiver = new FTPDataReceiver(server_ip, data_port, this);
				data_receiver.get_data();
			}
			if (return_code.equals("150")) { //服务器通知数据发送完毕
				System.out.println("receive code 150: " + return_str);
				tcp_socket.tcp_close();
			}
			//tcp_socket.tcp_close();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	private void send_command(String command) {
		try {
			tcp_socket.tcp_send(command.getBytes());
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Override
	public void connect_close_notify(boolean close_res) {
		// TODO Auto-generated method stub
		
	}
	
	public void run() {
		 try {
			InetAddress ip = InetAddress.getByName("192.168.2.127"); //连接ftp服务器
			server_ip = "192.168.2.127";
			short port = 20000;
			tcp_socket = new TCPThreeHandShakes(ip.getAddress(), port, this);
			tcp_socket.tcp_connect();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Override
	public void receive_ftp_data(byte[] data) {
		System.out.println("Successfuly get ftp data");
		String ftp_data = new String(data);
		System.out.println("content of ftp_data: " + ftp_data);
	}

}

代码实现中recv_notify用来解读服务器返回的信息,前三个字符是服务器的返回码,后面字符串是对返回码的解释。这里值得关注的是当客户端向服务器发送PSAV命令后,服务器返回码为227,其中的字符串包含了用于数据传输的端口,代码需要解读返回字符串,然后计算出端口,并像服务器发送TYPE A命令告诉服务器通过ASCII模式传输数据。最后启动新的tcp连接去接收数据。一旦在数据端口与服务器实现三次握手后,服务器会主动给我们推送数据。在完成PSAV命令后,代码向服务器发送LIST命令,要求服务器给出当前目录下的所有文件信息,然后代码创建FTPDataReceiver实例,该对象负责通过数据端口与服务器连接,同时等待服务器推送数据,接收完数据后他把接收到的内容推送给FTPClient对象,我们看FTPDataReceiver的实现:

package Application;

import java.net.InetAddress;
import java.nio.ByteBuffer;

import utils.IFTPDataReceiver;
import utils.ITCPHandler;

public class FTPDataReceiver implements ITCPHandler{
	private int data_port = 0;
	private IFTPDataReceiver data_receiver = null;
	private  TCPThreeHandShakes  tcp_socket = null;
	private String server_ip = "";
	private byte[] data_buffer = new byte[4096];
	private ByteBuffer byteBuffer = null;
	public FTPDataReceiver(String ip, int port, IFTPDataReceiver receiver) {
		this.data_port = port;
		this.data_receiver = receiver;
		this.server_ip = ip;
		byteBuffer = ByteBuffer.wrap(data_buffer);
	}
	
	
    public void get_data() {
    	 try {
 			InetAddress ip = InetAddress.getByName(server_ip); //连接ftp服务器
 			tcp_socket = new TCPThreeHandShakes(ip.getAddress(), (short)data_port, this);
 			tcp_socket.tcp_connect();
 		} catch (Exception e) {
 			// TODO Auto-generated catch block
 			e.printStackTrace();
 		}	
    }
    
	@Override
	public void connect_notify(boolean connect_res) {
		if (connect_res) {
			System.out.println("ftp data connection ok");
		} else {
			System.out.println("ftp data connection fail");
		}
		
	}

	@Override
	public void send_notify(boolean send_res, byte[] packet_send) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void recv_notify(byte[] packet_recv) {
		System.out.println("ftp receiving data");
		byteBuffer.put(packet_recv);
	}

	@Override
	public void connect_close_notify(boolean close_res) {
		try {
			tcp_socket.tcp_close();
			data_receiver.receive_ftp_data(byteBuffer.array());
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}

}

完成上面代码后运行,我们可以得到如下结果:

java代码实现FTP协议_System

从图中可以看到,我们代码成功接收了ftp服务器推送的目录信息。
公众号:

java代码实现FTP协议_服务器_02