Android 手机微服务架构的简单实现

功能:
使得手机app具有服务端功能,例如:wifi传书,可以通过手机app产生一个url地址,然后使用同一网段的电脑,打开浏览器,输入ip地址打开网页,上传文件,这样手机app就可以接受到这个文件,完成整个数据上传的过程。

知识储备:
通信协议:
TCP/IP 面向连接--需要连接之后才能传输数据(三次握手,四次挥手)数据可靠性--数据校验保证完整性,传输ack防丢包
UDP 不面向连接 不提供数据可靠性验证 速度快
容错性高,速度快,可以接受丢包使用UDP。

Socket:
网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。
ServerSocket 对socket进行简单封装
1、对指定端口监听--bind
2、等待连接--accept--对应三次握手的结束--接受到连接之后返回远程的socket
3、inputstream\outputstream 远程端口的输入输出流
4、关闭连接--close

http 请求格式:



http 相应格式:


路由规则:
客户端访问服务端以后,交给谁来处理。

代码:
网络配置类
WebConfiguration
public class WebConfiguration {
    // 端口号
    private int port;
    // 最大并发数
    private int maxParallels;

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public int getMaxParallels() {
        return maxParallels;
    }

    public void setMaxParallels(int maxParallels) {
        this.maxParallels = maxParallels;
    }
}


服务类--启动和停止服务,不断监听,针对不同的请求进行不同的处理。
SimpleHttpServer
public class SimpleHttpServer {
    private final WebConfiguration webConfig;
    private final ExecutorService threadPool;
    private boolean isEnable;
    private ServerSocket socket;
    private Set<IResourceHandler> resourceHandlers;

    SimpleHttpServer(WebConfiguration webConfig) {
        this.webConfig = webConfig;
        threadPool = Executors.newCachedThreadPool();
        resourceHandlers = new HashSet<IResourceHandler>();
    }

    /**
     * 启动Server(异步)
     */
    public void startAsync() {
        isEnable = true;
        new Thread(new Runnable() {
            @Override
            public void run() {
                doProcSync();
            }
        }).start();
    }

    /**
     * 停止Server(异步)
     */
    public void stopAsync() throws IOException {
        if (!isEnable || socket == null) {
            return;
        }
        isEnable = false;
        socket.close();
        socket = null;
    }

    private void doProcSync() {
        try {
            // 创建端口地址
            InetSocketAddress socketAddress = new InetSocketAddress(webConfig.getPort());
            socket = new ServerSocket();
            // 监听端口
            socket.bind(socketAddress);
            while (isEnable) {
                // 当远程有设备连接当前端口时,返回远程端口
                final Socket remotePeer = socket.accept();
                threadPool.submit(new Runnable() {
                    @Override
                    public void run() {
                        Log.d("spy", "a remote peer accepted..." + remotePeer.getRemoteSocketAddress().toString());
                        onAcceptedRemotePeer(remotePeer);
                    }
                });
            }
        } catch (IOException e) {
            Log.e("spy", e.toString());
        }
    }

    public void registerResourceHandler(IResourceHandler handler) {
        resourceHandlers.add(handler);
    }

    private void onAcceptedRemotePeer(Socket remotePeer) {
        try {
            HttpContext context = new HttpContext();
            context.setUnderlySocket(remotePeer);
//            remotePeer.getOutputStream().write("congratulations, connect successful".getBytes());
            InputStream nis = remotePeer.getInputStream();
            String headLine = null;
            String resourceUri = headLine = StreamTookit.readLine(nis).split(" ")[1];
            Log.d("spy", resourceUri);
            while ((headLine = StreamTookit.readLine(nis)) != null) {
                if (headLine.equals("\r\n")) {
                    break;
                }
                String[] pair = headLine.split(": ");
                if (pair.length > 1) {
                    context.addRequestHeaders(pair[0], pair[1].replace("\r\n", ""));
                }
                Log.d("spy", "header line = " + headLine);
            }

            for (IResourceHandler handler : resourceHandlers) {
                if (!handler.accept(resourceUri)) {
                    continue;
                }
                handler.handle(resourceUri, context);
            }
        } catch (IOException e) {
            Log.e("spy", e.toString());
        } finally {
            try {
                remotePeer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
 
 
启动服务之后,就会调用doProcSync函数,这个函数可以循环监听是否有远程连接到服务端。
首先根据网络配置监听指定的端口,然后监听,一旦有远程连接就返回远程socket,把他交给一个线程池中的某个线程进行处理,避免延迟和频繁的线程创建和销毁,影响其他连接的监听。
 
 
onAcceptedRemotePeer是真正的处理函数。可以从远程socket的inputstream获取网络请求头的具体内容,还可以根据url的内容把他交给相匹配的handler进行处理。
网络请求头的内容是有‘/r/n’来分行的,而我们需要的url具体请求地址在第一行,第二个字段中。
 
 
IResourceHandler
public interface IResourceHandler {
    boolean accept(String uri);

    void handle(String uri, HttpContext httpContext) throws IOException;
}

处理访问静态html的handler
如果路径是ip/static/xxx,就使用这个handler
public class ResourceInAssetsHandler implements IResourceHandler {
    private final Context context;
    private String acceptPrefix = "/static/";

    ResourceInAssetsHandler(Context context) {
        this.context = context;
    }

    @Override
    public boolean accept(String uri) {
        return uri.startsWith(acceptPrefix);
    }

    @Override
    public void handle(String uri, HttpContext httpContext) throws IOException {
        int startIndex = acceptPrefix.length();
        String assetsPath = uri.substring(startIndex);
        InputStream fis = context.getAssets().open(assetsPath);
        byte[] raw = StreamTookit.readRawFromStrem(fis);
        fis.close();
        OutputStream nos = httpContext.getUnderlySocket().getOutputStream();
        PrintStream print = new PrintStream(nos);
        print.println("HTTP/1.1 200 OK");
        print.println("Content-Length:" + raw.length);
        if (assetsPath.endsWith(".html")) {
            print.println("Content-Type:text/html");
        } else if (assetsPath.endsWith(".js")) {
            print.println("Content-Type:text/js");
        } else if (assetsPath.endsWith(".css")) {
            print.println("Content-Type:text/css");
        } else if (assetsPath.endsWith(".jpg")) {
            print.println("Content-Type:text/jpg");
        } else if (assetsPath.endsWith(".png")) {
            print.println("Content-Type:text/png");
        }
        print.println();
        print.write(raw);
        print.close();
    }
}
处理上传图片的handler
如果路径是ip/upload_image/xxx,就使用该handler.
public class UploadImageHandler implements IResourceHandler {
    private String acceptPrefix = "/upload_image/";

    @Override
    public boolean accept(String uri) {
        return uri.startsWith(acceptPrefix);
    }

    @Override
    public void handle(String uri, HttpContext httpContext) throws IOException {
        String tmpPath = Environment.getExternalStorageDirectory().getPath() + "/test_upload.jpg";
        long totalLength = Long.parseLong(httpContext.getRequestHeaderValue("Content-Length"));
        FileOutputStream fos = new FileOutputStream(tmpPath);
        InputStream nis = httpContext.getUnderlySocket().getInputStream();
        byte[] buffer = new byte[10240];
        int nReaded = 0;
        long nLeftLength = totalLength;
        while ((nLeftLength > 0) && (nReaded = nis.read(buffer)) > 0) {
            fos.write(buffer, 0, nReaded);
            nLeftLength -= nReaded;
        }
        fos.close();

        OutputStream nos = httpContext.getUnderlySocket().getOutputStream();
        PrintStream writer = new PrintStream(nos);
        writer.println("HTTP/1.1 200 OK");
        writer.println();

        onImageLoaded(tmpPath);
    }

    protected void onImageLoaded(String path) {

    }
}

MainActivity
public class MainActivity extends Activity {
    private SimpleHttpServer shs;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        WebConfiguration configuration = new WebConfiguration();
        configuration.setPort(8088);
        configuration.setMaxParallels(50);
        shs = new SimpleHttpServer(configuration);
        shs.registerResourceHandler(new ResourceInAssetsHandler(this));
        shs.registerResourceHandler(new UploadImageHandler() {
            @Override
            protected void onImageLoaded(String path) {
                showImage(path);
            }
        });
        shs.startAsync();
    }

    private void showImage(final String path) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                ImageView img = (ImageView) findViewById(R.id.img);
                Bitmap bitmap = BitmapFactory.decodeFile(path);
                img.setImageBitmap(bitmap);
                Toast.makeText(MainActivity.this, "image received and show", Toast.LENGTH_SHORT).show();
            }
        });
    }

    @Override
    protected void onDestroy() {
        try {
            shs.stopAsync();
        } catch (IOException e) {
            Log.e("spy", e.toString());
        }
        super.onDestroy();
    }
}

layout_main
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/img"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</LinearLayout>
HttpContext
public class HttpContext {
    private final HashMap<String, String> requestHeaders;
    private Socket underlySocket;

    HttpContext() {
        requestHeaders = new HashMap<String, String>();
    }

    public void setUnderlySocket(Socket socket) {
        this.underlySocket = socket;
    }

    public Socket getUnderlySocket() {
        return underlySocket;
    }

    public void addRequestHeaders(String headerName, String headerValue) {
        requestHeaders.put(headerName, headerValue);
    }

    public String getRequestHeaderValue(String headerName) {
        return requestHeaders.get(headerName);
    }
}

工具类
读取请求的每一行并返回。
public class StreamTookit {
    public static final String readLine(InputStream nis) throws IOException {
        StringBuffer sb = new StringBuffer();
        int c1 = 0;
        int c2 = 0;
        while (c2 != -1 && !(c1 == '\r' && c2 == '\n')) {
            c1 = c2;
            c2 = nis.read();
            sb.append((char) c2);
        }
        if (sb.length() == 0) {
            return null;
        }
        return sb.toString();
    }

    public static byte[] readRawFromStrem(InputStream fis) throws IOException {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        byte[] buffer = new byte[10240];
        int nReaded;
        while ((nReaded = fis.read(buffer)) > 0) {
            bos.write(buffer, 0, nReaded);
        }
        return bos.toByteArray();
    }
}



 
 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值