简单的tomcat实现
来源:互联网 发布:微信网页授权回调域名 编辑:程序博客网 时间:2024/04/29 10:58
做了长时间web开发,一直都是用spring,导致自己成了操作工,按照既定的模子,重复的劳动,没有丝毫的进步,所以想深入的了解一番干了这么长时间的web的整个运行流程,绝大多数web开发学习应该都是servlet开始的吧,所以又重拾了servelt狠狠的研究了一番,最后发现servlet其实就是些标准,那啥为标准,说白了,就是定了些接口,导致看源码的过程很不过瘾,感觉没啥提升,就决定了解下更底层的工作原理,也就是tomcat。
开始我并不知道tomcat是java写的,对tomcat那是神秘,惧怕,感觉太过高大上,自己做的那点web开发和tomcat这种红遍全球的软件相比简直小巫见大巫,技术含量压根儿就不是一个层次。不过我还是想一窥究竟,偶然的机会,看到了《深入剖析Tomcat》,作者对tomcat源码讲解的很是详细,通过循序渐进的例子,非常认真的讲出了tomcat的精髓,我是受益匪浅。现在我想自己也写一个,来作为这阶段学习的一个交代,就叫tiny-tomcat吧,同样通过循序渐进,慢慢完善,源码在github上:https://github.com/esiyuan/tiny-tomcat.git
简单的web服务器
git checkout step-001:主要功能如下
- 接受http请求
private void handlerRequest() throws IOException { while(true) { Socket socket = serverSocket.accept(); logger.info("获取连接[ address: " + socket.getInetAddress() + ", port: " + socket.getPort() + " ]"); Request request = new Request(socket.getInputStream()); request.parse(); Response response = new Response(request.getUri(), socket.getOutputStream()); response.sendStaticResource(); socket.close(); } }
通过ServerSocket监听本地端口,serverSocket.accept()等待请求。
- 解析请求uri
public void parse() throws IOException { BufferedReader reader = IOUtil.getBufferedReader(inputStream); String requestString = reader.readLine(); this.uri = parseUri(requestString); }
为了分开请求和响应,贴合servlet风格,我们通过Request对象,解析请求字符串,request对象构造的时候,传入socket的输入流,由于我们只对第一行感兴趣,所以在只读取了第一行进行解析,解析出uri作为属性保存。
- 根据uri定位html文件,并返回响应
最后通过 Response对象进行html响应,而response在构造的时候,传入socket的输出流。
/** * 输出响应 * <p>文件找到uri对应的文件,则进行输出,否则输出404 * @throws IOException */ public void sendStaticResource() throws IOException { File file = new File(Constants.WEB_ROOT, uri); if (file.exists()) { sendFile(file); } else { sendDefault(); } }
简单的servlet容器
git checkout step-002
主要在前一节代码上进行了部分改造,增加了响应servlet的功能,并分出了简单的servlet处理器,因为HttpServletRequest和HttpServletResponse是接口,为了达到演示的效果,避免request和response类过于复杂,使用了门面RequestFacade和ResponseFacade。
public class ServletProcessor { private ConcurrentHashMap<String, Servlet> cache = new ConcurrentHashMap<String, Servlet>(); /** * servlet没有加载,则加载并初始化 * <p>如有加载直接取缓存 * @param request * @param response */ public void process(Request request, Response response){ Servlet servlet = cache.get(request.getServerName()); if(servlet != null) { try { servlet.service(request, response); } catch (ServletException | IOException e) { e.printStackTrace(); } return; } try(URLClassLoader classLoader = new URLClassLoader(new URL[]{new URL("file://" + Constants.TOMCAT_CLASSLOADER_REPOSITORY)});) { Class<?> clazz = classLoader.loadClass("web_root." + request.getServerName()); servlet = (Servlet)clazz.newInstance(); servlet.init(null); servlet.service(request, response); cache.putIfAbsent(request.getServerName(), servlet); } catch (Exception e) { e.printStackTrace(); System.out.println("服务异常!"); } }}
为了体现servlet的生命周期,增加了init()方法,并且通过ConcurrentHashMap缓存了sevlet实例,为什么用ConcurrentHashMap,主要是考虑线程安全,其分段锁的设计,能够拥有较高的并发写的能力,同时并不会妨碍并发读取,由于读没有进行加锁,不需要等待写完成,所有必然的会造成数据的弱一致性,不过大多数时候都会有取出非空判断逻辑,也造不成什么问题。
servlet代码如下:
public class HelloServlet extends HttpServlet { private static final long serialVersionUID = -2585140950753353037L; private static final Logger logger = Logger.getLogger(HelloServlet.class); public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException { PrintWriter out = response.getWriter(); out.println("hello servlet..."); } @Override public void init() throws ServletException { System.out.println("HelloServlet..初始化开始.."); }}
运行结果:
非阻塞的servlet容器
git checkout step-003
前面的实现,都是非常简单的单线程,万一线程阻塞,那岂不是不能提供服务了,实际的产品肯定不能是这样的。tomcat连接器监听端口,获取创建socket,这个应该是单点,而容易阻塞的地方,应该是具体业务逻辑处理的代码了,为了保证服务的可用,提高系统的吞吐率,可以使用多线程提供多个处理器,让每个请求有单独的线程进行处理,这样性能会大幅度提高,不会以为单个连接出现问题而造成服务不可用。下面我们分析具体的代码。
Bootstrap应用启动,主要功能,初始化连接器:
public final class Bootstrap { public static void main(String[] args) { HttpConnector connector = new HttpConnector("localhost", 8080); try { connector.initialize(); connector.start(); System.in.read(); //连接器线程,让主线程挂起,保证其他后台线程运行 } catch (UnknownHostException e) { e.printStackTrace(); } catch (TomcatException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } }}
连接器进行本地端口的监听,实例化并运行处理器:
/** * 初始化连接处理器和处理器 * @throws TomcatException * @throws UnknownHostException * @throws IOException */ public void initialize() throws TomcatException, UnknownHostException, IOException { createServer(); while (curProcessors < minProcessors) { if (curProcessors >= maxProcessors) break; HttpProcessor processor = newProcessor(); recycle(processor); } }
处理器进入wait状态(HttpProcessor中方法),等待被唤醒。
@Override public void run() { try { while(!Thread.interrupted()) { Socket socket = waitingToNewSocket(); process(socket); } } catch (InterruptedException | IOException | ServletException e) { System.out.println("处理器异常。。。"); } } private synchronized Socket waitingToNewSocket() throws InterruptedException { while (!newSocketCome) { wait(); } Socket socket = this.socket; newSocketCome = false; return (socket); }
当有请求到来时,唤醒挂起的处理器,处理 完成,处理器重新入栈,等待下次被使用。
public synchronized void assign(Socket socket) { this.socket = socket; newSocketCome = true; notifyAll(); }
模块化的web容器
git checkout step-004
tomcat作为一个大型的产品,为了开发维护的方便,必然的会把大任务进行分解,进行分层分模块,这也是如今软件设计的思路,模块清晰,层次明了,对于后期的维护,更新都会有极大的便利。
tomcat进行功能的拆分,模拟管道与阀的思想,对于容器的处理组件,都放在阀中,请求会像流水一样流过每个阀,最后得到最终的处理。
而tomcat通过接口来定下标准,Pipeline接口表示管道,Valve表示阀,具体可以看代码。
生命周期监听
git checkout step-005
本节主要增加了生命周期控制组件,接口Lifecycle,定义组件的生命周期,主要增加了事件监听模块,监听tomcat启动状态改变,并作出反应,下图监听的逻辑。
每个实现生命周期接口的组件都能进行监听器的绑定,统一的实现监听器的控制逻辑,使用了LifecycleSupport来代理,实现监听器绑定,解绑,通知。
- 简单的tomcat实现
- 简单实现Tomcat使用SSL的连接。
- axis+tomcat实现简单的webservice
- tomcat——简单的日志实现
- Nginx+Tomcat实现简单的负载均衡
- 一个简单Tomcat实现
- Tomcat简单实现
- 在tomcat下实现简单的SSL应用
- 基于HTML5和Tomcat WebSocketServlet的聊天室简单实现
- 基于HTML5和Tomcat WebSocketServlet的聊天室简单实现
- Intellij idea13配置tomcat,并实现一个简单的servlet
- WebSocket简单介绍 Java后端WebSocket的Tomcat实现
- Java+Tomcat+MySQL实现简单的网页注册和登录
- tomcat原理解析(一):一个简单的实现
- [深入剖析Tomcat]一个简单的servlet容器实现
- [深入剖析Tomcat]一个简单的servlet容器实现2
- 深入剖析Tomcat-实现简单的Web服务器
- 用SQLserver和JavaEE+Tomcat实现简单的登录界面
- 为什么要用--非关系型数据库--
- 对Java中final关键字的理解
- qt protobuf使用
- Android之Activity系列总结(三)--Activity的四种启动模式
- 测绘地理信息企业转型之惑
- 简单的tomcat实现
- 数学狂想曲(三)——统计杂谈, PID算法, 20世纪10大算法, 矩阵&向量的积
- Cordova和React-Native两种框架的对比
- php方法调用注意问题
- [乐意黎原创] 2017年正月初十,恭祝鸡年大运,开工大吉
- mac 安装gdb以及证书认证
- Java重要知识点(继承、多态、接口,异常,工具,测试)
- ASP.NET Cache缓存
- Zend Studio使用教程:在Docker容器中调试PHP Web应用(五)