使用Socket与ServerSocket来构建客户端与服务器通信的桥梁,那个时候的架构是这样的:
Server端负责既负责与客户端连接又负责处理请求并返回处理结果;
下面是一个简单的Demo,麻雀虽小,五脏俱全,简单实现了Tomcat的主要功能:
"GET /index.html HTTP/1.1 Host: localhost:8080 Connection:Close"意为请求服务器ContextPath下相对路径为 "/index.html" 的这个资源:
/** * @CreatedBy:成江灿 * @Date:2019/12/22/15:24 * @Description:Socket通信客户端 */ public class Client { public static void main(String[] args) { Socket socket = new Socket("127.0.0.1", 8080){ OutputStream os = socket.getOutputStream(); PrintWriter out = new PrintWriter(os, true); /*向服务器发送一个HTTP1.1的请求*/ out.println("GET /index.html HTTP/1.1"); out.println("Host: localhost:8080"); out.println("Connection Close"); out.println(); /*读取服务器端响应的信息,这里用了带缓冲的Reader*/ BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); StringBuffer stringBuffer = new StringBuffer(8096); //一直等待服务器端的响应(NIO通过事件机制和Selector解决了这个问题) boolean loop = true; while(loop){ if(in.ready()){ int i = 0; //输入流读完后返回的值为-1 while(i != -1){ i = in.read(); stringBuffer.append((char)i); } loop = false; } Thread.currentThread().sleep(50L); } } } } 复制代码
服务器端的主要作用是根据请求流中的信息生成相应的Request对象,然后根据Request对象拿到URL信息并通过Response将对应URL的资源写回给客户端; ps:因为传统的JavaIO会卡在accept()方法下,为防止线程负荷增高,所以这里使用了线程池。
/** * @Description:Socket通信服务器端 */ public class Server { public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot"; /*用于标识服务器是否正在运行*/ private static boolean shutdown = false; private static BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(10); private static ExecutorService executorService = new ThreadPoolExecutor(2, 2, 100L, TimeUnit.MINUTES, workQueue); public static void main(String[] args) { ServerSocket serverSocket81 = null; final ServerSocket serverSocket80 = new ServerSocket(8080, 1, InetAddress.getByName("127.0.0.1")); Runnable runnableTask80 = new Runnable() { @Override public void run() { while(!shutdown){ Socket socket = null; InputStream inputStream = null; OutputStream outputStream = null; /*serverSocket建立连接后使用socket传值*/ socket = serverSocket80.accept(); inputStream = socket.getInputStream(); /*根据传入的请求流构建Request对象*/ Request request = new Request(inputStream); request.parse(); /*创建Response对象用于返回*/ Response response = new Response(outputStream); response.setRequest(request); response.sendStaticResource(); /*关闭Socket*/ socket.close(); } } }; executorService.execute(runnableTask80); } } 复制代码
主要负责从请求流中获取URL;
/** * @CreatedBy:成江灿 * @Date:2019/12/22/16:17 * @Description:将Socket中传入的请求流转为请求对象 */ public class Request { private InputStream inputStream; private String uri; public Request(InputStream inputStream){ this.inputStream = inputStream; } public void parse(){ /*从输入流中获取请求信息*/ StringBuffer request = new StringBuffer(2048); int i; byte[] buffer = new byte[2048]; try{ /*将输入流写入到缓冲区*/ i = inputStream.read(buffer); } catch (IOException e){ e.printStackTrace(); i = -1; } for(int j=0; j<i; j++){ request.append((char)buffer[j]); } uri = parseUri(request.toString()); } private String parseUri(String requestString){ int index1, index2; index1 = requestString.indexOf(' '); if (index1 != -1) { /*取出HTTP请求的第一行中涉及到URL的第二部分*/ index2 = requestString.indexOf(' ', index1 + 1); if (index2 > index1) return requestString.substring(index1 + 1, index2); } return null; } public String getUri(){ return uri; } } 复制代码
/** * @CreatedBy:成江灿 * @Date:2019/12/22/16:28 * @Description:主要作用是将请求的文件返回 */ public class Response { private static final int BUFFER_SIZE = 1024; Request request; OutputStream output; public Response(OutputStream output) { this.output = output; } public void setRequest(Request request) { this.request = request; } public void sendStaticResource() throws IOException { byte[] bytes = new byte[BUFFER_SIZE]; FileInputStream fis = null; try { File file = new File(System.getProperty("user.dir") + File.separator + "webroot", request.getUri()); if (file.exists()) { fis = new FileInputStream(file); int ch = fis.read(bytes, 0, BUFFER_SIZE); while (ch!=-1) { output.write(bytes, 0, ch); ch = fis.read(bytes, 0, BUFFER_SIZE); } } else { // file not found String errorMessage = "HTTP/1.1 404 File Not Found/r/n" + "Content-Type: text/html/r/n" + "Content-Length: 23/r/n" + "/r/n" + "<h1>File Not Found</h1>"; output.write(errorMessage.getBytes()); } } catch (Exception e) { // thrown if cannot instantiate a File object System.out.println(e.toString() ); } finally { if (fis!=null) fis.close(); } } } 复制代码
可以注意到,上面编写的服务端只能接受HTTP协议的请求并相应结果,而且上述程序是高度耦合的, 即连接的建立与请求的处理是耦合在服务器端的 ,所以一旦协议发生改变,那么连接的规则也会发生改变,那么服务器的代码也要发生改变,也许你会觉得重新复制一遍代码新建一个服务器端程序不就好了吗? 但你是否想过也许他们的请求处理程序可以是一样的吗,即处理程序它只是通过Request对象来获取信息的,所以我们是不是只需要改变一下处理请求流的规则便可以了呢?
所以我们需要将请求的连接与请求的处理在服务器端解耦合!!新版的架构图如下:
其中:(1)Connector负责协议负责各种协议连接的建立和Request和Response对象的构建;
(2)Engine则负责通过Request和Response对象对请求进行处理;
(3)Service则用于管理多个Connector与其相对应的Engine,因为基于不同的协议的请求可能会共用同一套处理程序的嘛!
写一篇博客真的要好久,写出来自己还是感觉很多地方没有说清,下次再开另一篇讲讲Engine吧~