[Android]Android 製作一個HTTP服務器應用
思考過程
HTTP 服務器就像Apache 服務器那樣的,不過不需要做那麼多的功能。
既然是HTTP 服務器那就應該遵守HTTP 協議唄,不過學習網絡編程時用的時socket,並且以爲socket 是一個協議,那這個HTTP 協議也應該有一個類似HTTP
和 ServerHTTP
的類唄。
但是,不是的,查了HTTP 之後,好多的文章都在說socket 不是一個協議,只是編程接口,只有HTTP 纔是協議。嗯,好吧。而且還說的是socket是傳輸層的,負責信息傳輸,HTTP 是應用層的,負責決定發送什麼樣的內容。哦,那也就是說應該使用socket 實現HTTP 協議。
發送數據使用socket,被髮送的數據要遵守HTTP 的規則,畢竟是應用層,我們也可以不遵守(不過不遵守瀏覽器就不幹了)。
編碼過程
先創建一個空的Android 項目。
-
在
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代碼。想要了解更多信息建議找更多的資料。時間那裏不知道有沒有嚴格限制,反正現在在瀏覽器中還沒有問題。
現在想想瀏覽器也不難啊,只要我能把內容渲染出來🤩。
後續
-
爲了獲取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 上學的)。 -
如果僅支持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
裏的錯誤碼,基本上不要參考這個玩意,很可能只會浪費自己的時間,當然也有可能是我沒有找對方法。 -
畢竟上面的寫法不太正確,而且後面又出現了無法連接的情況了,還是像以前一樣連接失敗,然後我就放棄了使用第三方庫的想法,決定自己解決這個問題。
需要解決的問題就是連接之後立馬關閉的問題。
首先我以爲是因爲一個循環結束後,對象被回收了,就開了線程,發現不行。然後在一個循環結束時查看
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 上寫的很清楚,如果想要了解更多信息,點擊上面的鏈接。