svn项目总结
此次svn项目主要功能:
客户端:
加载服务器文件,从服务器上更新文件到本地,提交本地文件到服务器上
服务器端:
客户端用户列表(每有一个IP连接上或断开服务器,列表都会更新),客户端操作记录(每个客户端的操作都会显示在文本框中,但这些记录没有保存在本地),服务器接收客户端的操作信息,相应做更新文件或发送消息的操作。
技术点主要就是socket的运用,以及文件版本控制原理,下面有一些关键点:
既然是socket项目,当然先建立socket连接才是一切开始的首要:
//客户端
socket = new Socket(getIP(), getPort());
ct = new ClientThread(socket);
//服务器
ServerSocket ss = new ServerSocket(7979);
while (true) {
// 等待客户端的请求
System.out.println("=======");
Socket s = ss.accept();
ServerThread st = new ServerThread(s);
new Thread(st).start();
// 输出谁连接上了
jta1.append(s.getInetAddress() + "连接成功" + "\n");
// jta2.append(s.getInetAddress());
String suser = s.getInetAddress().toString();
user.add(suser);
System.out.println(s.getInetAddress() + "连接成功");
}
封装从socket获取的输入输出流
InputStream ins = s.getInputStream();
OutputStream ous = s.getOutputStream();
dis = new DataInputStream(ins);
dos = new DataOutputStream(ous);
oos = new ObjectOutputStream(ous);
ois = new ObjectInputStream(ins);
使用ObjectOutputStream,ObjectInputStream发送读取对象信息
Message message = new Message(0, null);
ct.oos.writeObject(message);
ct.oos.flush();
Map m1 = (Map) ct.ois.readObject();
客户端读取本地文件并发送到服务器上
File file = new File(path);
FileInputStream fis = new FileInputStream(file);
// 获取文件输出流可读字节数
int byteNumber = fis.available();
byte[] count = new byte[byteNumber];
fis.read(count);
DataOutputStream dos = new DataOutputStream(ct.os);
System.out.println("获取数据输出流");
dos.writeInt(byteNumber);
System.out.println("count=" + count.length);
dos.write(count, 0, byteNumber);
// dos.write(count);
dos.flush();
服务器接收客户端发来的文件数据并保存到本地
int len = dis.readInt();
System.out.println(len + "=====================");
byte[] all = new byte[len];
dis.read(all);
System.out.println("文件" + new String(all));
System.out.println("=========" + all + "==========");
// System.out.print(all.toString());
FileOutputStream fos = new FileOutputStream("D:\\server\\" + bookname);
DataOutputStream fos1 = new DataOutputStream(new BufferedOutputStream(fos));
fos1.write(all);
fos1.flush();
判断读出来的对象类型
if ((obj2 = ct.ois.readObject()) instanceof Map) {
System.out.println("接收到Map");
serverMap = (Map<String, FileVersionInfo>) obj2;
}
svn的关键点就是版本控制,这里是存放文件版本信息的实体类FileVersionInfo,可以在客户端和服务器之间传递。为了在传递中不发生错误,传输过程中的类都会实现Serializable接口。
public class FileVersionInfo implements Serializable {
private static final long serialVersionUID = 1L;
public int version;
public Date modifyDate;
public String modifyUser;
public int fileStatus;
public List<FileVersionInfo> hisFileVersionInfo;
public FileVersionInfo(int version, Date modifyDate, String modifyUser,
int fileStatus, List<FileVersionInfo> hisFileVersionInfo) {
this.version = version;
this.modifyDate = modifyDate;
this.modifyUser = modifyUser;
this.fileStatus = fileStatus;
this.hisFileVersionInfo = hisFileVersionInfo;
}
public FileVersionInfo(int version, Date modifyDate, String modifyUser,
List<FileVersionInfo> hisFileVersionInfo) {
this.version = version;
this.modifyDate = modifyDate;
this.modifyUser = modifyUser;
this.hisFileVersionInfo = hisFileVersionInfo;
}
}
这里在客户端使用了一个存放文件版本信息的类,用来加载更新本地文件信息并保存。
文件信息类ClientFileInfo
public class ClientFileInfo implements Serializable {
private static final long serialVersionUID = 1L;
private static Map<String, FileVersionInfo> allFileInfo;
private static ClientFileInfo cfi;
private ClientFileInfo(Map<String, FileVersionInfo> map) {
this.allFileInfo = map;
}
public synchronized static Map<String, FileVersionInfo> getFileInfo() {
if (cfi == null) {
cfi = new ClientFileInfo(new HashMap<String, FileVersionInfo>());
}
return cfi.allFileInfo;
}
public static void serialize() throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(
new File("C:/ClientFileInfo.txt")));
oos.writeObject(ClientFileInfo.getFileInfo());
}
public static void deserialize() throws FileNotFoundException, IOException,
ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
new File("C:/ClientFileInfo.txt")));
allFileInfo = (Map) ois.readObject();
}
public static void init(String path, String modifyUser) {
System.out.println("path=" + path);
if (!"".equals(path)) {
File file = new File(path);
File[] files = file.listFiles();
FileVersionInfo fvi;
for (int i = 0; i < files.length; i++) {
long time = files[i].lastModified();
Date date = new Date(time);
// SimpleDateFormat sdf=new
// SimpleDateFormat("YYYY-mm-dd hh-MM-ss");
// String modifyDate=sdf.format(date);
List<FileVersionInfo> hisFile = new ArrayList<FileVersionInfo>();
// 初始版本为1,文件状态为提交
fvi = new FileVersionInfo(1, date, modifyUser, 2, hisFile);
hisFile.add(fvi);
fvi = new FileVersionInfo(1, date, modifyUser, 2, hisFile);
allFileInfo.put(files[i].getName(), fvi);
}
} else {
JOptionPane.showMessageDialog(null, "请输入有效路径!!");
}
}
public static void addFile(String path, String name) {
File file = new File(path);
File[] files = file.listFiles();
FileVersionInfo fvi;
for (int i = 0; i < files.length; i++) {
if (!allFileInfo.containsKey(files[i].getName())) {
long time = files[i].lastModified();
Date date = new Date(time);
// SimpleDateFormat sdf=new
// SimpleDateFormat("YYYY-mm-dd hh-MM-ss");
// String modifyDate=sdf.format(date);
List<FileVersionInfo> hisFile = new ArrayList<FileVersionInfo>();
// 初始版本为1,文件状态为提交
fvi = new FileVersionInfo(1, date, name, 2, hisFile);
hisFile.add(fvi);
fvi = new FileVersionInfo(1, date, name, 2, hisFile);
allFileInfo.put(files[i].getName(), fvi);
}
}
}
}
ServerFileInfo是用来保存服务器端文件信息的类,与客户端ClientFileInfo有点不一样,只是用来保存数据和获取,不提供修改功能。
文件状态修改类TellFileStatus
四个状态之间的转变的理解是svn的关键。
简要说下本地文件状态修改的逻辑:客户端的文件信息与服务器发来的信息做对比,与服务器相同则客户端状态改为一致,如果本地修改了文件是需要提交的,而服务器上又有其他的客户端提交过该文件,导致服务器上的该文件版本比本地大需要更新,则产生冲突,如果两者版本相同则比较它们的最后修改时间,因为可能本地修改了文件,服务器,没修改,本地最后修改时间会大于服务器,此时文件状态为提交,如果小于的话,就是有其他的客户端也提交了同名文件,修改了服务器文件的时间和版本,则文件状态要改为更新状态。
这里还有一点补充:
如果文件产生冲突,在现有主流的版本控制软件中,是有合并这一方法的,但是我这里没有解决这个冲突问题。
public class TellFileStatus {
Map m1 = ClientFileInfo.getFileInfo();
boolean flag = true;
public TellFileStatus(Map serverMap, String path) {
Set set1 = m1.keySet();
Iterator clientIter = set1.iterator();
File file = new File(path);
// 保存客户端key值
List<String> list = new ArrayList<String>();
// 保存更新状态的key值
List<String> _list = new ArrayList<String>();
while (clientIter.hasNext()) {
flag = false;
String key = (String) clientIter.next();
FileVersionInfo value = (FileVersionInfo) m1.get(key);
if (value.fileStatus == 0) {
_list.add(key);
} else
list.add(key);
}
for (int i = 0; i < _list.size(); i++) {
m1.remove(_list.get(i));
}
Set set = serverMap.keySet();
Iterator serverIter = set.iterator();
int i = 0;
while (serverIter.hasNext()) {
flag = true;
String _key = (String) serverIter.next();
FileVersionInfo _value = (FileVersionInfo) serverMap.get(_key);
System.out.println("clientkey=" + list.get(i) + ",serverkey="
+ _key);
if (_key.equals(list.get(i))) {
compare((FileVersionInfo) serverMap.get(_key),
(FileVersionInfo) m1.get(list.get(i)), path + "\\"
+ list.get(i));
break;
}
}
Set set2 = serverMap.keySet();
Iterator serverIter1 = set2.iterator();
while (serverIter1.hasNext()) {
String _key = (String) serverIter1.next();
FileVersionInfo _value = (FileVersionInfo) serverMap.get(_key);
if (!m1.containsKey(_key)) {
_value.fileStatus = 0;
m1.put(_key, _value);
}
}
// if (!flag) {// 如果服务器Map为空,迭代本地Map,文件状态为新增
// while (clientIter.hasNext()) {
// key = (String) clientIter.next();
// FileVersionInfo value = (FileVersionInfo) m1.get(key);
// New(value);// 改变文件状态
// }
// }
}
/**
* 对比客户端和服务器端文件的相关信息
*
* @param serverFileVersionInfo
* @param clientFileVersionInfo
* @param path
*/
void compare(FileVersionInfo serverFileVersionInfo,
FileVersionInfo clientFileVersionInfo, String path) {
File file = new File(path);
long time = file.lastModified();
Date lastClientmotifydate = new Date(time);
int CurrentlyServervision = serverFileVersionInfo.version;
int LocalClientvision = clientFileVersionInfo.version;
Date LocalClientmotifyDate = clientFileVersionInfo.modifyDate;
if (LocalClientmotifyDate.getTime() != lastClientmotifydate.getTime() &&LocalClientvision < CurrentlyServervision) {
conflict(clientFileVersionInfo);
// 冲突
} else {
if (LocalClientvision == CurrentlyServervision) {
System.out.println("LocalClientmotifyDate=" + LocalClientmotifyDate + ",,,lastClientmotifydate=" + lastClientmotifydate);
if (LocalClientmotifyDate.getTime() == lastClientmotifydate.getTime()) {
System.out.println("进入一致");
same(clientFileVersionInfo);// 与服务器文件一致,本地未更新,不操作
} else {
submit(clientFileVersionInfo);
}
} else {
update(clientFileVersionInfo);
}
}
}
static void same(FileVersionInfo m) {// 一致
m.fileStatus = 1;
}
void submit(FileVersionInfo m) {// 提交
m.fileStatus = 2;
}
void update(FileVersionInfo m) {// 更新
m.fileStatus = 0;
}
void conflict(FileVersionInfo m) {// 冲突
m.fileStatus = 3;
}
void New(FileVersionInfo m) {// 冲突
m.fileStatus = 4;
}
}
封装了一个消息类Message,用于告知服服务器应该发送哪种消息给客户端
public class Message implements Serializable {
private static final long serialVersionUID = 1L;
public int type;
public List<String> filename;
public Message(int type, List<String> filename) {
this.type = type;
this.filename = filename;
}
public int getType() {
return this.type;
}
public void setType(int type) {
this.type = type;
}
public List<String> getFilename() {
return this.filename;
}
public void setFilename(List<String> filename) {
this.filename = filename;
}
}