在android上如何编写一个小型web服务器?

这个是前几年之前接触到的一个项目的需求,需要是android手机建立一个无线热点,其他设备连接热点后,访问网站,都跳转到android手机上热点提供的网站,所以就需要android手机端实现一个简易的web服务器,服务器的资源文件都存储在sd卡,并且可以更新。

废话不多说,这边把项目早期做的可行性研究的demo整理了下,开源出来。

这个小型web服务器很大一部分参考AndroidWebServ工程(https://github.com/joinAero/AndroidWebServ),在此多谢。

刚刚百度了下,已经有很多大牛也实现了类似的功能,用的方法也基本一致。这边在大概介绍下用到的具体技术

1:创建一个ServerSocket

2:使用HttpService创建一个Http服务,并为这个http创建需要的HTTP请求执行器(用来响应客户端发过来的get,download,upload等请求)

3:接收通过ServerSocket过来的Socket请求,并将Socket映射到HttpService上,这样HttpService就会返回对应的数据到客户端,客户端就能浏览网页了

核心代码在org.join.ws.serv.WebServer上,这边摘取部分片段

 

// 创建服务器套接字
            serverSocket = new ServerSocket(port);
            // 设置端口重用
            serverSocket.setReuseAddress(true);
            // 创建HTTP协议处理器
            BasicHttpProcessor httpproc = new BasicHttpProcessor();
            // 增加HTTP协议拦截器
            httpproc.addInterceptor(new ResponseDate());
            httpproc.addInterceptor(new ResponseServer());
            httpproc.addInterceptor(new ResponseContent());
            httpproc.addInterceptor(new ResponseConnControl());
            // 创建HTTP服务
            HttpService httpService = new HttpService(httpproc,
                    new DefaultConnectionReuseStrategy(), new DefaultHttpResponseFactory());
            // 创建HTTP参数
            HttpParams params = new BasicHttpParams();
            params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 5000)
                    .setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 1024)
                    .setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, false)
                    .setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true)
                    .setParameter(CoreProtocolPNames.ORIGIN_SERVER, "WebServer/1.1");
            // 设置HTTP参数
            httpService.setParams(params);
            // 创建HTTP请求执行器注册表
            HttpRequestHandlerRegistry reqistry = new HttpRequestHandlerRegistry();
            // 增加HTTP请求执行器
            reqistry.register(UrlPattern.DOWNLOAD, new HttpDownHandler(webRoot));
            reqistry.register(UrlPattern.DELETE, new HttpDelHandler(webRoot));
            reqistry.register(UrlPattern.UPLOAD, new HttpUpHandler(webRoot));
            reqistry.register(UrlPattern.PROGRESS, new HttpProgressHandler());
            reqistry.register(UrlPattern.BROWSE, new HttpFBHandler(webRoot));
            // 设置HTTP请求执行器
            httpService.setHandlerResolver(reqistry);
            // 回调通知服务开始
            if (mListener != null) {
                mListener.onStarted();
            }
            /* 循环接收各客户端 */
            isLoop = true;
            while (isLoop && !Thread.interrupted()) {
                // 接收客户端套接字
                Socket socket = serverSocket.accept();
                // 绑定至服务器端HTTP连接
                DefaultHttpServerConnection conn = new DefaultHttpServerConnection();
                conn.bind(socket, params);
                // 派送至WorkerThread处理请求
                Thread t = new WorkerThread(httpService, conn, mListener);
                t.setDaemon(true); // 设为守护线程
                pool.execute(t); // 执行
            }

项目中还用到 一个比较重要的库,Jangod,这个库很强大,可以用来从文件或者从代码中加载html,并支持css,有兴趣的同学可以 继续研究下去。

 

另外还有一个问题,怎么样把客户端所有的http请求都引到我们的ServerSocket?这边就需要用到nat表了,前提是你手机root了

1:将所有对80端口的http请求都强制转换到我们的ServerSocket,命令如下

 

String cmd = "iptables -t nat -A PREROUTING -d 0.0.0.0/0 -p tcp --dport 80 -j DNAT --to-destination "
	           +CommonUtil.getLocalIpAddress()+":" + Config.PORT;//192.168.43.1:7766";

2:将 所有对53端口的dns请求都强制装换到我们的dns请求端口,并实现dns的响应 

String cmd53 = "iptables -t nat -A PREROUTING -d 0.0.0.0/0 -p udp --dport 53 -j DNAT --to-destination "
	 	           +CommonUtil.getLocalIpAddress()+":" + Config.PORT_DNS;//192.168.43.1:7766";

 

package org.join.ws.serv;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.DefaultHttpResponseFactory;
import org.apache.http.impl.DefaultHttpServerConnection;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.BasicHttpProcessor;
import org.apache.http.protocol.HttpRequestHandlerRegistry;
import org.apache.http.protocol.HttpService;
import org.apache.http.protocol.ResponseConnControl;
import org.apache.http.protocol.ResponseContent;
import org.apache.http.protocol.ResponseDate;
import org.apache.http.protocol.ResponseServer;
import org.join.ws.Constants.Config;
import org.join.ws.serv.req.HttpDelHandler;
import org.join.ws.serv.req.HttpDownHandler;
import org.join.ws.serv.req.HttpFBHandler;
import org.join.ws.serv.req.HttpProgressHandler;
import org.join.ws.serv.req.HttpUpHandler;
import org.join.ws.util.CommonUtil;

/**
 * @brief DnsWeb服务类
 * @author talkercenter
 */
public class DnsServer extends Thread {

    static final String TAG = "DnsServer";
    static final boolean DEBUG = false || Config.DEV_MODE;
    
    byte[] requestBuffer = new byte[256];
    byte[] responseBuffer = new byte[256];
    byte[] ipBuffer = { (byte) 0xc0, 0x0c, 0x00, 0x01, 0x00, 0x01, 0x00,
    0x00, 0x01, 0x7c, 0x00, 0x04, (byte)0xc0, (byte)0xa8, 0x2b, 0x01 };
    
    public static final int ERR_UNEXPECT = 0x0101;
    public static final int ERR_PORT_IN_USE = 0x0102;
    public static final int ERR_TEMP_NOT_FOUND = 0x0103;

    private int port;
    //private String webRoot;

    private DatagramSocket serverSocket;
    /* package */static boolean isLoop;

    private OnWebServListener mListener;

    //private ExecutorService pool; // 线程池

    public DnsServer(int port, final String webRoot) {
        super();
        this.port = port;
        //this.webRoot = webRoot;
        isLoop = false;

        //pool = Executors.newCachedThreadPool();
    }

    @Override
    public void run() {
        try {
            // Decide if port is in use.
            if (CommonUtil.getSingleton().isLocalPortInUse(port)) {
                if (mListener != null) {
                    mListener.onError(ERR_PORT_IN_USE);
                }
                return;
            }
            // 创建服务器套接字
            serverSocket = new DatagramSocket(port);
            DatagramPacket requestPacket = new DatagramPacket(requestBuffer,requestBuffer.length);
            /* 循环接收各客户端 */
            isLoop = true;
            while (isLoop && !Thread.interrupted()) {
            	serverSocket.receive(requestPacket);
                int requestLength = requestPacket.getLength();
                System.arraycopy(requestBuffer, 0, responseBuffer, 0, requestLength);
                System.arraycopy(ipBuffer, 0, responseBuffer, requestLength, ipBuffer.length);
                // 标志位
                responseBuffer[2] = (byte) 0x81;
                responseBuffer[3] = (byte) 0x80;
                // 响应数
                responseBuffer[6] = (byte) 0x00;
                responseBuffer[7] = (byte) 0x01;
                DatagramPacket response = new DatagramPacket(responseBuffer, requestLength + ipBuffer.length, requestPacket.getAddress(), requestPacket.getPort());
                serverSocket.send(response);
            }
        } catch (IOException e) {
            if (isLoop) { // 以排除close造成的异常
                // 回调通知服务出错
                if (mListener != null) {
                    mListener.onError(ERR_UNEXPECT);
                }
                if (DEBUG)
                    e.printStackTrace();
                isLoop = false;
            }
        } finally {
            try {
                if (serverSocket != null) {
                    serverSocket.close();
                }
                // 回调通知服务结束
                if (mListener != null) {
                    mListener.onStopped();
                }
            } catch (Exception e) {
            }
        }
    }

    public void close() {
        isLoop = false;
        try {
            if (serverSocket != null) {
                serverSocket.close();
            }
        } catch (Exception e) {
        }
    }

    public interface OnWebServListener {
        void onStarted();

        void onStopped();

        void onError(int code);
    }

    public void setOnWebServListener(OnWebServListener mListener) {
        this.mListener = mListener;
    }

}

 

结束,完整代码请访问

https://github.com/bobohuang1985/android-utils-api