名称:Minicat
Minicat要做的事情:作为⼀个服务器软件提供服务的,也即我们可以通过浏览器客户端发送http请求,
Minicat可以接收到请求进⾏处理,处理之后的结果可以返回浏览器客户端。
1)提供服务,接收请求(Socket通信)
2)请求信息封装成Request对象(Response对象)
3)客户端请求资源,资源分为静态资源(html)和动态资源(Servlet)
4)资源返回给客户端浏览器
我们递进式完成以上需求,提出V1.0、V2.0、V3.0版本的需求
V1.0需求:浏览器请求http://localhost:8080,返回⼀个固定的字符串到⻚⾯"Hello Minicat!”
V2.0需求:封装Request和Response对象,返回html静态资源⽂件
V3.0需求:可以请求动态资源(Servlet)
完成上述三个版本后,我们的代码如下:
V1.0实现:
初步实现
- 开启socket
- 获取输出流,输出指定字符串。
- 测试http://localhost:8080 ```java package server;
import java.io.IOException; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket;
/**
MiniCat 主类 */ public class Bootstrap { //定义socket定义的端口号 private int port=8080;
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
/**
- MiniCat 启动需要初始化 展开的操作
- V1.0需求:浏览器请求http://localhost:8080,返回⼀个固定的字符串到⻚⾯"Hello Minicat!”
- V2.0需求:封装Request和Response对象,返回html静态资源⽂件
- V3.0需求:可以请求动态资源(Servlet)
*/
public void start() throws IOException {
//miniCat 1.0 版本 浏览器访问(http://localhost:8080)返回固定字符串到页面
ServerSocket serverSocket =new ServerSocket(port);
System.out.println(“MiniCat start on port:”+port);
while (true){
} } public static void main(String[] args) { Bootstrap bootstrap = new Bootstrap(); try {final Socket socket = serverSocket.accept();final OutputStream outputStream = socket.getOutputStream();outputStream.write("hello MiniCat!".getBytes());socket.close();
} catch (IOException e) {bootstrap.start();
} } }e.printStackTrace();
测试结果<br /><br />**发现没有请求头和响应头,像这样**<br />****<a name="nqmpO"></a>### 增加响应头**必要字段有:**- **HTTP/1.1 200 OK**- **Content-Type: text/html**- **Content-Length****代码如下:**```java/*** http协议工具类,主要提供响应头信息,这里我们只提供200和404*/public class HttpProtocolUtil {/*** 为响应码200提供请求头信息* @return*/public static String getHttpHeader200(long contentLength) {return "HTTP/1.1 200 OK \n" +"Content-Type: text/html \n" +"Content-Length: " + contentLength + " \n" +"\r\n";}/*** 为响应码404提供请求头信息(此处也包含了数据内容)* @return*/public static String getHttpHeader404() {String str404 = "<h1>404 not found</h1>";return "HTTP/1.1 404 NOT Found \n" +"Content-Type: text/html \n" +"Content-Length: " + str404.getBytes().length + " \n" +"\r\n" + str404;}}
在启动类中增加响应头信息。变更start方法如下:
增加获取http响应头信息,并将length传入,加上data用输出流进行输出。
/*** MiniCat 启动需要初始化 展开的操作* V1.0需求:浏览器请求http://localhost:8080,返回⼀个固定的字符串到⻚⾯"Hello Minicat!"* V2.0需求:封装Request和Response对象,返回html静态资源⽂件* V3.0需求:可以请求动态资源(Servlet)*/public void start() throws IOException {//miniCat 1.0 版本 浏览器访问(http://localhost:8080)返回固定字符串到页面ServerSocket serverSocket =new ServerSocket(port);System.out.println("MiniCat start on port:"+port);while (true){final Socket socket = serverSocket.accept();final OutputStream outputStream = socket.getOutputStream();String data="hello MiniCat!";String responseText= HttpProtocolUtil.getHttpHeader200(data.getBytes().length)+data;outputStream.write(responseText.getBytes());socket.close();}}
V2.0实现:
获取请求信息
while (true){final Socket socket = serverSocket.accept();//从输入流中获取请求信息final InputStream inputStream = socket.getInputStream();//针对网络情况等于0的时候int count=0;while (count==0){//同一批数据有可能会过不来 获取输入流中的长度count = inputStream.available();}byte[] bytes=new byte[count];inputStream.read(bytes);System.out.println("====>请求信息"+new String(bytes));}
封装request:
request封装如下:
/*** 把请求信息封装为Request对象(根据InputSteam输入流封装)*/public class Request {private String method; // 请求方式,比如GET/POSTprivate String url; // 例如 /,/index.htmlprivate InputStream inputStream; // 输入流,其他属性从输入流中解析出来public String getMethod() {return method;}public void setMethod(String method) {this.method = method;}public String getUrl() {return url;}public void setUrl(String url) {this.url = url;}public InputStream getInputStream() {return inputStream;}public void setInputStream(InputStream inputStream) {this.inputStream = inputStream;}public Request() {}// 构造器,输入流传入public Request(InputStream inputStream) throws IOException {this.inputStream = inputStream;// 从输入流中获取请求信息int count = 0;while (count == 0) {count = inputStream.available();}byte[] bytes = new byte[count];inputStream.read(bytes);String inputStr = new String(bytes);// 获取第一行请求头信息String firstLineStr = inputStr.split("\\n")[0]; // GET / HTTP/1.1String[] strings = firstLineStr.split(" ");this.method = strings[0];this.url = strings[1];System.out.println("=====>>method:" + method);System.out.println("=====>>url:" + url);}}
封装response:
package server;import java.io.File;import java.io.FileInputStream;import java.io.IOException;import java.io.OutputStream;/*** 封装Response对象,需要依赖于OutputStream** 该对象需要提供核心方法,输出html*/public class Response {private OutputStream outputStream;public Response() {}public Response(OutputStream outputStream) {this.outputStream = outputStream;}// 使用输出流输出指定字符串public void output(String content) throws IOException {outputStream.write(content.getBytes());}/**** @param path url,随后要根据url来获取到静态资源的绝对路径,进一步根据绝对路径读取该静态资源文件,最终通过* 输出流输出* /-----> classes*/public void outputHtml(String path) throws IOException {// 获取静态资源文件的绝对路径String absoluteResourcePath = StaticResourceUtil.getAbsolutePath(path);// 输入静态资源文件File file = new File(absoluteResourcePath);if(file.exists() && file.isFile()) {// 读取静态资源文件,输出静态资源StaticResourceUtil.outputStaticResource(new FileInputStream(file),outputStream);}else{// 输出404output(HttpProtocolUtil.getHttpHeader404());}}}
编写静态资源工具类:
package server;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;public class StaticResourceUtil {/*** 获取静态资源文件的绝对路径* @param path* @return*/public static String getAbsolutePath(String path) {String absolutePath = StaticResourceUtil.class.getResource("/").getPath();return absolutePath.replaceAll("\\\\","/") + path;}/*** 读取静态资源文件输入流,通过输出流输出*/public static void outputStaticResource(InputStream inputStream, OutputStream outputStream) throws IOException {int count = 0;while(count == 0) {count = inputStream.available();}int resourceSize = count;// 输出http请求头,然后再输出具体内容outputStream.write(HttpProtocolUtil.getHttpHeader200(resourceSize).getBytes());// 读取内容输出long written = 0 ;// 已经读取的内容长度int byteSize = 1024; // 计划每次缓冲的长度byte[] bytes = new byte[byteSize];while(written < resourceSize) {if(written + byteSize > resourceSize) { // 说明剩余未读取大小不足一个1024长度,那就按真实长度处理byteSize = (int) (resourceSize - written); // 剩余的文件内容长度bytes = new byte[byteSize];}inputStream.read(bytes);outputStream.write(bytes);outputStream.flush();written+=byteSize;}}}
- 获取socket输入流,作为获取请求头信息。
- 获取socket输出流,作为响应信息。
- 分别进行封装。
- StaticResourceUtil 根据url进行读取文件,缓冲式输出,一次输出1024.
最终调用请求封装成request,响应封装成response。传入socket输入及输出流。具体代码如下:
while(true) {Socket socket = serverSocket.accept();InputStream inputStream = socket.getInputStream();// 封装Request对象和Response对象Request request = new Request(inputStream);Response response = new Response(socket.getOutputStream());response.outputHtml(request.getUrl());socket.close();}
V3.0实现:
- 启动先加载配置文件到servlet
- 初始化加载servlet,解析web.xml
- 找到对应封装好的 request,response。
定义servlet
编写servlet接口: ```java public interface Servlet {
void init() throws Exception;
void destory() throws Exception;
void service(Request request, Response response) throws Exception; }
- 编写HttpServlet```javapublic abstract class HttpServlet implements Servlet{public abstract void doGet(Request request, Response response);public abstract void doPost(Request request,Response response);@Overridepublic void service(Request request, Response response) throws Exception {if("GET".equalsIgnoreCase(request.getMethod())) {doGet(request,response);}else{doPost(request,response);}}}
自定义servlet
public class LagouServlet extends HttpServlet {@Overridepublic void doGet(Request request, Response response) {String content = "<h1>LagouServlet get</h1>";try {response.output((HttpProtocolUtil.getHttpHeader200(content.getBytes().length) + content));} catch (IOException e) {e.printStackTrace();}}@Overridepublic void doPost(Request request, Response response) {String content = "<h1>LagouServlet post</h1>";try {response.output((HttpProtocolUtil.getHttpHeader200(content.getBytes().length) + content));} catch (IOException e) {e.printStackTrace();}}@Overridepublic void init() throws Exception {}@Overridepublic void destory() throws Exception {}}
自定义web.xml
```xml <?xml version=”1.0” encoding=”UTF-8” ?>
lagou servlet.LagouServlet
<servlet-mapping><servlet-name>lagou</servlet-name><url-pattern>/lagou</url-pattern></servlet-mapping>
目的 输入http://localhost:8080/lagou就可以到LagouServlet<a name="nCVQi"></a>#### 更改bootstrap```javaloadServlet();while(true) {Socket socket = serverSocket.accept();InputStream inputStream = socket.getInputStream();// 封装Request对象和Response对象Request request = new Request(inputStream);Response response = new Response(socket.getOutputStream());// 静态资源处理if(servletMap.get(request.getUrl()) == null) {response.outputHtml(request.getUrl());}else{// 动态资源servlet请求HttpServlet httpServlet = servletMap.get(request.getUrl());httpServlet.service(request,response);}socket.close();}
引入需要的pom
dom4j,jaxen
<dependency><groupId>dom4j</groupId><artifactId>dom4j</artifactId><version>1.6.1</version></dependency><dependency><groupId>jaxen</groupId><artifactId>jaxen</artifactId><version>1.1.6</version></dependency>
解析web.xml
- 读取文件成document对象
- 取到servlet找到servlet-name,对应的class
- 找到servlet-name对应的servlet-mapping
缓存url和servlet ```java private Map
servletMap = new HashMap (); /** 加载解析web.xml,初始化Servlet */ private void loadServlet() { InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(“web.xml”); SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(resourceAsStream);Element rootElement = document.getRootElement();List<Element> selectNodes = rootElement.selectNodes("//servlet");for (int i = 0; i < selectNodes.size(); i++) {Element element = selectNodes.get(i);// <servlet-name>lagou</servlet-name>Element servletnameElement = (Element) element.selectSingleNode("servlet-name");String servletName = servletnameElement.getStringValue();// <servlet-class>server.LagouServlet</servlet-class>Element servletclassElement = (Element) element.selectSingleNode("servlet-class");String servletClass = servletclassElement.getStringValue();
// 根据servlet-name的值找到url-patternElement servletMapping = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']");// /lagouString urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();servletMap.put(urlPattern, (HttpServlet) Class.forName(servletClass).newInstance());}} catch (DocumentException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();}}
<a name="QsgE3"></a>## 多线程改造:当前问题,现在属于单线程,只能单线程的去处理。<br />所以要改造成多线程方式。<a name="v3SZ9"></a>### 定义requestProcess```javapublic class RequestProcessor extends Thread {private Socket socket;private Map<String, HttpServlet> servletMap;public RequestProcessor(Socket socket, Map<String, HttpServlet> servletMap) {this.socket = socket;this.servletMap = servletMap;}@Overridepublic void run() {try{InputStream inputStream = socket.getInputStream();// 封装Request对象和Response对象Request request = new Request(inputStream);Response response = new Response(socket.getOutputStream());// 静态资源处理if(servletMap.get(request.getUrl()) == null) {response.outputHtml(request.getUrl());}else{// 动态资源servlet请求HttpServlet httpServlet = servletMap.get(request.getUrl());httpServlet.service(request,response);}socket.close();}catch (Exception e) {e.printStackTrace();}}}
不使用线程池bootstarp
while(true) {Socket socket = serverSocket.accept();RequestProcessor requestProcessor = new RequestProcessor(socket,servletMap);requestProcessor.start();}
使用线程池
定义线程池
// 定义一个线程池int corePoolSize = 10;//基本大小核心线程数int maximumPoolSize =50;//最大线程数long keepAliveTime = 100L;//保持时间TimeUnit unit = TimeUnit.SECONDS;//单位BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(50);//线程队列ThreadFactory threadFactory = Executors.defaultThreadFactory();//线程工厂RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();//拒绝策略 如何拒绝任务ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory,handler);
使用线程池改造bootstarp
while(true) {Socket socket = serverSocket.accept();RequestProcessor requestProcessor = new RequestProcessor(socket,servletMap);//requestProcessor.start();threadPoolExecutor.execute(requestProcessor);}
为什么并发达到一定数量,使用线程池效率更高一些呢?
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
- 提高线程的可管理性:对线程进行统一分配和监控,避免无限制创建线程导致内存溢出或者耗尽CPU
