前几节我们完成了ftp协议的主要讲解,同时使用wireshark抓包了解ftp数据协议包的特征,本节我们使用代码完成ftp协议,代码将模仿ftp客户端,它与服务器建立连接后,使用用户名和密码登陆服务器,然后获得服务器的当前目录内容,继而通过数据连接获取服务器推送目录具体信息,最后客户端关闭,下面我们看看具体的代码实现,首先在工程目录下新建名为FTPClient的类,相关实现如下:
代码语言:javascript复制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 chenyirn");
}
if (return_code.equals("331")) {
System.out.println("receive code 331: " return_str);
//服务器请求用户名密码
send_command("PASS 1111rn");
}
if (return_code.equals("230")) {
System.out.println("receive code 230: " return_str);
//用户登录成功
send_command("PWDrn"); //获取服务器文件目录
}
if (return_code.equals("257")) {
System.out.println("receive code 257: " return_str);
send_command("PASVrn");
}
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 Arn"); //通知服务器以ASCII的方式传输数据
}
if (return_code.equals("200")) { //服务器同意使用ASCII方式传递数据
System.out.println("receive code 200: " return_str);
send_command("LISTrn");//要求服务器传输当前目录下的文件信息
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的实现:
代码语言:javascript复制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();
}
}
}
完成上面代码后运行,我们可以得到如下结果:
从图中可以看到,我们代码成功接收了ftp服务器推送的目录信息。更多详细讲解和代码调试演示请点击’阅读原文‘。