提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
Tomcat Web服务器
前言
提示:这里可以添加本文要记录的大概内容:
tomcat的简单介绍,自己实现一个简易的tomcat。
Tomcat架构
Tomcat是⼀个Http服务器,当用户使用浏览器对某个网站发起请求时,浏览器生成HTTP格式的数据包,浏览器通过TCP协议与服务器传输数据。
HTTP 服务器接收到请求之后把请求交给Servlet容器来处理,Servlet 容器通过Servlet接⼝调⽤业务
类。Servlet接⼝和Servlet容器这⼀整套内容叫作Servlet规范。
Tomcat的两个重要身份
1)http服务器:和客户端浏览器进⾏交互,进⾏socket通信,将字节流和Request/Response等对象进⾏转换
2)Tomcat是⼀个Servlet容器:Servlet容器处理业务逻辑。
针对上述两个角色,tomcat内部实现有两个组件: 连接器组件 Coyote和容器组件Catalina
Tomcat Coyote
Tomcat Coyote ⽀持的 IO模型与协议
Coyote 是Tomcat 中连接器的组件名称 , 是对外的接⼝。客户端通过Coyote与服务器建⽴连接、发送请求并接受响应 。
(1)Coyote 封装了底层的⽹络通信(Socket 请求及响应处理)
(2)Coyote 使Catalina 容器(容器组件)与具体的请求协议及IO操作⽅式完全解耦
(3)Coyote 将Socket 输⼊转换封装为 Request 对象,进⼀步封装后交由Catalina 容器进⾏处理,处
理请求完成后, Catalina 通过Coyote 提供的Response 对象将结果写⼊输出流
(4)Coyote 负责的是具体协议(应⽤层)和IO(传输层)相关内容
Tomcat Catalina
可以认为整个Tomcat就是⼀个Catalina实例,Tomcat 启动的时候会初始化这个实例,Catalina
实例通过加载server.xml完成其他实例的创建,创建并管理⼀个Server,Server创建并管理多个服务,
每个服务⼜可以有多个Connector和⼀个Container。
- Catalina
负责解析Tomcat的配置⽂件(server.xml) , 以此来创建服务器Server组件并进⾏管理 - Server
服务器表示整个Catalina Servlet容器以及其它组件,负责组装并启动Servlaet引擎,Tomcat连接
器。Server通过实现Lifecycle接⼝,提供了⼀种优雅的启动和关闭整个系统的⽅式 - Service
服务是Server内部的组件,⼀个Server包含多个Service。它将若⼲个Connector组件绑定到⼀个 - Container
Container容器,负责处理⽤户的servlet请求,并返回对象给web⽤户的模块
手写实现简易tomcat
V1版:返回⼀个固定的字符串
浏览器请求http://localhost:8080,返回⼀个固定的字符串到⻚⾯"Hello Minicat!
private void miniCatV1(ServerSocket serverSocket) throws IOException {
while (true) {
Socket socket = serverSocket.accept();
OutputStream outputStream = socket.getOutputStream();
String data = "Hello MiniCat";
// String responseText = HttpProtocolUtil.getHttpHeader200(data.getBytes().length) + data;
String res404 = HttpProtocolUtil.getHttpHeader404() + data;
outputStream.write(res404.getBytes());
socket.close();
}
}
V2版:封装Request和Response对象,返回html静态资源⽂件
/**
* 需求:封装Request和Response对象,返回html静态资源⽂件
*
* @param serverSocket
* @throws IOException
*/
private void miniCatV2(ServerSocket serverSocket) throws IOException {
while (true) {
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
Request request = new Request(inputStream);
OutputStream outputStream = socket.getOutputStream();
Response response = new Response(outputStream);
response.outputHtml(request.getUrl());
socket.close();
}
}
V3版:可以请求动态资源(Servlet)
/**
* 需求:可以请求动态资源(Servlet)
*
* @param serverSocket
* @throws IOException
*/
private void miniCatV3(ServerSocket serverSocket) throws Exception {
while (true) {
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
Request request = new Request(inputStream);
OutputStream outputStream = socket.getOutputStream();
Response response = new Response(outputStream);
if (servletMap.get(request.getUrl()) == null) {
response.outputHtml(request.getUrl());
} else {
HttpServlet httpServlet = servletMap.get(request.getUrl());
httpServlet.service(request, response);
}
socket.close();
}
}
/**
* 加载web.xml,初始化servlet
*/
private void loadServlet() {
InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("web.xml");
SAXReader saxReader = new SAXReader();
try {
Document read = saxReader.read(resourceAsStream);
Element rootElement = read.getRootElement();
List<Element> list = rootElement.selectNodes("//servlet");
for (int i = 0; i < list.size(); i++) {
Element element = list.get(i);
Element servletNameElement = (Element) element.selectSingleNode("servlet-name");
String servletName = servletNameElement.getStringValue();
Element servletClassElement = (Element) element.selectSingleNode("servlet-class");
String servletClass = servletClassElement.getStringValue();
// 根据servlet-name的值找到url-pattern
Element servletMapping = (Element) rootElement.selectSingleNode("//servlet-mapping[servlet-name='" + servletName + "']");
String urlPattern = servletMapping.selectSingleNode("servlet-pattern").getStringValue();
servletMap.put(urlPattern, (HttpServlet) Class.forName(servletClass).newInstance());
}
} catch (DocumentException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
优化-引入线程池
public void start() throws Exception {
loadServlet();
//定义线程池改造
int coolPoolSize = 10;
int maximumPoolSize = 50;
long keepAliveTime = 100L;
BlockingQueue<Runnable> workDequeue = new ArrayBlockingQueue<>(50);
ThreadFactory threadFactory = Executors.defaultThreadFactory();
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(coolPoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, workDequeue, threadFactory, handler);
ServerSocket serverSocket = new ServerSocket(port);
System.out.println("MiniCat started with port:" + port);
// miniCatV3(serverSocket);
while (true) {
Socket socket = serverSocket.accept();
RequestProcessor requestProcessor = new RequestProcessor(socket, servletMap);
//多线程,不使用线程池
// requestProcessor.start();
//使用线程池
threadPoolExecutor.execute(requestProcessor);
}
}
工作中遇到的问题分析
1、 service中定义了静态成员变量localdate,期望每次调用service时重新new一个对象,结果只实例化了一次。联想到单例模式也有用静态关键词修饰的,顺便探究一下service类的声明周期。
解答:
如果Servlet还没有被加载,就⽤反射机制创建这个Servlet,并调⽤Servlet的init⽅法来完成初始化。
总结
可扩展的思路:springboot集成的tomcat、Spring管理的bean声明周期。