OrientShare,Java写的跨平台文件传输软件-服务端

    前两天搞的跨平台文件传输软件,仅限局域网内,功能很简单,算是个Demo吧。

主要功能:

     客户端自动上线;

     服务端自动握手链接;

     服务端向客户端进行文件同步操作;

     文件改变时自动发送。

     最小化到托盘,自动运行。

     三种平台(Win Mac Linux)兼容。

说实话,这个跨平台有点作弊的嫌疑,因为本身就是用Java写的,除了做了点判断平台的操作之外,基本没什么技术含量。

最近事情有点多,过两天整理并贴出代码。见谅。

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — — 

11月9日22:48更新

注意,这里没有讲怎么用Java的Socket进行编程,这方面的教程已经有人写了,而且肯定比我写的好,所以,这里介绍的是应用Socket、Java中的ClientSocket以及ServerSocket的小例子。🌰

 

程序分为服务端和客户端两部分。

流程如下:

  服务端有三个线程,分别为:

    BroadcastListener,广播监听线程

    SyncThread,文件同步线程

    SyncFolder,目录监视线程

  客户端有两个线程,分别是:

    BroadcastListener,广播监听线程

    SyncThread,文件同步线程

  其中界面线程不算在内,因为界面直接是NetBeans的JFormDesigner做的。😄

  服务端或者客户端启动后,通过5021端口发送上线消息,同时启动广播监听线程,监听并解析消息。如果服务端捕获到了客户端的上线消息,则进行握手并且发送本地文件。在服务端内由文件同步线程监控同步文件夹内的文件更改。如果发现了文件更改,则对当前所有已连接的客户端发送文件。

 

 

本篇先说服务端。

 

服务端在主函数中调用界面MainFrame的构造函数,启动文件同步线程SyncThread。

这里只给出了主函数,MainFrame是继承的JFrame,在构造函数中增加了点其他的初始化操作,具体是:


    增加了个托盘区的图标(Windows和Mac下可用,图标很丑。。。大家将就看吧,也许本来就不会有人来我这荒草院子~)以及响应事件;
    初始化了各组件的位置和大小;
    启动了主界面更新线程。
    注意,这个主要更新的是服务器当前状态,也就是一个字符串,下面各个类里面如果有StatusString,那就是他了~
    

 1 public static void main(String args[]) throws Exception {
 2 //NetBeans Generate Code.
 3 java.awt.EventQueue.invokeLater(new Runnable() {
 4             public void run() {
 5                 MainFrame mf;
 6                 mf = new MainFrame();
 7                 mf.setVisible(true);
 8             }
 9         });
10         sync = new SyncThread();
11         sync.start();
12

  在SyncThread的构造函数中,做了如下几件事情:

    创建服务端的UUID;构造记录用户IP与UUID的HashMap;

    构造封装好的广播发送类,BroadcastSend sender。

    判断当前操作系统的类型,启动SyncFolder线程;

  在线程的执行函数中执行如下两个操作:

    循环判断服务器当前状态;

    每隔1s发送消息;(注意,这里有个BUG。)

 

  发送广播的时候,服务端发送的是最简单的本机的UUID和消息代码,在客户端中,我们发送的广播是比较详细的。

 

  1 package widekuan;
  2 
  3 import java.io.IOException;
  4 import java.util.Iterator;
  5 import java.util.Map;
  6 import java.util.Properties;
  7 import java.util.UUID;
  8 import java.util.logging.Level;
  9 import java.util.logging.Logger;
 10 
 11 /**
 12  *
 13  * @author sunkuan
 14  */
 15 public class SyncThread extends Thread{
 16     UserMap map;
 17     BroadcastSend sender;
 18     BroadcastListenThread listener;
 19     FileShareServer client;
 20     String globalUuid;
 21     String osName;
 22     SyncFolder Folder;
 23     /*
 24      * int status:
 25      * 0:idle.
 26      * 1:online.
 27      * 2:offline.
 28      */
 29     int status;
 30     String FileName;
 31     public int osFlag;
 32     public String StatusString;
 33     
 34     public SyncThread() throws Exception{
 35         map = new UserMap();
 36         globalUuid = UUID.randomUUID().toString();//length:36.
 37         sender = new BroadcastSend(map,this);
 38         /*
 39          * 3:Server is online.
 40          */
 41         globalUuid = globalUuid.concat("3");
 42         sender.SendMessage(globalUuid);
 43         /*
 44          * osFlag:
 45          * 0:Mac
 46          * 1:Linux
 47          * 2:Win
 48          */
 49         Properties props=System.getProperties();
 50         osName = props.getProperty("os.name");
 51         System.out.println(osName);
 52         if(osName.startsWith("Mac")){
 53             FileName = "DFT/Send/";  
 54         }else if(osName.startsWith("Linux")){
 55             FileName = "DFT/Send/";
 56         }else if(osName.startsWith("Win")){
 57             FileName = "DFT\\Send\\";
 58         }
 59         Folder = new SyncFolder(FileName,this);
 60         Folder.start();
 61         status = 0;
 62         this.listener = sender.listener;
 63         StatusString = "Initialize.";
 64     }
 65     
 66     @Override
 67     public void run(){
 68         while(true){
 69             switch(status){
 70                 case 0:
 71                     //idle.
 72                     StatusString = "Waiting Client online.";
 73                     break;
 74                 case 1:
 75                     //online.
 76                     StatusString = "Client online";
 77                     Iterator iterIPID = map.MapIPID.entrySet().iterator(); 
 78                     
 79                     //The MapIPID has NO next entry,but the folder is changed. 
 80                     while (iterIPID.hasNext()){
 81                         Map.Entry entryIPID = (Map.Entry) iterIPID.next();
 82                         
 83                         for(int i = 0;i < Folder.fileList.size();i++){
 84                                 //Creat a new Server.
 85                             try {
 86                                 StatusString = "Connecting Client @ "+entryIPID.
 87                                         getValue().toString();
 88                                 client = new FileShareServer(entryIPID.getValue().toString());
 89                                 StatusString = "Connected Client @ "+entryIPID.getKey().toString();
 90                                 client.UUID = entryIPID.getKey().toString();
 91                                 StatusString = "Get respons from client @"+entryIPID.getValue().toString();
 92                                 client.getRespond(Folder.fileList.get(i).getAbsolutePath());
 93                                 //Must have a little nap here.
 94                                 //Or the AcceptFlag can NOT work correctly.
 95                                 Thread.sleep(1000);
 96                             } catch (Exception ex) {
 97                                 Logger.getLogger(SyncThread.class.getName()).log(Level.SEVERE, null, ex);
 98                             }
 99                             StatusString = "Waiting for server accept file.";
100                         while(!listener.respondFlag);
101                         if(!listener.AcceptFlag){
102                             //Wait the client to repond.
103                             //If ANY respond recieved,it won't be online here.
104                             StatusString = "Client refused file.";
105                             client.closeServer();
106                             StatusString = "Client @ "+entryIPID.getValue().toString()+"rejected file "+Folder.fileList.get(i).getName();
107 //                            System.out.println("Rejected.");
108                             continue;
109                            
110                         }else{
111                             
112                             StatusString = "Client @ "+entryIPID.getValue().toString()+"accepted file "+Folder.fileList.get(i).getName();
113 //                            System.out.println("Client Accept.");
114                             try {
115                                 StatusString = "Sending file to Client @ "+entryIPID.getValue().toString()+"File name:"+Folder.fileList.get(i).getName();
116                                 client.Send(Folder.fileList.get(i).getAbsolutePath());
117                                 client.closeServer();
118                                 } catch (IOException ex) {
119                                     Logger.getLogger(SyncThread.class.getName()).log(Level.SEVERE, null, ex);
120                                     }
121                                 /*
122                                  * Make the flag flase here.
123                                  * So the next file we need to check that bitch again.
124                                  * And that's MAYBE make sense.
125                                  */
126                                 listener.AcceptFlag = false;
127                                 listener.respondFlag = false;
128                         }
129                             
130                             }
131                     }
132                     Folder.isChanged = false;
133                     System.out.println("All Done.");
134                     status = 0;
135                     break;
136                 case 2:
137                     //offline.
138                     
139                     break;
140             }
141             try {
142                 StatusString = "Broadcasting online message.";
143                 Thread.sleep(1000);
144                 sender.SendMessage(globalUuid);
145             } catch (Exception ex) {
146                 System.out.println(ex.getMessage());
147             }
148         }
149     }
150 
151 }

 

  下面是BroadcastSend类,主要负责发送消息,因为服务端需要发送的消息比较少,因此只对这个类进行了最简单的封装。

  

 1 package widekuan;
 2 
 3 import java.io.IOException;
 4 import java.net.DatagramPacket;
 5 import java.net.DatagramSocket;
 6 import java.net.InetAddress;
 7 
 8 /**
 9  *
10  * @author sunkuan
11  */
12 public class BroadcastSend {
13     public DatagramSocket server;// UDP
14     private InetAddress broadcastAddress;//Broadcast Addr.
15     private int port;// Listen port.
16     BroadcastListenThread listener;
17     /**
18      * 
19      * @param UserMap,contains the users' UUID and IP address.
20      */
21     public BroadcastSend(UserMap usermapArg,SyncThread syncArg){
22         try {
23             port = 5021;
24             server = new DatagramSocket(port);
25             broadcastAddress = InetAddress.getByName("255.255.255.255");
26             if(listener == null)
27             {
28                 listener = new BroadcastListenThread(server,usermapArg,syncArg);
29                 listener.start();
30             }
31         } catch (Exception ex) {
32             System.err.println("Socket Connection FAIL");
33         }
34     }
35     public void sendBroadcast(byte[] data, InetAddress address) {
36         try {
37             DatagramPacket dp = new DatagramPacket(data, data.length, address, port);
38             server.send(dp);
39         } catch (IOException ex) {
40             System.err.println("Send Broadcast FAIL.");
41         }
42     }    
43     /**
44      * @param Message need to send.
45      * @return void.
46      * @exception Exceptions.
47      */
48     public void SendMessage(String msgArg) throws Exception{
49         //String sendMSG = new String("Fuck!");
50         sendBroadcast(msgArg.getBytes(), broadcastAddress);
51         
52     }
53 }

 

  文件同步类,SyncFolder,通过文件夹的修改时间来判断其中文件是否有变化。

 1 package widekuan;
 2 
 3 import java.io.File;
 4 import java.util.ArrayList;
 5 
 6 /**
 7  *
 8  * @author sunkuan
 9  */
10 public class SyncFolder extends Thread{
11     File file;
12     ArrayList<File> fileList;
13     File[] fileArray;
14     String FolderName;
15     long lastmodifiedOld;
16     boolean isChanged;
17     SyncThread syncThread;
18     public SyncFolder(String FolderNameArg,SyncThread syncThreadArg){
19         FolderName = FolderNameArg;
20         file = new File(FolderName);
21         lastmodifiedOld = file.lastModified();
22         //ScanFile(FolderName);
23         isChanged = false;
24         syncThread = syncThreadArg;
25         
26         if(file.isDirectory()){
27            fileArray = file.listFiles();
28         }else{
29             System.err.println("The file name is not a FOLDER!");
30         }
31         fileList = new ArrayList<File>();
32         ScanAllFile(file);
33         lastmodifiedOld = file.lastModified();
34     }
35     /*
36      * Don't use this.
37      */
38     private void ScanFile(String PathArg){
39         File searchFile = new File(PathArg);
40         if(searchFile.isDirectory()){
41            fileArray = file.listFiles();
42         }
43         fileList = new ArrayList<>();
44         for(int i = 0;i < fileArray.length;i++){
45             if(fileArray[i].getName().startsWith(".")){
46                 continue;
47             }
48             if(fileArray[i].isDirectory()){
49                 ScanFile(fileArray[i].getName());
50             }
51             fileList.add(fileArray[i]);
52             System.out.println(fileArray[i].getName());
53         }
54     }
55     private void ScanAllFile(File fileArg){
56         File[] foo;
57         /*
58          * We must guarantee the arg is a folder.
59          */
60         foo = fileArg.listFiles();
61         for(int i = 0;i <foo.length;i++){
62             if(foo[i].isDirectory()){
63                 ScanAllFile(foo[i]);
64             }else{
65                 if(!foo[i].getName().startsWith(".")){
66                     if(!fileList.contains(foo[i])){
67                         fileList.add(foo[i]);
68                         System.out.println(foo[i].getAbsoluteFile());
69                     }else{
70                         continue;
71                     }
72                 }
73             }
74         }
75     }
76     @Override
77     public void run(){
78         while(true){
79             try {
80                 Thread.sleep(5000);
81             } catch (InterruptedException ex) {
82                 continue;
83             }
84             if(lastmodifiedOld != file.lastModified()){
85                 //File modify detected.
86                 ScanAllFile(file);
87                 lastmodifiedOld = file.lastModified();
88                 System.err.println("File Changed.");
89                 isChanged = true;
90                 syncThread.status = 1;
91             }else{
92                 //No motified files.
93                 //Do NOTHING here.Modify the boolean in the sync Thread.
94                 
95             }  
96             
97         }
98     }
99 }

  万事具备,只欠客户端发来的上线消息了~所以我们需要一个监听客户端发来消息的东东,BroadcastListener。

  客户端发来的消息都是明码,所以这个代码也就很浅显易懂了。

  

  1 /*
  2  * To change this template, choose Tools | Templates
  3  * and open the template in the editor.
  4  */
  5 package widekuan;
  6 
  7 import java.net.DatagramPacket;
  8 import java.net.DatagramSocket;
  9 import java.net.InetAddress;
 10 import java.util.HashMap;
 11 
 12 
 13 public class BroadcastListenThread extends Thread{
 14 
 15     private DatagramSocket server;
 16     public UserMap userMap;
 17     public HashMap<String,Integer> respondMap;
 18     private SyncThread sync;
 19     String msg;
 20     DatagramPacket dp;
 21     boolean DoneFlag;
 22     boolean respondFlag;
 23     boolean AcceptFlag;
 24     InetAddress RespondIP;
 25     public BroadcastListenThread(
 26             DatagramSocket serverArg, 
 27             UserMap usermapArg,SyncThread syncArg) throws Exception{
 28             server = serverArg;
 29             userMap = usermapArg;
 30             sync = syncArg;
 31             AcceptFlag = false;
 32             DoneFlag = false;
 33             respondFlag = false;
 34             RespondIP = null;
 35             
 36     }
 37     
 38     @Override
 39     public void run() {
 40         dp = new DatagramPacket( new byte[1024], 1024);
 41                 while (true) {
 42                     try {
 43                             server.receive(dp);
 44                             //decode broadcast pakage.
 45                             msg = new String(dp.getData(),
 46                                                     0, dp.getLength(), "UTF-8");
 47                     } catch (Exception e) {
 48                         msg = "";
 49                     }
 50                     if(msg.endsWith("online") && !userMap.MapIPID.containsKey(msg)){
 51                         sync.status = 1;
 52                         userMap.add(msg, dp.getAddress());
 53                         System.out.printf("Client count is %d \n",
 54                                                             userMap.MapIPID.size());
 55                         //continue;
 56                     }else if(msg.endsWith("online") && userMap.MapIPID.containsKey(msg)){
 57                         //Client send a useless online message.
 58                         //continue;
 59                     }else if(msg.length()<36||msg.length() == 36){
 60                         //No client here.
 61                         sync.status = 0;
 62                         continue;
 63                     }else if(msg.endsWith("offline")){
 64                         System.out.println("Client offline.");
 65 //                        userMap.remove(msg,dp.getAddress());
 66 //                        if(userMap.MapIPID.isEmpty()){
 67 //                            sync.status = 2;
 68 //                        }
 69                         
 70                         //Modify the HashMap .
 71                         //So the server won't send file to it.
 72                         //The orignal version has a wrong String.
 73                         userMap.remove(msg.replaceAll("offline", "online"), RespondIP);
 74                         continue;
 75                     }else if(msg.endsWith("accept")){
 76                         //Client Accept the file.
 77                         //System.out.println(msg);
 78                         AcceptFlag = true;
 79                         RespondIP = dp.getAddress();
 80                         respondFlag = true;
 81                         continue;
 82                     }else if(msg.endsWith("reject")){
 83                         AcceptFlag = false;
 84                         RespondIP = dp.getAddress();
 85                         respondFlag = true;
 86                         continue;
 87                     }else if(msg.endsWith("done")){
 88                         DoneFlag = true;
 89                         respondFlag = false;
 90                         AcceptFlag = false;
 91                         continue;
 92                     }else if(msg.endsWith("done")){
 93                         DoneFlag = true;
 94                         respondFlag = false;
 95                         AcceptFlag = false;
 96                         continue;
 97                     }
 98                 }
 99         }
100     
101 }

 

  由BroadcastListener的功能可知,这个线程只是根据客户端发来的消息更改了几个“Flag”变量,那么,这几个“Flag”变量是怎么被应用的呢?让我们回头看下SyncThread 的执行函数。我们会发现,在case 1(客户端上线)中,有两个个判断。首先是判断当前有木有客户端连接上,如果木有,则继续等待;如果有,则循环发送文件列表中的所有文件。发送文件的过程中,则又有一个问题。如果客户端不想要这个文件怎么办呢?我们不能强塞给人家啊,所以就需要一个AcceptFlag。服务端首先发送的是文件名以及文件大小(当然这个完全可以用MD5和Hash值替代啊~),然后由客户端判断这个文件是不是我们所要的。如果文件重复的话(光靠文件名和大小是不能判断重复的!这里只是做个演示啊!),则拒绝该文件,这时服务端的AcceptFlag当然是false。如果客户端接受文件的话,AcceptFlag显然就为true了,我们就可以调用FlieShareServer去发送文件了。当然,不管是客户端拒绝还是接受,总得给个回应是吧,这就是respondFlag的用处,首先先判断下客户端是否发送回应给我们。这个标识位是用于扩展程序功能以及防止丢包的。

  那么,FileShareServer是怎么发送文件的呢?下面的代码就是答案。

 1 package widekuan;
 2 
 3 import java.io.BufferedInputStream;
 4 import java.io.BufferedOutputStream;
 5 import java.io.DataInputStream;
 6 import java.io.DataOutputStream;
 7 import java.io.File;
 8 import java.io.FileInputStream;
 9 import java.io.IOException;
10 import java.net.ServerSocket;
11 import java.net.Socket;
12 
13 /**
14  *
15  * @author sunkuan
16  */
17 public class FileShareServer {
18     private String SERVER_IP;
19     private static final int SERVER_PORT = 5020;
20      
21     private ServerSocket server;
22     private Socket client;
23     //private DataInputStream dis;
24     private DataInputStream fis;
25     private DataOutputStream dos;
26     public int osFlag;
27     String UUID;
28     public float progress;
29     public FileShareServer(String IPArg) throws IOException{
30         SERVER_IP = IPArg.substring(1, IPArg.length());
31         //System.out.println("try to connect server.");
32         if(server == null){
33             server = new ServerSocket(SERVER_PORT);
34         }
35         //System.out.println("Connected.");
36     }
37     
38     
39     public void Send(String FileNameArg) throws IOException{
40         File carbageFile = new File(FileNameArg);
41         //dis = new DataInputStream(new BufferedInputStream(client.getInputStream()));
42         fis = new DataInputStream(new BufferedInputStream(new FileInputStream(FileNameArg)));
43         //dos = new DataOutputStream(client.getOutputStream());
44         dos = new DataOutputStream(new BufferedOutputStream(client.getOutputStream()));
45         //Send the File name and length again?
46 //        dos.writeUTF(carbageFile.getName());
47 //        dos.flush();
48 //        dos.writeLong((long) carbageFile.length());
49 //        dos.flush();
50         int bufferSize = 8192;
51         byte[] buffer = new byte[bufferSize];
52         while (true) {
53             int read = 0;
54             if (fis != null) {
55                 read = fis.read(buffer);
56             }
57             if (read == -1) {
58                 break;
59             }
60             dos.write(buffer, 0, read);
61             progress += (float) ((bufferSize/carbageFile.length())*100);
62         }
63         dos.flush();
64         fis.close();
65         client.close();
66         server.close();
67         System.out.println("Done.");
68     }
69     
70     /*
71      * Send the filename and length,and wait for the client to response
72      * in the SyncThread.
73      */
74     public void getRespond(String FileNameArg) throws IOException{
75         File carbageFile = new File(FileNameArg);
76         client = server.accept();
77         fis = new DataInputStream(new BufferedInputStream(new FileInputStream(FileNameArg)));
78         dos = new DataOutputStream(new BufferedOutputStream(client.getOutputStream()));
79         dos.writeUTF(carbageFile.getName());
80         dos.flush();
81         dos.writeLong((long) carbageFile.length());
82         dos.flush();
83     }
84     public void closeServer(){
85         try {
86             server.close();
87         } catch (IOException ex) {
88             System.err.println("Server Closed ERROE");
89         }
90     }
91 }

  我们可以看到,在getRespond这个函数中我们只发送了文件名和大小,然后交给客户端去判断。实际上,这个地方改称哈希值要更好些。

 

  对了,上文中的UserMap就是个HashMap,之所以封装一下是为了以后拓展一些功能。

 

  服务端到这里基本就结束了,Quite simple~?

 

PS.第一次在博客园发文章,写的不好,见谅。之前主页里面还有一些东西,不过空间都已经删了,而且自己的地盘,写起来总是有点肆无忌惮,不提了。

PPS.最近复习的跟shi一样。。。也不是没有努力,只是感觉自己好笨。数学学不好也许只有一辈子当码农了,玩不了什么高级的东西了。

PPPS.客户端的代码近期内贴出来,并且最后会打包发到pudn上。

 
 
 
 
 

posted on 2013-11-01 16:24  widekuan  阅读(523)  评论(0编辑  收藏  举报

导航

2013    UESTC    EE    SunKuan.