网络编程实现FTP服务器
1:FTP
文件传输协议(File Transfer Protocol,FTP)是Internet上使用最广泛的文件传送协议,它是TCP/IP协议族中的协议之一,其目标是提高文件的共享性,提供可靠高效的数据传输服务。
FTP由RFC959定义,是基于TCP服务的应用层协议。FTP服务器一般运行在TCP的20,21号两个端口,端口20用于在客户端和服务器之间的数据传输;端口21用于传输控制流,并且是控制命令通向FTP服务器的入口。
2:FTP的工作原理:
(1)FTP的运作流程:
FTP是应用层的协议,它基于传输层TCP协议传输,TCP建立连接需要三次握手,FTP服务一般运行在20和21两个端口,端口20用于在客户端和服务器之间传输数据流,而端口21用于传输控制流.这两种传输都需要经过三次握手。
(2)文件传输中的处理过程:
建立了FTP的运作流程和文件传输处理过程的模型,我们就来了解主动连接。
1)三次握手的第一次:客服端发送FTP命令到服务端
客服端是用Windows命令进行操作的,下面是我们需要用到的命令
打开服务器(自己编程实现的),然后在Windows界面去连接。
以下是Windows中FTP命令,但最终会被用户协议解释器转换成相应的FTP命令发送给服务端;
Debug 可以看到所发送的内容。
1 ftp> help 列出 ftp 文件传输,可使用的任何命令。 2 ftp> !ls 列出本地工作站,目前目录下的所有文件名。 3 ftp> !pwd 列出本地工作站,目前所在的工作目录位置。 4 ftp> ls 列出远端工作站目前目录下的所有文件名。 5 ftp> dir 列出远端工作站目前目录下的所有文件名(略同于 UNIX 的 ls -l 指令). 6 ftp> pwd 列出远端工作站目前所在的目录位置。 7 ftp> cd dir1 更改远端工作站的工作目录位置至 dir1 下。 8 ftp> get file1 将远端工作站的文件 file1 ,拷贝到本地工作站中。 9 ftp> put file2 将本地工作站的文件 file2 ,拷贝到远端工作站中。
第一次握手客户端发送给服务器的内容是
发送的格式是 PORT x,x,x,x,pH,pL 下面是一个例子
PORT 127,0,0,1,208,6 中 127.0.0.1 是客户端发送给服务端的IP地址(将逗号转换成小数点)。因为端口是采用16位端口地址,所以告诉服务器,客户端正在监听的端口号是 208 * 256 + 6(PH * 256 + PL), 建立数据通道的时候会用。
2)三次握手第二次:FTP应答
应答都是ASCII码形式的3位数字,并很有报文选项。其原因是软件系统需要根据数据代码来决定如何应答。
应答3位码中的每一位都有不同的含义,这里是一些典型的应答,都带有一个可能得报文串
125 数据连接已经打开;传输开始;
150 数据通道已打开,开始传输文件;
200 新的连接已打开,请发送命令吧;
220 命令通道已建立,等待命令;
230 登录成功;
214 帮助报文(面向用户);
331 用户名就绪,要求输入口令;
425 建立数据通道失败;
501 语法错误;
530 用户或密码错误;
3)三次握手第三次:发出相应的FTP命令;
文件操作命令
LIST
ERTR
... ...
通过三次握手,实现FTP文件传输,下面是实现FTP服务器部分功能的代码:
1 import java.net.*; 2 import java.io.*; 3 import java.nio.charset.Charset; 4 import java.util.Random; 5 6 public class ServerBO{ 7 8 public static void main(String[] args){ 9 final String F_DIR = "D:/网络技术/FTP服务器/服务器中的文件"; 10 ServerSocket server_socket = null; 11 try{ 12 server_socket = new ServerSocket(21); 13 while(true){ 14 Socket socket = server_socket.accept(); 15 System.out.println("用户连接成功!"); 16 new ClientThread(socket,F_DIR).start(); 17 } 18 }catch(Exception e){ 19 e.printStackTrace(); 20 } 21 } 22 } 23 24 class ClientThread extends Thread{ 25 Socket socket; 26 String dir = ""; 27 public ClientThread(Socket s,String F_DIR){ 28 this.socket = s; 29 this.dir = F_DIR; 30 } 31 32 public void run(){ 33 InputStream is = null; 34 OutputStream os= null; 35 try{ 36 is = socket.getInputStream(); 37 os = socket.getOutputStream(); 38 }catch(Exception e){ 39 e.printStackTrace(); 40 } 41 42 BufferedReader br = new BufferedReader(new InputStreamReader(is,Charset.forName("UTF-8"))); 43 PrintWriter pw = new PrintWriter(os,true); 44 String clientIP = socket.getInetAddress().toString().substring(1); 45 46 String username ="not login in"; 47 String password = ""; 48 String command = ""; 49 boolean loginStatus = false; 50 final String LOGIN_WARNING = "530 Please log in with USER and PASS first."; 51 String str = ""; //命令内容字符串 52 int port_high = 0; 53 int port_low = 0; 54 String retr_ip = "";//接受文件的IP地址 55 56 Socket tempsocket = null; 57 //打印欢迎信息 58 pw.println("220 FTP Server A version 1.0 written by root"); 59 pw.flush(); 60 boolean b = true; 61 while(b){ 62 try{ 63 command = br.readLine(); 64 System.out.println(command); 65 if(null == command) break; 66 67 }catch(Exception e){ 68 pw.println("331 Failed to get commend"); 69 pw.flush(); 70 e.printStackTrace(); 71 b = false; 72 } 73 74 if(command.toUpperCase().startsWith("OPTS")){ 75 //告诉用户已经准备好了 76 System.out.println(command); 77 pw.println("FTP is ready"); 78 pw.flush(); 79 } 80 else if(command.toUpperCase().startsWith("USER")){ 81 username = command.substring(4).trim(); 82 if("".equals(username)){ 83 pw.println("501 Syntax error"); 84 pw.flush(); 85 username = "not logged in"; 86 }else{ 87 pw.println("331 Password required for " + username); 88 pw.flush(); 89 } 90 loginStatus = false; 91 } 92 93 else if(command.toUpperCase().startsWith("PASS")){ 94 password = command.substring(4).trim(); 95 if(username.equals("root") && password.equals("root")){ 96 pw.println("230 logged in"); 97 pw.flush(); 98 loginStatus = true; 99 }else{ 100 pw.println("530 login or password incorrect!"); 101 pw.flush(); 102 username = "not logged in"; 103 } 104 } 105 else if(command.toUpperCase().startsWith("PORT")){ 106 if(loginStatus){ 107 try{ 108 str = command.substring(4).trim(); 109 port_low = Integer.parseInt(str.substring(str.lastIndexOf(",")+1)); 110 port_high= Integer.parseInt(str.substring(0,str.lastIndexOf(",")) 111 .substring(str.substring(0,str.lastIndexOf(",")).lastIndexOf(",")+1)); 112 String str1 = str.substring(0,str.substring(0,str.lastIndexOf(",")).lastIndexOf(",")); 113 retr_ip = str1.replace(",","."); 114 int port = socket.getPort(); 115 try{ 116 tempsocket = new Socket(retr_ip,port_high * 256 + port_low); //这句话出现了问题 117 pw.println("200 新的连接已经打开!"); 118 pw.flush(); 119 }catch(Exception e){ 120 pw.println("425 不能建立数据连接!"); 121 pw.flush(); 122 e.printStackTrace(); 123 } 124 }catch (NumberFormatException e) { 125 pw.println("503 Bad sequence of commands."); 126 pw.flush(); 127 } 128 }else{ 129 pw.println(LOGIN_WARNING); 130 pw.flush(); 131 } 132 }else if (command.toUpperCase().startsWith("LIST")){ 133 if(loginStatus){ 134 try{ 135 pw.println("150 opening data channel for directory list"); 136 pw.flush(); 137 PrintWriter pwr = null; 138 try{ 139 pwr = new PrintWriter(tempsocket.getOutputStream(),true); 140 }catch(Exception e){ 141 e.printStackTrace(); 142 } 143 144 FtpUtil.getDetailList(pwr,dir); 145 try{ 146 tempsocket.close(); 147 pwr.close(); 148 }catch(Exception e){ 149 e.printStackTrace(); 150 } 151 152 pw.println("226 transfer OK"); 153 pw.flush(); 154 }catch(Exception e){ 155 156 e.printStackTrace(); 157 } 158 159 }else{ 160 pw.println(LOGIN_WARNING); 161 pw.flush(); 162 } 163 }else if(command.toUpperCase().startsWith("RETR")){ 164 if(loginStatus){ 165 str = command.substring(4).trim(); 166 if("".equals(str)){ 167 pw.println("501 Syntax error"); 168 pw.flush(); 169 } 170 else { 171 try { 172 pw.println("150 Opening data channel for file transfer."); 173 pw.flush(); 174 RandomAccessFile outfile = null; 175 OutputStream outsocket = null; 176 try { 177 outfile = new RandomAccessFile(new File(dir+"/"+str),"rw"); 178 outsocket = tempsocket.getOutputStream(); 179 } catch (Exception e) { 180 e.printStackTrace(); 181 } 182 byte bytebuffer[]= new byte[1024]; 183 int length; 184 try{ 185 while((length = outfile.read(bytebuffer)) != -1){ 186 outsocket.write(bytebuffer, 0, length); 187 } 188 outsocket.close(); 189 outfile.close(); 190 tempsocket.close(); 191 } 192 catch(IOException e){ 193 e.printStackTrace(); 194 } 195 pw.println("226 Transfer OK"); 196 pw.flush(); 197 } catch (Exception e){ 198 e.printStackTrace(); 199 pw.println("503 Bad sequence of commands."); 200 pw.flush(); 201 } 202 } 203 } 204 else{ 205 pw.println(LOGIN_WARNING); 206 pw.flush(); 207 } 208 }else { 209 pw.println("500 Systax error, command unrecognized."); 210 pw.flush(); 211 } 212 } 213 214 try{ 215 br.close(); 216 socket.close(); 217 pw.close(); 218 if(null != tempsocket){ 219 tempsocket.close(); 220 } 221 }catch(Exception e){ 222 e.printStackTrace(); 223 } 224 } 225 }
1 import java.io.File; 2 import java.io.PrintWriter; 3 import java.text.SimpleDateFormat; 4 import java.util.Date; 5 6 /** 7 * FTP工具类 8 * @author Leon 9 * 10 */ 11 public class FtpUtil { 12 13 public static void getDetailList(PrintWriter pwr, String path) throws Exception{ 14 File dir = new File(path); 15 if (!dir.isDirectory()) { 16 pwr.println("500 No such file or directory./r/n"); 17 pwr.flush(); 18 } 19 File[] files = dir.listFiles(); 20 String modifyDate; 21 for (int i = 0; i < files.length; i++) { 22 modifyDate = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date(files[i].lastModified())); 23 if (files[i].isDirectory()) { 24 pwr.println(modifyDate + " " + files[i].getName()); 25 pwr.flush(); 26 System.out.println(modifyDate + " " + files[i].getName()); 27 } else { 28 pwr.println(modifyDate+" "+ files[i].getName()); 29 pwr.flush(); 30 System.out.println(modifyDate + " " + files[i].getName()); 31 } 32 33 } 34 System.out.println("total:" + files.length); 35 pwr.println("total:" + files.length); 36 pwr.flush(); 37 } 38 39 }