提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
DataPulse-智能文件同步系统-1
项目信息
项目名称:DataPulse-智能文件同步系统
项目目标: 实现一个智能文件同步系统,能够在本地文件夹和远程服务器之间同步文件,具备冲突检测和自动合并功能。
实现成果:
一个完整可视化的文件同步系统。
实现文件的实时监控和自动同步
冲突检测和自动合并功能
图形化界面,方便用户操作和管理
至少支持两种同步模式:单向同步和双向同步
一、页面设计及功能
登录注册页面
首页:左侧显示用户信息,右侧显示该用户本地维护的文件
功能:
用户登录,系统初始化时读取配置文件中的用户信息,用户输入账号和密码进行登录。
上传文件功能,点击选择文件,文件存储至用户本地文件夹中并将文件发送给服务器,服务器存储上传文件。
删除文件功能,在文件表格中选中需要删除的文件,删除本地文件夹中的相应文件和服务器中的文件。
二、功能实现
1.上传功能
技术点:
IO流操作,文件读写,socket网络通信
上传按钮监听类:
本地存储上传文件:
获取选中文件,将文件存复制到本地
JFileChooser.APPROVE_OPTION:用户点击了“打开”按钮,选择了一个或多个文件。
FileMap为存储File对象的HashSet,是User中的一个成员变量,存储当前用户本地所有的文件
本地文件名为用户名+上传文件名
若文件不存在,创建文件,并将上传文件的内容写入新创建的文件中
若重复上传同一文件,则本地需要维护最新的文件,将原有文件删除再重新复制最新上传文件
服务器同步上传文件:
客户端:将用户名长度、操作指令、文件名和文件内容发送给服务器,UPLOAD、DELETE分别代表上传和删除操作
服务端:接收文件名,若文件不存在,创建文件,存在则删除重新创建,接收文件内容,写入新建文件
网络通信编程:
Socket:用于TCP网络通信的客户端。
ServerSocket:用于TCP网络通信的服务器端。
服务器端的 ServerSocket:服务器端创建一个 ServerSocket 对象,并指定一个端口号。调用 serverSocket.accept() 方法会使得服务器进入阻塞状态,直到一个客户端尝试连接到这个端口。
客户端的 Socket:客户端创建一个 Socket 对象,并尝试连接到服务器的IP地址和端口号。一旦连接建立,客户端 Socket 对象就与服务器端的某个 Socket 对象形成了一个通信链路。
关联但不等同:当服务器端的 serverSocket.accept() 方法返回时,它返回一个新的 Socket 对象,这个对象代表了与客户端的连接。虽然客户端和服务器端的 Socket 对象不是同一个对象,但它们是一对,通过TCP/IP协议栈进行通信。
输入输出流:每个 Socket 对象都有相关的输入流和输出流。服务器端的 Socket 输入流可以读取客户端发送的数据,服务器端的 Socket 输出流可以向客户端发送数据。同理,客户端的 Socket 对象也有输入流和输出流用于与服务器通信。
关闭连接:当通信完成后,应该关闭相关的 Socket 对象以及它们的输入输出流。这会释放系统资源,并通知对方通信已经结束。
更新文件显示面板显示上传的文件
filePanel为文件展示面板
通过filePanel获取表格组件,并通过model向其中添加列显示文件名和文件大小(B)
问题记录:
使用缓冲流读写doc、pdf等文件时内容会乱码
查阅资料后发现word等文档需要使用特定的库,因为这些文件格式是专有的,并没有在Java标准库中直接支持。
后续发现使用FileInputStream,FileOutPutStream读写doc,pdf等文件内容不会乱码,暂时使用文件字节流代替缓冲流处理乱码问题。
UpListener
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import java.net.Socket;
import java.util.HashSet;
public class UpListener implements ActionListener {
JPanel fileJPanel;
User user ;
public UpListener(User user){
this.user = user ;
}
public UpListener(){}
@Override
public void actionPerformed(ActionEvent e) {
JFileChooser fileChooser = new JFileChooser();
int result = fileChooser.showOpenDialog(null);
if (result == JFileChooser.APPROVE_OPTION) {
System.out.println("选中文件");
// 用户选择了文件
File selectedFile = fileChooser.getSelectedFile();
System.out.println("Selected file: " + selectedFile.getAbsolutePath());
HashSet<File> files = user.getFileMap();
//将上传的文件复制后保存至本地
String oldName = selectedFile.getName();
String newName = user.getUname()+oldName;
System.out.println("上传后的文件名为:"+newName);
File file = new File("userdata\\"+newName);
files.add(file);
if(!file.exists()){
try {
file.createNewFile();
} catch (IOException ioException) {
files.remove(file);
//创建文件失败 从set中移除file对象
ioException.printStackTrace();
}
}else{
file.delete();
try {
file.createNewFile();
} catch (IOException ioException) {
files.remove(file);
ioException.printStackTrace();
}
}
//将上传的文件内容复制到新创建的文件中去
copyFile(selectedFile,file);
//将文件同步上传至服务器
sendFileToServer("127.0.0.1",12345,file,user.getUname());
//上传操作需要更新文件面板
Component[] coms = fileJPanel.getComponents();
for(Component com : coms){
if(com != null){
if("cyPane".equals(com.getName())){
Component component = ((JScrollPane)com).getViewport().getView();
JTable table = (JTable) component;
DefaultTableModel model = (DefaultTableModel) table.getModel();
model.addRow(new Object[]{file.getName(),file.length()/8});
}
}
}
System.out.println("coms"+coms.length);
}
}
public void copyFile(File oldFile , File newFile){
FileInputStream inputStream = null;
FileOutputStream outputStream = null ;
try {
inputStream = new FileInputStream(oldFile);
outputStream = new FileOutputStream(newFile,true);
byte[] bytes = new byte[1024];
int count ;
while((count = inputStream.read(bytes))!= -1){
outputStream.write(bytes,0,count);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally{
if(inputStream != null){
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(outputStream != null){
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public void sendFileToServer(String ip , int port , File file,String uName){
String filePath = file.getPath();
String fileName = file.getName();
//文件大小 单位为字节
long size = file.length();
//文件名大小
System.out.println("本地文件的路径为:"+filePath);
System.out.println("本地文件名为:"+fileName);
Socket socket = null ;
FileInputStream fis = null;
OutputStream outputStream = null ;
try {
socket = new Socket(ip,port);
fis = new FileInputStream(filePath);
outputStream = socket.getOutputStream();
//发送指令
outputStream.write("UPLOAD".getBytes());
//发送用户名长度
int len = uName.length();
outputStream.write(len);
//文件名长度不超过256
byte[] fName = fileName.getBytes();
outputStream.write(fName.length);
System.out.println("文件名字节长度:"+fName.length);
outputStream.write(fName);
byte[] bytes = new byte[2048];
int count ;
while((count = fis.read(bytes)) != -1){
outputStream.write(bytes,0,count);
}
System.out.println("文件发送成功!");
} catch (IOException e) {
e.printStackTrace();
}finally{
if(fis != null){
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(outputStream != null){
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
2.删除功能
删除本地文件
从table组件中获取选中文件名,从组件中移除选中行,并根据获取到的文件名找到本地对应的文件删除文件
删除服务器文件
将操作指令、用户名、删除的文件名发送给服务器,服务器找到相应文件删除
发送文件名前先将文件的个数发送至服务器,下面代码使用字节流将一个int发送至服务器
//发送一个int表示要删除文件的个数
int delFiles = fileNames.length;
int b1 = (delFiles >> 24) & 0xFF;
int b2 = (delFiles >> 16) & 0xFF;
int b3 = (delFiles >> 8) & 0xFF;
int b4 = (delFiles >> 0) & 0xFF;
out.write(b1);
out.write(b2);
out.write(b3);
out.write(b4);
问题记录
调用file.delete()方法文件无法删除,手动进行删除提示IOException
跟当前删除的文件相关的读写流没有关闭完全,因此需要检查代码,将相关的流全部关闭
DelListener
import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import java.net.Socket;
public class DelListener implements ActionListener {
JTable fileTable ;
User user ;
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("删除按钮点击事件!");
//页面上将文件名从列表中移除
int[] selectedRows = fileTable.getSelectedRows();
String[] fileNames = new String[selectedRows.length];
System.out.println("待删除文件名:"+fileNames);
int index = 0;
DefaultTableModel model = (DefaultTableModel) fileTable.getModel();
//获取文件名
for (int row : selectedRows) {
// 假设表格有三列
String fileName = (String) model.getValueAt(row, 0); // 获取第一列的值
fileNames[index++] = fileName;
}
//本地删除文件
delFiles(fileNames);
for (int i = selectedRows.length - 1; i >= 0; i--) {
model.removeRow(selectedRows[i]);
}
//服务器删除文件
delServerFiles("127.0.0.1",12345, fileNames,user.getUname());
}
public void delFiles(String[] fileNames){
for (String s : fileNames){
String path = "userdata\\"+s;
System.out.println("本地删除的文件相对路径:"+path);
File file = new File(path);
if(file.exists()){
boolean isDel = file.delete();
System.out.println(path+"删除成功"+isDel);
}
}
}
public void delServerFiles( String ip , int port , String[] fileNames , String uName ){
//将文件名发给服务器
try {
Socket socket = new Socket(ip, port);
OutputStream out = socket.getOutputStream();
out.write("DELETE".getBytes());
out.write(uName.getBytes().length);
out.write(uName.getBytes());
//发送一个int表示要删除文件的个数
int delFiles = fileNames.length;
int b1 = (delFiles >> 24) & 0xFF;
int b2 = (delFiles >> 16) & 0xFF;
int b3 = (delFiles >> 8) & 0xFF;
int b4 = (delFiles >> 0) & 0xFF;
out.write(b1);
out.write(b2);
out.write(b3);
out.write(b4);
for (String fileName : fileNames){
out.write(fileName.getBytes().length);
out.write(fileName.getBytes());
}
out.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.服务器
Server
userFile存储用户名和当前用户所有文件名-文件对象map的映射,后续多用户处理可能会用到
每次获取一个客户端连接,新建一个线程进行处理
服务器将接收到的四个字节还原成int
int b1 = in.read();
int b2 = in.read();
int b3 = in.read();
int b4 = in.read();
int delfiles = (b1<<24)|(b2<< 16)|(b3 << 8)|b4;
import java.net.Socket;
import java.util.*;
public class Server {
//key 用户名 value 存储文件名-文件对象的map
static HashMap<String,HashMap<String,File>> userFile = new HashMap<>();
public static void main(String[] args) {
ServerSocket server = null ;
try {
server = new ServerSocket(12345);
// 监听发送到这个端口的连接
System.out.println("阻塞监听连接:");
while(true){
Socket socket = server.accept();// 阻塞方法
System.out.println("监听连接成功" + socket.getInetAddress());
new Thread(new Runnable() {
@Override
public void run() {
//处理服务器发送的数据
handleClient(socket);
}
}).start();
}
} catch (IOException e) {
e.printStackTrace();
}finally{
if(server != null ){
try {
server.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void handleClient(Socket socket){
System.out.println("进入handleClient方法");
InputStream in = null;
OutputStream out =null;
try {
in = socket.getInputStream();
//读取指令
byte[] command = new byte[6];
in.read(command);
String cmd = new String(command);
System.out.println("服务器收到的指令为:"+cmd);
if("UPLOAD".equals(cmd)){
int uLen = in.read();
// 输入流获取文件名长度的ascii码
int len = in.read();
byte[] fileName = new byte[len];
in.read(fileName);
String fName = new String(fileName);
//文件名
String uName = fName.substring(0,uLen);
System.out.println("服务器接收到的用户名为:"+uName);
System.out.println("接受到上传的文件名为:"+fName);
File directory = new File("serverFile\\"+uName);
if(!directory.exists()){
boolean isCreated = directory.mkdir();
System.out.println(directory.getPath()+"文件夹是否创建成功"+isCreated);
}
File file = new File("serverFile\\"+uName+"\\"+fName);
if(!file.exists()){
file.createNewFile();
userFile.getOrDefault(uName,new HashMap<>()).put(fName,file);
}else{
file.delete();
file.createNewFile();
userFile.getOrDefault(uName,new HashMap<>()).put(fName,file);
}
out = new FileOutputStream(file);
byte[] bytes = new byte[2048];
int count ;
while((count = in.read(bytes)) != -1){
out.write(bytes,0,count);
}
System.out.println("文件上传成功!");
}else if("DELETE".equals(cmd)){
int uLen = in.read();
byte[] bytes = new byte[uLen];
in.read(bytes);
String uName = new String(bytes);
System.out.println("删除指令服务器接收到的用户名"+uName);
int b1 = in.read();
int b2 = in.read();
int b3 = in.read();
int b4 = in.read();
int delfiles = (b1<<24)|(b2<< 16)|(b3 << 8)|b4;
System.out.println("服务器接收到待删除的文件数量为"+delfiles);
for (int i = 0; i < delfiles; i++) {
int len = in.read();
byte[] b = new byte[len];
in.read(b);
String fileName = new String(b);
System.out.println("待删除文件名为:"+fileName);
File file = new File("serverFile\\"+uName+"\\"+fileName);
System.out.println("");
if(file.exists()){
System.out.println("文件存在");
boolean ret = file.delete();
System.out.println(ret);
if(ret){
userFile.get(uName).remove(fileName);
System.out.println("服务器删除"+fileName+"文件成功!");
}
}
System.out.println("循环结束");
}
}
} catch (IOException e) {
e.printStackTrace();
}finally{
if(in != null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(out != null ){
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
总结
实现了登录,上传文件和删除文件功能,后续会继续完善,包括多客户端同步等功能