2018.4.24是学完javaSE的时候,身边的哥哥让我去写一个FTP服务器来检测一下自己的学习成果,一开始的两天是没有一点思路的- - 经过大量的百度和看别人的实现(尽管很多都是用Jar包实现的),摸清了些思路,抱着见招拆招的心开始写,功能实现的差不多之后再设计多线程和服务器 客户端交互过程中发现了自己的设计思想太不成熟,所以考虑半天后进行重写,重新设计自己的程序,……终于在今天初步完成,有待改进的地方应该很多,但最基本的功能都已经实现,我知道在IT学习这条路上这仅仅是个开始,毕竟是自己面向对象语言的第一个小项目,我认为需要写一个总结来总结自己的欠缺之处以及在写项目过程中碰到的一些问题的解决方法,So,一个大一小白将总结一下自己的智障之处- - 放出自己那破烂不堪的代码,让未来的自己羞愧一把!
————————————————————————————————-
这个程序无非就是通过Socket网络编程去连接客户端与对应端口号的服务器,通过服务器去调用工具类的方法实现客户端输入的对应指令的功能,同时为了一个服务器运行同时能有多个客户端与其进行通信,也写了一个线程类,使服务器不停的开辟线程服务客户端,服务器端对客户端的真正的服务其实是在线程中实现的,服务器端的main方法只是起到一个启动服务器并不断开辟线程等待客户端连接的一个功能。
本项目可以实现的功能有:登陆,文件的上传,下载,对服务器端文件夹的遍历,对文件的删除,重命名。
在实现过程中用到了 IO流,Socket网络编程,在其中对功能的实现中用到了很多字符串处理操作。
————未完待续
客户端类
package com.xijian1.java;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
//客户端类
public class Client {
private String Connect_ServerIp;//需连接服务器的IP地址
private int Connect_ServerPort;//需连接服务器的端口号
//需连接服务器的IP与端口号的get、set方法
public String getConnect_ServerIp() {
return Connect_ServerIp;
}
public void setConnect_ServerIp(String connect_ServerIp) {
Connect_ServerIp = connect_ServerIp;
}
public int getConnect_ServerPort() {
return Connect_ServerPort;
}
public void setConnect_ServerPort(int connect_ServerPort) {
Connect_ServerPort = connect_ServerPort;
}
//客户端类的构造器
public Client() {
}
//通过此构造器创建服务器端对象
public Client(String Connect_ServerIp,int Connect_ServerPort) {
this.Connect_ServerIp = Connect_ServerIp;
this.Connect_ServerPort = Connect_ServerPort;
}
//启动客户端
public void startClient() {
InputStream is = null;
OutputStream os = null;
Scanner scanner = new Scanner(System.in);//用户输入
boolean done = false;//退出客户端while循环的标志
boolean flag = false;
Socket socket =null;
//建立客户端套接字
try {
socket = new Socket(InetAddress.getByName(Connect_ServerIp),Connect_ServerPort);
is =socket.getInputStream();
os =socket.getOutputStream();
System.out.println(readMsg(is));//读取欢迎信息
while(!done) {
String str = scanner.nextLine();//用户输入指令
String command = str.trim().toUpperCase();//处理用户输入字符串
sendMsg(os,command);
if(command.equals("EXIT")) {//退出功能
done = true;
System.out.println(readMsg(is));
System.out.println("请输入指令(help可以查看帮助菜单):");
}
else if(command.equals("HELP")) {//帮助菜单
System.out.println(readMsg(is));
System.out.println("请输入指令(help可以查看帮助菜单):");
}
else if(command.equals("LOGIN")) {//登陆操作
System.out.println(readMsg(is));
String AccountId = scanner.nextLine();
sendMsg(os,AccountId);
System.out.println(readMsg(is));
String AccountKey = scanner.nextLine();
sendMsg(os,AccountKey);
String result = readMsg(is);//获取登陆结果
System.out.println(result);
if(result.equals("登陆成功!")) {
flag = true;//获取了功能权限
System.out.println("请输入指令(help可以查看帮助菜单):");
}
else {
done = true;
}
}
else if(command.equals("DIR") && flag == true) {//遍历文件夹
String str1= readMsg(is);
String[] strs =str1.trim().split(" ");
for(int i = 0;i<strs.length;i++) {
System.out.println(strs[i]);
}
System.out.println("请输入指令(help可以查看帮助菜单):");
}
else if(command.equals("DOWNLOAD") && flag == true) {//文件下载操作
System.out.println(readMsg(is));
String FileName = scanner.nextLine();
sendMsg(os,FileName);
System.out.println(readMsg(is));
String DownloadPath = scanner.nextLine();
sendMsg(os,DownloadPath);
System.out.println(readMsg(is));
System.out.println("请输入指令(help可以查看帮助菜单):");
}
else if(command.equals("UPLOAD")) {//文件上传操作
System.out.println(readMsg(is));
String UploadPath = scanner.nextLine();
sendMsg(os,UploadPath);
System.out.println(readMsg(is));
System.out.println("请输入指令(help可以查看帮助菜单):");
}
else if(command.equals("DELETE")) {//文件删除操作
System.out.println(readMsg(is));
String FileName = scanner.nextLine();
sendMsg(os,FileName);
System.out.println(readMsg(is));
System.out.println("请输入指令(help可以查看帮助菜单):");
}
else if(command.equals("RENAME")) {//文件重命名操作
System.out.println(readMsg(is));
String OldName = scanner.nextLine();
sendMsg(os,OldName);
System.out.println(readMsg(is));
String NewName = scanner.nextLine();
sendMsg(os,NewName);
System.out.println(readMsg(is));
System.out.println("请输入指令(help可以查看帮助菜单):");
}
else {
System.out.println(readMsg(is));
}
}//end of while
} catch (UnknownHostException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}finally {
scanner.close();
if(socket != null) {
try {
socket.close();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
}
//向服务器发送消息
public void sendMsg(OutputStream os,String str) {
try {
os.write(str.getBytes());
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
//读取服务器发送的消息
public String readMsg(InputStream is){
byte[] b = new byte[1024];
int len = 0;
try {
len = is.read(b);
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
String str = new String(b,0,len);
return str;
}
//main方法
public static void main(String[] args) {
Client client = new Client("127.0.0.1",9090);
client.startClient();
}
}
工具类
package com.xijian1.java;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
//工具类
public class Command {
private String UploadPath;//文件的上传路径,根据登陆成功后的账号去确定并设置好这个属性。
//关于UploadPath的get、set方法
public String getUploadPath() {
return UploadPath;
}
public void setUploadPath(String UploadPath) {
this.UploadPath = UploadPath;
}
//Command构造器
public Command() {
super();
}
//Login登陆功能
public boolean Login(String AccountId,String AccountKey) {
BufferedReader br =null;
try {
File file1 = new File("D:\\学习\\作业\\JAVA课程设计 ftp服务器\\配置文件\\ConfigurationFiles.txt");//配置文件
FileReader fr = new FileReader(file1);
br = new BufferedReader(fr);
String str = null;
while((str = br.readLine()) != null) {
String[] strs = str.trim().split("\\s");//将字符串去除前后空白字符后用\s分割
if(strs[0].equals(AccountId)) {//比对账号
if(strs[1].equals(AccountKey)) {//比对密码
UploadPath = "D:\\学习\\作业\\JAVA课程设计 ftp服务器\\UploadPath"+"\\\\"+AccountId;//确定上传路径
File file2 = new File(UploadPath);
if(file2.exists() == false) {//如果文件夹不存在,创建文件夹
file2.mkdirs();
}
return true;
}
}
}//end of while
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}// end of Login
//UpLoad上传功能
public boolean UpLoad(String FilePath) {
File file1 = new File(FilePath);
if(file1.exists() == false) {//检查用户指定的文件是否存在
return false;
}
//读取需上传文件的名称
String FileName = file1.getName();
File file2 = new File(UploadPath+"\\\\"+FileName);
//上传文件操作
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
InputStream is = new FileInputStream(file1);
OutputStream os = new FileOutputStream(file2);
bis = new BufferedInputStream(is);
bos = new BufferedOutputStream(os);
byte[] b = new byte[1024];
int len;
while((len = bis.read(b)) != -1) {
bos.write(b, 0, len);
bos.flush();
}
} catch (FileNotFoundException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
return true;
}//end of Upload
//删除操作
public boolean Delete(String FileName) {
File file = new File(UploadPath+"\\\\"+FileName);//需要删除的文件
if(file.exists() == false) {//判断文件是否存在
return false;
}
file.delete();
return true;
}
//下载文件的操作DownLoad
public boolean DownLoad(String FileName,String DownloadPath) {
File file1 = new File(UploadPath+"\\\\"+FileName);//访问服务器文件夹中的对应指定文件
if(file1.exists() == false) {
return false;
}
File file2 = new File(DownloadPath+"\\\\"+FileName);//用户下载路径
//下载操作
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
InputStream is = new FileInputStream(file1);
OutputStream os = new FileOutputStream(file2);
bis = new BufferedInputStream(is);
bos = new BufferedOutputStream(os);
byte[] b = new byte[1024];
int len;
while((len = bis.read(b)) != -1) {
bos.write(b, 0, len);
bos.flush();
}
} catch (FileNotFoundException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
return true;
}
//遍历服务器文件夹内的文件列表
public String Dir() {//传入客户端一个字符串,需要客户端将字符串处理。
File file = new File(UploadPath);
String[] strs = file.list();
String str = "";
String str1 = " ";
for(int i = 0;i<strs.length;i++) {
str = str + str1 + strs[i];
}
return str;
}
//文件重命名操作
public boolean reName(String OldName,String NewName) {
File file = new File(UploadPath+"\\\\"+OldName);
if(file.exists() == false) {
return false;
}
File file1 = new File(UploadPath + "\\\\" + NewName);
file.renameTo(file1);
return true;
}
}
服务器端类
package com.xijian1.java;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
//服务端类
public class Server {
private final String SERVER_IP = "127.0.0.1";//本地服务器IP
private final int SERVER_PORT = 9090;//本地服务器端口号
//IP地址和端口号的get方法
public String getSERVER_IP() {
return SERVER_IP;
}
public int getSERVER_PORT() {
return SERVER_PORT;
}
//服务器端类的构造器
public Server() {
super();
}
//启动服务器
public void startServer() {
Socket reQuest = null;
ServerSocket rServer = null;
Thread receiveThread = null;
try {
rServer = new ServerSocket(SERVER_PORT);
System.out.println("欢迎进入MYFTP服务器端,服务器已经启动");
System.out.println("等待客户端连接~");
while(true) {
reQuest = rServer.accept();//等待客户端连接
receiveThread = new ServerThread(reQuest);
receiveThread.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
//main方法,启动服务器
public static void main(String[] args) {
Server server = new Server();
server.startServer();//启动服务器
}
}
服务器端线程类
package com.xijian1.java;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class ServerThread extends Thread{
Socket clientRequest;// 用户连接的通信套接字
InputStream is = null;//输入流
OutputStream os = null;//输出流
//线程类的构造器
public ServerThread(Socket s) {
try {
clientRequest = s;
is =clientRequest.getInputStream();
os =clientRequest.getOutputStream();
sendMsg(os,"与服务端连接成功~"+"\n"+"请输入指令(help可以查看帮助菜单):");
} catch (IOException e) {
e.printStackTrace();
}
}
//线程的启动方法
public void run() {
String command = null;//用户输入的指令
String str = null;//读取的字符串
boolean done=false;//while大循环结束标志
boolean flag = false;//作为是否登陆的标志
Command c = new Command();
while(!done) {
str = readMsg(is);
command = str.trim().toUpperCase();
if(str == null || command.equals("EXIT")) {//退出客户端操作
done = true;
sendMsg(os,"欢迎下次使用!");
}
else if(command.equals("HELP")) {
sendMsg(os,"欢迎来到Help帮助文档"+"\n"+"Login~~~~~客户端登陆功能"+"\n"+
"Dir~~~~~遍历服务端文件夹功能"+"\n"+"Download~~~~~下载文件功能"
+ "\n"+"Upload~~~~~上传文件功能"+"\n"+"Delete~~~~~删除文件功能"
+"\n"+"Rename~~~~~文件重命名"+"\n"+"Exit~~~~~退出客户端");
}
else if(command.equals("LOGIN")) {//登陆操作
sendMsg(os,"请输入您的账号:");
String AccountId = readMsg(is);
sendMsg(os,"请输入您的密码:");
String AccountKey = readMsg(is);
Boolean b1 = c.Login(AccountId, AccountKey);
if(b1 == true) {
sendMsg(os,"登陆成功!");
flag = true;//获得了功能使用权限
}
else {
sendMsg(os,"登录失败,账号或密码有误!感谢您的使用~再见!");
done = true;//结束此线程
}
}
else if(command.equals("DIR") && flag == true) {//遍历文件夹操作
sendMsg(os,c.Dir());
}//遍历服务器文件夹列表,需要在客户端进行字符串操作
else if(command.equals("DOWNLOAD") && flag == true) {//文件下载操作
sendMsg(os,"请输入您需下载的文件名称(XXX.xx):");
String FileName = readMsg(is);
sendMsg(os,"请输入您的下载路径(X:\\\\xx):");
String DownloadPath = readMsg(is);
boolean b2 = c.DownLoad(FileName, DownloadPath);
if(b2 == false) {
sendMsg(os,"您输入的文件名或下载路径有误,请重试!");
}
else {
sendMsg(os,FileName+" 下载成功!");
}
}
else if(command.equals("UPLOAD") && flag == true) {//文件上传操作
sendMsg(os,"请输入您要上传的文件路径(X:\\\\XX.xxx):");
String UploadPath = readMsg(is);
boolean b3 = c.UpLoad(UploadPath);
if(b3 == false) {
sendMsg(os,"您输入的文件路径有误,请重试!");
}
else {
sendMsg(os,"文件上传成功!");
}
}
else if(command.equals("DELETE") && flag == true) {//文件删除操作
sendMsg(os,"请输入您要删除的文件名称(XX.xxx):");
String FileName = readMsg(is);
boolean b4 = c.Delete(FileName);
if(b4 == false) {
sendMsg(os,"您输入的文件名称有误,请重试!");
}
else {
sendMsg(os,FileName+" 文件删除成功!");
}
}
else if(command.equals("RENAME") && flag == true) {//文件重命名操作
sendMsg(os,"请输入您需要重命名的文件名称(XX.xxx):");
String OldName = readMsg(is);
sendMsg(os,"请输入您新的命名名称:");
String NewName = readMsg(is);
boolean b5 = c.reName(OldName, NewName);
if(b5 == false) {
sendMsg(os,"您输入的文件名称有误,请重试!");
}
else {
sendMsg(os,"重命名文件成功");
}
}
else {//指令错误
sendMsg(os,"您输入的指令有误,或者您没有进行登陆!");
}
}//end of while
}
//接收客户器端发送的消息
public String readMsg(InputStream is){
byte[] b = new byte[1024];
int len = 0;
try {
len = is.read(b);
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
String str = new String(b,0,len);
return str;
}
public void sendMsg(OutputStream os,String str){
try {
os.write(str.getBytes());
} catch (IOException e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
}
}
——————————————————————————————————————
在这个项目中碰到的首要问题就是设计思路问题,类的设计,类中成员属性的设计,以及服务器与客户端交互时的sendMsg与reaMsg方法,是最让人头疼的!在文件传输中应该去用UDP传输,但我在工具类中是用TCP传输实现的,这与文件传输的效率有关,所以有待改进~
代码块中有足够多的注释,应该不用我这个小白说太多,从输出语句到这一步我用了接近三个月,非常感谢的是身边有一个大神哥哥给我思路以及功能实现方面的指导,才得以完成这个小项目,谢谢谢谢!继续努力