手机服务器微架构设计与实现 之 http server
·应用
·传输协议和应用层协议概念
TCP
UDP
TCP和UDP选择
三次握手(客户端与服务器端建立连接)/四次挥手(断开连接)过程图
·java Socket 基础
·Get 与 Post 协议格式
·开发真机与模拟器网络调试工具与配置
真机:开发机和真机处于同一网段下即可
模拟器:
·关键代码
1 package com.example.lifen.simplehttpserver; 2 3 import android.util.Log; 4 5 import java.io.IOException; 6 import java.io.InputStream; 7 import java.net.InetSocketAddress; 8 import java.net.ServerSocket; 9 import java.net.Socket; 10 import java.util.HashSet; 11 import java.util.concurrent.ExecutorService; 12 import java.util.concurrent.Executors; 13 14 /** 15 * Created by LiFen on 2017/12/27. 16 */ 17 18 public class SimpleHttpServer { 19 private static final String TAG = "SimpleHttpServer"; 20 private final WebConfiguration webConfig; 21 private final ExecutorService threadPool;//线程池 22 private final HashSet<IRsourceUriHandler> resourceUriHandlers; 23 private boolean isEnable; 24 private ServerSocket socket; 25 26 public SimpleHttpServer(WebConfiguration webConfig){ 27 this.webConfig = webConfig; 28 threadPool = Executors.newCachedThreadPool(); 29 resourceUriHandlers = new HashSet<>(); 30 } 31 32 public void registerResourceHandler(IRsourceUriHandler iRsourceUriHandler){ 33 resourceUriHandlers.add(iRsourceUriHandler); 34 } 35 36 /** 37 * 启动Server(异步) 38 */ 39 public void startAsync(){ 40 isEnable = true; 41 new Thread(new Runnable() { 42 @Override 43 public void run() { 44 doProcSync(); 45 } 46 }).start(); 47 } 48 49 /** 50 * 停止Server(异步) 51 */ 52 public void stopAsync() { 53 if(!isEnable){ 54 return; 55 } 56 isEnable = false; 57 try { 58 socket.close(); 59 } catch (IOException e) { 60 e.printStackTrace(); 61 } 62 socket = null; 63 } 64 65 private void doProcSync() { 66 Log.d(TAG, "doProcSync() called"); 67 try { 68 InetSocketAddress socketAddr = new InetSocketAddress(webConfig.getPort()); 69 socket = new ServerSocket(); 70 socket.bind(socketAddr); 71 while(isEnable){ 72 final Socket remotePeer = socket.accept(); 73 threadPool.submit(new Runnable() { 74 @Override 75 public void run() { 76 onAcceptRemotePeer(remotePeer); 77 } 78 }); 79 } 80 } catch (IOException e) { 81 Log.e(TAG, "doProcSync: e ", e); 82 } 83 } 84 85 /** 86 * 一个HTTP请求报文由请求行(request line)、请求头部(header)、空行和请求数据4个部分组成 87 * @param remotePeer 88 */ 89 private void onAcceptRemotePeer(Socket remotePeer) { 90 Log.d(TAG, "onAcceptRemotePeer() called with: remotePeer.getRemoteSocketAddress() = [" + remotePeer.getRemoteSocketAddress() + "]"); 91 HttpContext httpContext = new HttpContext(); 92 try { 93 // remotePeer.getOutputStream().write("congratulations, connected success".getBytes()); 94 httpContext.setUnderlySocket(remotePeer); 95 InputStream nis = remotePeer.getInputStream(); 96 String headerLine = null; 97 String readLine = StreamToolkit.readLine(nis);//http请求行(request line)1 98 Log.i(TAG, "http 1 请求行 readLine =" + readLine); 99 //测试请求行结果 readLine =POST /get/?key=dddd&sss=123456 HTTP/1.1 100 httpContext.setType(readLine.split(" ")[0]); 101 String resourceUri = headerLine = readLine.split(" ")[1]; 102 Log.i(TAG, "地址 =" + headerLine); 103 while ((headerLine = StreamToolkit.readLine(nis)) != null) {//http请求头部(header)2 104 105 if (headerLine.equals(" ")) {//http请求空行3 106 Log.i(TAG, "http 3 请求头部 /r/n "); 107 break; 108 } 109 Log.i(TAG, "http 2 请求头部 headerLine = " + headerLine); 110 String[] pair = headerLine.split(": "); 111 if (pair.length > 1) { 112 httpContext.addRequestHeader(pair[0], pair[1]); 113 } 114 } 115 116 for (IRsourceUriHandler handler : resourceUriHandlers) { 117 if (!handler.accept(resourceUri)) { 118 continue; 119 } 120 handler.postHandle(resourceUri, httpContext);//http请求数据4 121 } 122 } catch (IOException e) { 123 Log.e("spy",e.toString()); 124 }finally {//只要流或socket不关闭,浏览器就收不到信息 125 try { 126 remotePeer.close(); 127 } catch (IOException e) { 128 e.printStackTrace(); 129 } 130 } 131 } 132 }
1 package com.example.lifen.simplehttpserver; 2 3 import android.graphics.Bitmap; 4 import android.graphics.BitmapFactory; 5 import android.os.Bundle; 6 import android.support.v7.app.AppCompatActivity; 7 import android.widget.ImageView; 8 9 public class MainActivity extends AppCompatActivity { 10 11 private SimpleHttpServer shs; 12 private ImageView imageView; 13 14 @Override 15 protected void onCreate(Bundle savedInstanceState) { 16 super.onCreate(savedInstanceState); 17 setContentView(R.layout.activity_main); 18 imageView = (ImageView) findViewById(R.id.imageView); 19 20 WebConfiguration wc = new WebConfiguration(); 21 wc.setPort(8088); 22 wc.setMaxParallels(50); 23 24 shs = new SimpleHttpServer(wc); 25 /* 26 向 shs 中添加 IRsourceUriHander 实例 27 */ 28 shs.registerResourceHandler(new ResourceInAssetsHandler(this)); 29 shs.registerResourceHandler(new UploadImageHandler(this){ 30 @Override 31 public void onImageLoaded(final String path) {//重写onImageLoaded方法 在UiTread 主线程中更新Ui 32 runOnUiThread(new Runnable() { 33 @Override 34 public void run() { 35 Bitmap bitmap = BitmapFactory.decodeFile(path); 36 imageView.setImageBitmap(bitmap); 37 } 38 }); 39 } 40 }); 41 42 shs.startAsync(); 43 } 44 45 @Override 46 protected void onDestroy() { 47 super.onDestroy(); 48 shs.stopAsync(); 49 } 50 }
1 package com.example.lifen.simplehttpserver; 2 3 import android.content.Context; 4 import android.util.Log; 5 6 import java.io.IOException; 7 import java.io.InputStream; 8 import java.io.OutputStream; 9 import java.io.PrintStream; 10 11 /** 12 * Created by LiFen on 2017/12/29. 13 */ 14 15 public class ResourceInAssetsHandler implements IRsourceUriHandler { 16 private static final String TAG = "ResourceInAssetsHandler"; 17 private String acceptPrefix = "/static/"; 18 private Context context; 19 20 public ResourceInAssetsHandler(Context context){ 21 this.context = context; 22 } 23 24 @Override 25 public boolean accept(String uri) { 26 /* 27 判断uri 是否以 “/static/" 开始 28 */ 29 return uri.startsWith(acceptPrefix); 30 } 31 32 @Override 33 public void postHandle(String uri, HttpContext httpContext) throws IOException{ 34 Log.d(TAG, "postHandle() called with: uri = [" + uri + "], httpContext = [" + httpContext + "]"); 35 /* 36 截取assets 文件夹下静态网页相对路径 37 */ 38 int startIndex = acceptPrefix.length(); 39 String assetsPath = uri.substring(startIndex); 40 Log.i(TAG, "assetsPath: " + assetsPath); 41 42 /* 43 打开静态网页 返回一个输入流 转化为 byte[] 44 */ 45 InputStream inputStream = context.getAssets().open(assetsPath);//打开文件 返回一个输入流 46 byte[] raw = StreamToolkit.readRawFromStream(inputStream); 47 Log.i(TAG, "assetsPath "+ "raw =" + raw.length); 48 inputStream.close(); 49 50 OutputStream outputStream = httpContext.getUnderlySocket().getOutputStream();//获取当前Socket的输出流 51 PrintStream printWriter = new PrintStream(outputStream);//输出符合http协议的信息给浏览器 52 printWriter.println("HTTP/1.1 200 OK"); 53 printWriter.println("Content-Length:" + raw.length); 54 55 if(assetsPath.endsWith(".html")){ 56 printWriter.println("Content-Type:text/html"); 57 }else if(assetsPath.endsWith(".js")){ 58 printWriter.println("Content-Type:text/js"); 59 }else if(assetsPath.endsWith(".css")){ 60 printWriter.println("Content-Type:text/css"); 61 }else if(assetsPath.endsWith(".jpg")){ 62 printWriter.println("Content-Type:text/jpg"); 63 }else if(assetsPath.endsWith(".png")){ 64 printWriter.println("Content-Type:text/png"); 65 } 66 printWriter.println(); 67 68 printWriter.write(raw); 69 Log.i(TAG, "postHandle: over"); 70 } 71 }
1 package com.example.lifen.simplehttpserver; 2 3 import android.app.Activity; 4 import android.os.Environment; 5 import android.util.Log; 6 7 import java.io.File; 8 import java.io.FileOutputStream; 9 import java.io.IOException; 10 import java.io.InputStream; 11 import java.io.OutputStream; 12 import java.io.PrintWriter; 13 14 15 /** 16 * Created by LiFen on 2017/12/30. 17 */ 18 19 class UploadImageHandler implements IRsourceUriHandler { 20 private static final String TAG = "UploadImageHandler"; 21 String acceptPrefix = "/image/"; 22 Activity activity; 23 24 public UploadImageHandler(Activity activity){ 25 this.activity = activity; 26 } 27 28 @Override 29 public boolean accept(String uri) { 30 return uri.startsWith(acceptPrefix); 31 } 32 33 @Override 34 public void postHandle(String uri, HttpContext httpContext) throws IOException { 35 long totalLength = Long.parseLong(httpContext.getRequestHeaderValue("Content-Length").trim()); 36 /* 37 将 接收到的 图片存储到本机 SD卡目录下 38 */ 39 File file = new File(Environment.getExternalStorageDirectory(), 40 "tmpFile.jpg"); 41 Log.i(TAG, "postHandle: " +"totalLength=" + totalLength + " file getPath=" + file.getPath() ); 42 if(file.exists()){ 43 file.delete(); 44 } 45 byte[] buffer = new byte[10240]; 46 int read; 47 long nLeftLength = totalLength; 48 FileOutputStream fileOutputStream = new FileOutputStream(file.getPath());//文件输出流 49 InputStream inputStream = httpContext.getUnderlySocket().getInputStream();//从当前 Socket 中得到文件输入流 50 while (nLeftLength > 0 && (read = inputStream.read(buffer)) > 0){//写到文件输出流中,即存储到SD 卡路径下 51 fileOutputStream.write(buffer,0,read); 52 nLeftLength -= read; 53 } 54 Log.i(TAG, "postHandle: close"); 55 fileOutputStream.close(); 56 57 /* 58 从当前 Socket 中得到 文件输出流,将 符合http协议的信息 返回给浏览器 59 */ 60 OutputStream outputStream = httpContext.getUnderlySocket().getOutputStream(); 61 PrintWriter printWriter = new PrintWriter(outputStream); 62 printWriter.print("HTTP/1.1 200 OK"); 63 printWriter.println(); 64 /* 65 记录图片存储的位置 66 */ 67 onImageLoaded(file.getPath()); 68 } 69 70 public void onImageLoaded(String path){ 71 Log.d(TAG, "onImageLoaded() called with: path = [" + path + "]"); 72 } 73 }
1 package com.example.lifen.simplehttpserver; 2 3 import java.net.Socket; 4 import java.util.HashMap; 5 import java.util.Map; 6 7 /** 8 * Created by LiFen on 2017/12/28. 9 */ 10 11 public class HttpContext { 12 private Socket underlySocket; 13 Map<String,String> heard = new HashMap<>(); 14 private String type; 15 16 public void setUnderlySocket(Socket underlySocket){ 17 this.underlySocket = underlySocket; 18 } 19 20 public void addRequestHeader(String key,String value){ 21 heard.put(key,value); 22 } 23 24 public Socket getUnderlySocket(){ 25 return underlySocket; 26 } 27 28 public String getRequestHeaderValue(String key){ 29 return heard.get(key); 30 } 31 32 public String getType(){ 33 return type; 34 } 35 36 public void setType(String type){ 37 this.type = type; 38 } 39 }
1 package com.example.lifen.simplehttpserver; 2 3 import java.io.ByteArrayOutputStream; 4 import java.io.IOException; 5 import java.io.InputStream; 6 7 /** 8 * Created by LiFen on 2017/12/27. 9 */ 10 11 public class StreamToolkit { 12 public static final String readLine(InputStream nis) throws IOException { 13 StringBuffer sb = new StringBuffer(); 14 int c1 = 0; 15 int c2 = 0; 16 while (c2 != -1 && !(c1 == ' ' && c2 == ' ')){ 17 c1 = c2; 18 c2 = nis.read(); 19 sb.append((char)c2); 20 } 21 if(sb.length() == 0) { 22 return null; 23 } 24 return sb.toString(); 25 } 26 27 public static byte[] readRawFromStream(InputStream inputStream) throws IOException { 28 ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 29 byte[] buffer = new byte[10240]; 30 int read; 31 while ((read = inputStream.read(buffer)) > 0){ 32 outputStream.write(buffer,0,read); 33 } 34 return outputStream.toByteArray(); 35 } 36 37 }
1 package com.example.lifen.simplehttpserver; 2 3 /** 4 * Created by LiFen on 2017/12/27. 5 */ 6 7 public class WebConfiguration { 8 /** 9 * 端口 10 */ 11 private int port; 12 /** 13 * 最大监听数 14 */ 15 private int maxParallels; 16 17 public int getPort() { 18 return port; 19 } 20 21 public void setPort(int port) { 22 this.port = port; 23 } 24 25 public int getMaxParallels() { 26 return maxParallels; 27 } 28 29 public void setMaxParallels(int maxParallels) { 30 this.maxParallels = maxParallels; 31 } 32 }
1 package com.example.lifen.simplehttpserver; 2 3 import java.io.IOException; 4 5 /** 6 * Created by LiFen on 2017/12/29. 7 */ 8 9 public interface IRsourceUriHandler { 10 boolean accept(String uri); 11 void postHandle(String uri, HttpContext httpContext) throws IOException; 12 }
此项目github下载地址:
https://github.com/li-fengjie/SimpleHttpServer
win10正式版telnet不是内部或外部命令解决办法:
https://jingyan.baidu.com/article/1e5468f9033a71484961b7d7.html