[Android]Android 製作一個HTTP服務器應用 [Android]Android 製作一個HTTP服務器應用

[Android]Android 製作一個HTTP服務器應用

思考過程

HTTP 服務器就像Apache 服務器那樣的,不過不需要做那麼多的功能。

既然是HTTP 服務器那就應該遵守HTTP 協議唄,不過學習網絡編程時用的時socket,並且以爲socket 是一個協議,那這個HTTP 協議也應該有一個類似HTTPServerHTTP 的類唄。

但是,不是的,查了HTTP 之後,好多的文章都在說socket 不是一個協議,只是編程接口,只有HTTP 纔是協議。嗯,好吧。而且還說的是socket是傳輸層的,負責信息傳輸,HTTP 是應用層的,負責決定發送什麼樣的內容。哦,那也就是說應該使用socket 實現HTTP 協議。

發送數據使用socket,被髮送的數據要遵守HTTP 的規則,畢竟是應用層,我們也可以不遵守(不過不遵守瀏覽器就不幹了)。

編碼過程

  1. 先創建一個空的Android 項目。

  2. MainActivity 類中添加如下代碼。

    
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    ServerSocket serverSocket = new ServerSocket(8080);
                    Socket socket = serverSocket.accept();
                    socket.getOutputStream().write(("HTTP/1.1 200 OK\n" +
                            "Date: "+new Date().toString()+"\n" +
                            "Content-Type: text/html; charset=UTF-8\n" +
                            "\n" +
                            "<html>\n" +
                            "      <head></head>\n" +
                            "      <body>\n" +
                            "            <p>hello world</p>\n" +
                            "      </body>\n" +
                            "</html>").getBytes(StandardCharsets.UTF_8));
    
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    

    代碼非常簡單,socket 都沒有關閉,僅僅演示這個功能。如果添加對瀏覽器請求進行處理的功能,然後傳回相應的信息,然後丟掉這個socket 對象,好像就是一個服務器了哦。

    剛開始在write 函數裏只輸出“hello world”,無效,瀏覽器加載失敗,想到需要添加一些信息,就像做web開發時的response一樣。就是現在的這個樣子,最後面是html代碼。想要了解更多信息建議找更多的資料。

    時間那裏不知道有沒有嚴格限制,反正現在在瀏覽器中還沒有問題。

    現在想想瀏覽器也不難啊,只要我能把內容渲染出來🤩。

後續

  1. 爲了獲取request 數據,使用InputStream 進行讀取,但是出現了一個問題
    雖說我在做這個的時候有點主觀想法吧,用了一個while 循環,就像讀取文件似的,然後瀏覽器就開始沒有響應了,去掉循環就沒有問題了,request 對象長度400多,用1024 字節數組讀取沒有什麼問題,但是還是不太理解。後面增加的想法:可能這個流怎麼讀都不說自己的流到頭了,畢竟還要上傳文件呢。

    最後,這個文章後續可能會更新,比如遇到了什麼坑。

    更好的辦法是用Scanner 獲取請求頭,畢竟真的遇到了一次讀取的內容超過1204;

    Scanner scanner=new Scanner(inputStream);
    String request=scanner.useDelimiter("\\r\\n\\r\\n").next();
    

    因爲一個請求頭的結尾就是\r\n(從MDN 上學的)。

  2. 如果僅支持HTTP 的話,服務器無法主動傳送數據給瀏覽器,爲了能夠主動傳數據,再增開一個WebSocket 的服務器,用的是MDN 提供給的demo代碼

        const socket = new WebSocket('ws://' + location.hostname + ':8081');
        // Connection opened
        socket.addEventListener('open', function(event) {
            console.log('open', event);
        });
        // Listen for messages
        socket.addEventListener('message', function(event) {
            console.log('Message from server ', event);
        });
        socket.addEventListener('close', function(e) {
            console.log('close', e);
        });
        socket.addEventListener("error", function(e) {
            console.log(e);
        });
    
    

    上面的代碼和原來的稍有不同。
    服務器的代碼就不貼了,因爲有問題。問題是,連接是能夠連接上,但是連接上之後節立馬斷開,找了很多問答資料都解決不了,所以換了一個開源的集成的java 庫,這個庫雖然不會立馬斷開,但是有時候根本連接不上,有時候一刷新就會連接失敗,後來想着這個功能我是玩不通了,就想着連接不上的時候給用戶提示,就添加了try catch發現基本上連接都可以成功,不再失敗了,雖然問題解決但也是一個謎題(我用的開源庫是Java-WebSocket,用起來很無腦,不貼代碼了)。

    還有就是try catch 其實用的不對,因爲這個好像不是同步代碼,是捕獲不到的。

    還有要注意的是socket 回調函數的event 裏的錯誤碼,基本上不要參考這個玩意,很可能只會浪費自己的時間,當然也有可能是我沒有找對方法。

  3. 畢竟上面的寫法不太正確,而且後面又出現了無法連接的情況了,還是像以前一樣連接失敗,然後我就放棄了使用第三方庫的想法,決定自己解決這個問題。

    需要解決的問題就是連接之後立馬關閉的問題。

    首先我以爲是因爲一個循環結束後,對象被回收了,就開了線程,發現不行。然後在一個循環結束時查看Socket的關閉狀態,是關閉狀態,然後就想到了流的問題,把關閉輸入流的代碼去除(原MDN 上的代碼就是關閉了流的),socket 就不再關閉,問題解決。MDN Writing a WebSocket server in Java

    瀏覽器端發送一個“hello” 的字符串,在服務端獲得數據是
    -127 -123 79 -45 -102 62 39 -74 -10 82 32,直接轉換成字符串沒有任何意義,都是一些亂碼,因爲發過來的是幀數據 MDN Writing WebSocket servers

        Frame format:  
    ​​
        0                   1                   2                   3
        0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
        +-+-+-+-+-------+-+-------------+-------------------------------+
        |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
        |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
        |N|V|V|V|       |S|             |   (if payload len==126/127)   |
        | |1|2|3|       |K|             |                               |
        +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
        |     Extended payload length continued, if payload len == 127  |
        + - - - - - - - - - - - - - - - +-------------------------------+
        |                               |Masking-key, if MASK set to 1  |
        +-------------------------------+-------------------------------+
        | Masking-key (continued)       |          Payload Data         |
        +-------------------------------- - - - - - - - - - - - - - - - +
        :                     Payload Data continued ...                :
        + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
        |                     Payload Data continued ...                |
        +---------------------------------------------------------------+
    

    每一個都是一位,一個字節是八位,所以讀取第一個字節就是“fin rsv1,rsv2,rsv3 opcode”,下一個字節就是“mask payload len”了,後面“payload Data”是我們發送的信息(上面有一個方框什麼都沒有寫其實數據依然是Extend payload length continued),剩下的內容MDN 上寫的很清楚,如果想要了解更多信息,點擊上面的鏈接。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章