功能:
使得手机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
处理上传图片的handlerpublic 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(); } }
如果路径是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(); } }