1. 程式人生 > >JAVA實踐專案---樹莓派資訊自動化採集後入庫專案(二)

JAVA實踐專案---樹莓派資訊自動化採集後入庫專案(二)

專案原始碼可訪問我的github:https://github.com/Spacider/Gather-and-store
如果覺得好的話請給個star哦~

專案初始

開發IDE: IDEA 2018.03 JDK 1.8
開發環境: macOS 10.13.6 (如windows請對專案中部分路徑進行改寫)
資料庫: Oracle 11g


正式開發

  1. 用IDEA建立一個maven專案,這個百度都可以找到,我在這裡就不細講了。
  2. 解決專案所有的依賴jar包,在專案下的pom.xml檔案中新增如下內容:
 <dependencies>
        <dependency>
            <groupId>com.oracle</groupId>
            <artifactId>ojdbc6</artifactId>
            <version>11.2.0.3</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.39</version>
        </dependency>
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-dbcp2</artifactId>
            <version>2.0.1</version>
        </dependency>
        <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>1.6</version>
        </dependency>
    </dependencies>

第一階段資料的採集

在這裡插入圖片描述
這裡是我們向樹莓派獲取溫度和溼度的模組示意圖,通過XML檔案的互動就可以獲得我們需要的資料,獲得到資料以後我們拆分得到的XML 並向log檔案中存入我們解析好的資料!

首先,模組結構:
在這裡插入圖片描述
然後開始編寫程式碼:

  1. 我們建立一個包為Server,然後建立一個類DataServer,這個類的作用是用來模擬樹莓派系統的,他的主要職能是接收XML檔案,並返回對應的資料,這裡是模擬,所以我們每次都返回一個相同的資料用於測試。

我們先定義個Thread類,以便於配合 while 語句來讓Server端一直保持接收資料的狀態。通過構造器將Socket物件傳入Thread類中,通過Socket物件我們就可以對獲取對應的流對接收與傳送進行控制!

class ServerThread extends Thread{
	private Socket socket;
    public ServerThread(Socket socket){
        this.socket = socket;
    }
     @Override
    public void run() {
    }
}

接下來我們的認識就是重寫run方法,我們需要接收客戶端發來的XML檔案,由於傳送過程中每一行都有一個換行符,所以巢狀一個BufferedReader物件時,我們就可以通過readLine()方法來根據行讀取檔案,末尾結束的標誌是</Message>

is = socket.getInputStream();
br = new BufferedReader(new InputStreamReader(is));
String str = null;
StringBuilder sb = new StringBuilder();
while ((str = br.readLine()) != null) {
    sb.append(str + "\r\n");
    // 如果讀取到最後一行,則退出
    if (str.trim().equals("</Message>")) {
        break;
    }
}

為了關閉資源方便,我們用到了關閉資源輔助類:
建立util包,建立IOUtil方法。

public class IOUtil {
    public static void close(Closeable... closeableList) {
        try {
            for (Closeable closeable : closeableList) {
                if (closeable != null) {
                    closeable.close();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

讀取到XML檔案時,我們就需要對相應的Client端返回一個新的XML檔案,為了方便我們編寫一個SAXReaderHelper類來協助我們的操作,編寫一個方法BackXml來進行相關的處理。該方法通過解析從客戶端發來的xml檔案,獲取SensorAddress值來模擬感測器的選擇,從而返回不同的模擬資料。

  • sensorAddress 為 16 採集到的資料為10位,前8位為溼度和溫度資料,後兩位為序號。
  • sensorAddress 為 256 採集到的資料為6位,前四位為光照資料,後兩位為序號。
  • sensorAddress 為 1280 採集到的資料為6位,前四位為二氧化碳資料,後兩位為序號。
 /**
     * 根據不同的 SensorAddress 返回不同的XML
     * @param bris
     */
    public static String BackXml(String sb){
        ByteArrayInputStream bais = null;
        // 把客戶端傳過來的 xml 轉化為字串儲存
        String TotalStr = sb.toString();
        byte[] TotalBytes = TotalStr.getBytes();
        bais = new ByteArrayInputStream(TotalBytes);
        SAXReader reader = new SAXReader();
        Document document = null;
        String BackStr = null;
        // 獲取根節點
        try {
            document = reader.read(bais,"utf-8");
            Element Message = document.getRootElement();
            String str = null;
            // 獲取結點 SensorAddress 的值
            String SensorAddress =  Message.element("SensorAddress").getText();
            if (SensorAddress.equals("16")){
                str = "5d606f7802";
                BackStr = getBakXml(str);
            }else if (SensorAddress.equals("256")){
                str = "5d6002";
                BackStr = getBakXml(str);
            }else if (SensorAddress.equals("1280")){
                str = "5d6002";
                BackStr = getBakXml(str);
            }
        } catch (DocumentException e) {
            System.out.println("返回XML檔案失敗");
        } finally {
            IOUtil.close(bais);
        }
        return BackStr;
    }
    /**
     * 拼接返回的 XML (提供模擬的 Server 端使用)
     * @param DataStr
     * @return
     */
    public static String getBakXml(String DataStr){
        String str = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"+
                "<Message>\r\n"+
                    "<SrcID>100</SrcID>\r\n"+
                    "<DstID>101</DstID>\r\n"+
                    "<DevID>2</DevID>\r\n"+
                    "<SensorAddress>0</SensorAddress>\r\n"+
                    "<Counter>0</Counter>\r\n"+
                    "<Cmd>3</Cmd>\r\n"+
                    "<Data>"+DataStr+"</Data>\r\n"+
                    "<Status>1</Status>\r\n"+
                "</Message>\r\n";
        return str;
    }

在封裝了方法以後,我們就可以一行解決問題:

// 根據不同的 SensorAddress 返回不同的XML
String BackStr = SAXReaderHelper.BackXml(sb.toString());

最後通過輸出流再次將封裝好的模擬資料傳送給Client

os = socket.getOutputStream();
pw = new PrintWriter(os);
pw.write(BackStr.toCharArray());
pw.flush();

最後關閉資源~這樣我們就編寫好了一個模擬的樹莓派Server端。通過while(true)寫死迴圈,這樣就可以一直不停的接收資料!

ServerSocket server = new ServerSocket(8888);
while (true){
   Socket socket = server.accept();
   new ServerThread(socket).start();
}

  1. Client端編寫
    Client端是傳送XML檔案到Server端的,其中還是用流進行傳輸。

首先我們編寫ClientReceiveHelper類開進行輔助我們操作。

這是拼接傳送的XML檔案的方法!

    /**
         * 拼裝傳送的 XML 檔案
         * @param SensorAddress
         * @param counter
         * @return
         */
        private final static String XmlToSend(String SensorAddress,String counter){
            String str = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"+
                    "<Message>\r\n"+
                    "<SrcID>100</SrcID>\r\n"+
                    "<DstID>101</DstID>\r\n"+
                    "<DevID>2</DevID>\r\n"+
                    "<SensorAddress>"+SensorAddress+"</SensorAddress>\r\n"+
                    "<Counter>"+counter+"</Counter>\r\n"+
                    "<Cmd>3</Cmd>\r\n"+
                    "<Status>1</Status>\r\n"+
                    "</Message>\r\n";
            return str;
        }

客戶端通過的是TCP/IP向Server端傳送訊息。
然後編寫傳送訊息的方法ClientGetXml

public final static void ClientGetXml(String SensorAddress,int counter){
}

通過Socket來開啟一個連線

 socket = new Socket("127.0.0.1", 8888);

加鎖:考慮到一種特殊情況,socket交替執行時可能會出現時間片用完的情況,這種情況下可能會導致上一個socket的資料傳輸給了下個socket使用,導致資料的錯誤!
通過傳送XML讓Server返回一個XML檔案給我們,本階段我們以獲取到了上文Server傳送的XML檔案為正確。

synchronized (socket) {
       os = socket.getOutputStream();
       pw = new PrintWriter(os);
       String str = XmlToSend(SensorAddress, counter + "");
       pw.write(str.toCharArray());
       pw.flush();

       is = socket.getInputStream();
       br = new BufferedReader(new InputStreamReader(is));
       String Backstr = null;
       // 拼接讀取返回的 XML 檔案
       StringBuilder sb = new StringBuilder();
       while ((Backstr = br.readLine()) != null) {
           // 在末尾加入 \r\n 方便觀察,也方便讀取
           sb.append(Backstr + "\r\n");
           if (Backstr.trim().equals("</Message>")) {
               break;
           }
       }
}

編寫到這裡, 你可以列印一下BackStr,看下是否發生了死鎖(可能未關閉資源或連線未斷開等)和傳輸到的資料是否正確。

經過如上的編寫,你就可以編寫Client 如:
方法中的引數為SensorAddress的值(判斷溫度溼度或光照或二氧化碳),和counter操作的感測器個數!ClientGetXml就是上文我們編寫的Client的操作方法。

public final class GuangClient {
    public static void guangGetObj(){
        ClientReceiveHelper.ClientGetXml("256",1);
    }
}

在這裡插入圖片描述

如果你已經獲得了Server端傳送的資料,那麼恭喜你,你這一階段的編寫已經取得了成功,可以進行下一階段的編寫!

本文中為程式碼詳解,可能與原始碼不一定相同,可以檢視我的github:https://github.com/Spacider/Gather-and-store 檢視原始碼與你的程式碼進行比對!如果覺得我寫的好的話請給一個star哦!