Android 如何搭建一個區域網路 Web 伺服器
前言
在寫部落格之前我已經做了一個
Android Http Server
的開源項目,我把它取名叫
AndServer
,
AndServer
源代碼托管在Github:https://github.com/yanzhenjie/AndServer。
AndServer
是
Android Http Server
的簡寫,顧名思義AndServer是Android端Http伺服器的一個項目。
目的在于在Android可以很友善的搭建Http伺服器,這幾天有人問我區域網路Client和Client通信的時候有時候用什麼技術比較好,其實我想到的是Socket和Http,我們知道Http是基于Socket的,是以它是一個非常成熟的Socket,是以我選擇了用Http來實作,今天的部落格内容也是主要講Android端如何搭建一個Http伺服器。
AndServer如何引用
如果不想研究原理,隻是想快速解決項目中的問題的同學,直接依賴
AndServer
,具體用法往下看,或者從Github上下載下傳AndServer的Demo。這裡給出AndroidStudio和Eclipse的使用方式:
- Eclipse使用Jar包,如果需要依賴源碼,請自行下載下傳。
下載下傳Jar包
- AndroidStudio使用Gradle建構添加依賴(推薦)
Android端用什麼技術實作Http伺服器
ApacheHttpCore
是一個優秀的Http底層架構,支援建構伺服器,支援建構用戶端,是以我們第一個版本選擇了Apache的
HttpCore
,因為Android棄用了
ApacheHttpClient
相關API,代碼中會有棄用的警告,不過這一點大家不要擔心,下面會給出解決方案。
Android棄用了HttpClient後怎麼繼續使用HttpClient
Android6.0之後SDK中删除
HttpClient
相關的API,我看了Google的官方文檔後提示我們,如果還想繼續使用
HttpClient
的話:
如果你的SDK下沒有 org.apache.http.legacy.jar
的話到這裡下載下傳。
方案一:AndroidStuid主module的gradle中配置:
android {
useLibrary ‘org.apache.http.legacy‘
}
如果提示編譯不過的話,需要在
android-sdk-windows\platforms\android-23\optional
下檢查有沒有以下兩個檔案:
optional.json
org.apache.http.legacy.jar
方案二:如果你使用的是Eclipse
拷貝
android-sdk-windows\platforms\android-23\optional
下的org.apache.http.legacy.jar到你項目的libs下就完結。
方案三:下載下傳Apache的jar包(不推薦)
從Apache官網下載下傳
HttpClient
和
HttpCore
的jar包導入到項目。位址是:http://hc.apache.org/downloads.cgi。
但是我推薦方案一和方案二,因為AndroidSDK中删除了
HttpClient
的api,但是手機系統裡面還是有
HttpClient
的api的。方案一和二的原理最終都是引用SDK下
android-sdk-windows\platforms\android-23\optional
下的
org.apache.http.legacy.jar
這個jar包到項目中,是Google處理過的jar,添加了
AndroidHttpClient
等适合Android使用的api,體積相對從Apache官網下載下傳的jar小的多。
如何使用AndServer
這裡先給大家看下AndServer怎麼用,下一步詳解如何一步步用
HttpCore
實作一個簡易的
HttpServer
。
(一)實作AndServerRequestHandler接口,相當Servlet
我們每寫一個服務端接口,就要一個對應的類來處理,這裡要實作
AndServerRequestHandler
接口,相當于Java繼承Servet一樣,我們隻需要處理Request,在Response中給出響應即可:
publicclassAndServerTestHandlerimplementsAndServerRequestHandler{
@Override
publicvoid handle(HttpRequest rq,HttpResponse rp,HttpContext ct)throwsHttpException,IOException{
response.setEntity(newStringEntity("請求成功。","utf-8"));
}
}
(二)在AndServer上注冊接口名稱,并啟動伺服器
在啟動
AndServer
的時候最好放在Service中,這裡給出啟動的關鍵代碼。指定伺服器的端口号,并注冊接口,再啟動伺服器:
AndServerBuild andServerBuild =AndServerBuild.create();
andServerBuild.setPort(4477);// 指定http端口号。
// 注冊接口。
andServerBuild.add("test",newAndServerTestHandler());
// 這裡還可以注冊很多接口。
// 建構AndServer并啟動伺服器。
AndServer andServer = andServerBuild.build();
andServer.launch();
到這裡就完成了一個Android WebServer的搭建,我們已經可以通過浏覽器或者NoHttp來通路我們的
WebServer
了。
(三)其他裝置如何通路
如果是浏覽器方法,和我們普通通路網站沒有差別,比如通路我們上面的接口:
Android本機通路的位址:http://locahost:4477/test
區域網路其他裝置通路位址:http://192.168.1.116:4477/test
如果是其它Android系統的裝置,推薦使用NoHttp來通路,
NoHttp
是我的另一個Http用戶端的項目,和AndWeb正好是相對的,一個做服務端,一個做用戶端。
到這裡怎麼用AndServer和Android搭建服務端的教程就完了,如果想自己嘗試利用HttpCore搭建一個Http服務端的話,請往下看。
AndroidCore實作一個簡易的Http伺服器
其實裡邊的東西比較複雜個人感覺如果你不想自己寫一個這樣的架構的,沒有太大必要看完,但是我推薦大家往下看噢,我相信你會有收獲的。這裡講解下關鍵的代碼,一共有六步:
(一)ServerSocket建構服務端連接配接
我們知道Http是基于Socket的,那麼服務端肯定是
ServerSocket
了,是以我們這裡也是需要一個
ServerSocket
來接受用戶端請求的:
ServerSocket mServerSocket =newServerSocket();
mServerSocket.setReuseAddress(true);
mServerSocket.bind(newInetSocketAddress(mPort));// 綁定端口
(二)HttpProcessor增加Http協定處理器
這個就是添加Http協定攔截器,都是Http基本資訊。
// HTTP協定攔截器。
BasicHttpProcessor httpProcessor =newBasicHttpProcessor();
httpProcessor.addInterceptor(newResponseDate());
httpProcessor.addInterceptor(newResponseServer());
httpProcessor.addInterceptor(newResponseContent());
httpProcessor.addInterceptor(newResponseConnControl());
(三)HttpParams初始化Http基本資訊
初始化Http連接配接的資訊,比如逾時時間,緩存區大小,是否使用GZIP等。
// HTTP Attribute.
HttpParams httpParams =newBasicHttpParams();
httpParams.setIntParameter(CoreConnectionPNames.SO_TIMEOUT,)
.setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE,*)
.setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK,false)
.setBooleanParameter(CoreConnectionPNames.TCP_NODELAY,true)
.setParameter(CoreProtocolPNames.ORIGIN_SERVER,"WebServer/1.1");
(四)HttpRequestHandlerRegistry增加接口名稱
這裡要用HttpRequestHandlerRegistry把我們的
RequestHandler
注冊進來,這一步也是最重要的,就是我們的接口名稱,相當于是注冊Servlet到
web.xml
中一樣。
舉個例子,假設通路嚴振傑的首頁http://www.yanzhenjie.com,我的首頁下假設有一個login的接口,那我們的位址是:
http://www.yanzhenjie.com/login
,我們的
Android Web Server
也要實作這樣一個可以通路的位址,就要注冊一個login的接口,是以這裡是增加接口名稱:
// 注冊Http接口。
HttpRequestHandlerRegistry handlerRegistry =newHttpRequestHandlerRegistry();
handlerRegistry.register("/login",newRequestLoginHandle());// 增加登入接口
handlerRegistry.register("/download",newRequestTestHandle());// 增加下載下傳接口
這裡可以注冊很多個接口,我們後面的接口對象是實作了
HttpCore
中
HttpRequestHandler
接口的自定義類,比如我們上面的
RequestLoginHandle
的實作是:
publicclassRequestLoginHandleimplementsHttpRequestHandler{
@Override
publicvoid handle(HttpRequest rq,HttpResponse rp,HttpContext c){
// 隻要在這裡處理HttpRequest,如果要發出響應資料,用HttpResponse
}
}
(五)HttpService建立Http服務
前面準備的幾步都是為這一步準備參數的,把我們前面準備好的
httpProcessor
、
httpParams
、
handlerRegistry
都傳到
HttpService
,為下一步的Connection做好準備。
// 建立HTTP服務。
HttpService httpService =newHttpService(httpProcessor,newConnectionReuseStrategy(),newHttpResponseFactory());
httpService.setParams(httpParams);
httpService.setHandlerResolver(handlerRegistry);
(六)Socket、DefaultHttpServerConnection處理用戶端請求
上面的工作都做完了,就用到我們最開始準備好的
ServerSocket
來接受用戶端的連接配接的socket了,接受到一個用戶端的連接配接後,把剛才的httpParams和socket綁定到
HttpServerConnection
中,開始處理請求,下面代碼中有一個
RequestHandleTask
類,這是一個單獨的線程,因為每個請求都不能幹涉伺服器的主線程,是以這裡新開一個非阻塞的線程去處理每一個請求:
while(isLoop){
if(!mServerSocket.isClosed()){
// 阻塞接受用戶端。
Socket socket = mServerSocket.accept();
DefaultHttpServerConnection serverConnection =newDefaultHttpServerConnection();
// 接受到一個請求到,把請求和剛才的param綁定到connection中。
serverConnection.bind(socket, httpParams);
// 開啟一個線程去處理這個請求,不阻塞目前線程。
RequestHandleTask requestTask =newRequestHandleTask(this, httpService, serverConnection);
requestTask.setDaemon(true);
AndWebUtil.executeRunnable(requestTask);
}
}
在
RequestHandleTask
中的run方法中,我們隻要判斷
HttpServerConnection
是連接配接的,就調用
HttpService
的
handleRequest
方法交給
HttpCore
去分析請求,并自動分發到我們剛才注冊的login接口中。
while(mServerConnection.isOpen()){
mHttpService.handleRequest(mServerConnection,newBasicHttpContext());
}
當
HttpCore
分析出來這個連接配接中的請求符合我們剛才注冊的接口:
它會自動調用
RequestLoginHandle
的
hande()
方法,因為我們實作了
HttpRequestHandle
接口。
到這裡,如何利用
HttpCore
搭建一個
Android Http Server
就完成了。
把幾個步驟合起來
有的同學可能不會把上面的代碼整合起來,這裡給出完整的代碼:
try{
ServerSocket mServerSocket =newServerSocket();
mServerSocket.setReuseAddress(true);
mServerSocket.bind(newInetSocketAddress(mPort));
// HTTP協定攔截器。
BasicHttpProcessor httpProcessor =newBasicHttpProcessor();
httpProcessor.addInterceptor(newResponseDate());
httpProcessor.addInterceptor(newResponseServer());
httpProcessor.addInterceptor(newResponseContent());
httpProcessor.addInterceptor(newResponseConnControl());
// HTTP Attribute.
HttpParams httpParams =newBasicHttpParams();
httpParams.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, timeout)
.setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE,*)
.setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK,false)
.setBooleanParameter(CoreConnectionPNames.TCP_NODELAY,true)
.setParameter(CoreProtocolPNames.ORIGIN_SERVER,"WebServer/1.1");
// 注冊Http接口。
HttpRequestHandlerRegistry handlerRegistry =newHttpRequestHandlerRegistry();
for(Map.Entry<String,AndServerRequestHandler> handlerEntry : mRequestHandlerMap.entrySet()){
handlerRegistry.register("/"+ handlerEntry.getKey(),newDefaultHttpRequestHandler(handlerEntry.getValue()));
}
// 建立HTTP服務。
HttpService httpService =newHttpService(httpProcessor,newDefaultConnectionReuseStrategy(),newDefaultHttpResponseFactory());
httpService.setParams(httpParams);
httpService.setHandlerResolver(handlerRegistry);
/**
* 開始接受用戶端請求。
*/
while(isLoop){
// 接收用戶端套接字。
if(!mServerSocket.isClosed()){
Socket socket = mServerSocket.accept();
DefaultHttpServerConnection serverConnection =newDefaultHttpServerConnection();
serverConnection.bind(socket, httpParams);
// Dispatch request handler.
RequestHandleTask requestTask =newRequestHandleTask(this, httpService, serverConnection);
requestTask.setDaemon(true);
AndWebUtil.executeRunnable(requestTask);
}
}
}catch(Exception e){
}finally{
close();
}
RequestHandleTask
類的完整代碼:
publicclassRequestHandleTaskextendsThread{
privatefinalHttpService mHttpService;
privatefinalHttpServerConnection mServerConnection;
privateDefaultAndServer mWebServerThread;
publicRequestHandleTask(DefaultAndServer webServerThread,HttpService httpservice,HttpServerConnection conn){
this.mWebServerThread = webServerThread;
this.mHttpService = httpservice;
this.mServerConnection = conn;
}
@Override
publicvoid run(){
try{
while(mWebServerThread.isLooping()&& mServerConnection.isOpen()){
this.mHttpService.handleRequest(this.mServerConnection,newBasicHttpContext());
}
}catch(IOException e){
}catch(HttpException e){
}finally{
try{
this.mServerConnection.shutdown();
}catch(IOException e){
}
}
}
}