模拟最简单的Web容器
// 创建socket服务
ServerSocket serverSock = new ServerSocket(port,backlog);
// 循环等待socket连接
while(shutdown){
// 接收到socket连接
Scoket socket = serverSock.accept();
// 将Http报文解析为Request
Request request = createRequest(socket.getInputStream);
// 封装返回内容,Response
Response response = createResponse(request);
// 创建Servlet
URLClassLoader classLoader = new URLClassLoader();
Class servletClass = classLoader.loadClasser(servletName);
Servlet servlet = servletClass.newInstance();
// 执行业务逻辑
servlet.service(request,response);
// socket关闭
socket.close;
}
BIO模式
关键类:JIoEndpoint
BIO请求流程:
1. 启动Acceptor线程,创建ServerSocket#acceptor,循环等待请求;
2. 接收到请求后,交由Tomcat线程池,SocketProcessor#run 处理请求;
3. 转换请求,Request、Response,AbstractHttp11Processor#process;#fill从recvBuf获取数据;
4. Adapter#service,准备交由容器进行处理;
5. 流向Engine管道的第一个阀门,StandardEngineValve#invokde;
6. 流向Host管道的第一个阀门,AccessLogValve->ErrorReportValve->StandardHostValve
a. 设置Thread的ContextClassLoader为WebappClassLoader
7. 流向Context管道的第一个阀门,AuthenticatorBase->StandardContextValve
8. 流向Wrapper管道的第一个阀门,StandardWrapperValve
a. 创建Servlet对象,StandardWrapper#allocate
b. 创建过滤器链,执行所有过滤器,ApplicationFilterFactory#createFilterChain,filterChain.doFilter
c. servlet#service
9. HttpServlet#service,判断请求方式,Get还是Post等等
a. 如果是get,执行doGet,执行到业务定义的方法
NIO模式
Tomcat使用I/O多路复用,来代替传统BIO模型,利用少量的线程来管理大量的连接;
为什么有NIO模式?
- BIO模式下一个连接对应一个工作线程,之前默认最多只同时支持200个请求;
- 如果连接建立之后,tomcat会阻塞来获取请求数据,占用工作线程;
- http1.1之后,大部分请求都是长连接,很多情况下tomcat只能干等着请求到来,很多情况下都是等不到后再超时断开连接;
关键类:NioEndpoint、Poller
NIO请求流程
1. NioEndpoint#bind,绑定地址,将socket设为阻塞
2. NioEndpoint#startInternal,启动poller(多核2,单核1个)、acceptor线程
3. ServerSocket#accept接收到请求,设为非阻塞
4. SocketChannel 封装成 NioChannel
5. Poller#register
1. 封装PollerEvent;KeyAttachment.interestOps:OP_REGISTER;intOps:OP_READ,读事件
2. 将PollerEvent添加到事件列表,Poller.events
6. Poller#run -> #events -> PollerEvent#run: 真正将读事件注册到当前poller中的selector对象上
7. Poller#run -> 循环就绪的事件 selector.selectedKeys()
1. #processKey -> 获取NioChannel -> #processSocket,从Socket中读/写数据
2. 分派任务到线程池
Poller
NIO模式的实现类
selector
- 一个Poller对应一个Selector,每个Poller负责轮询自己的Selector上就绪的事件,并处理
events
- 存放PollerEvent的队列
tomcat要socketChannel注册到selector上,但是Tomcat并没有直接这么做,而是先自己生成一个PollerEvent,然后把PollerEvent加入到队列events中,然后这个队列中的事件会在Poller线程的循环过程中真正执行
#run
- #events,循环 events,将事件注册到Select
- #select、#selectKeys,循环就绪的事件,获取socket请求,分派到线程执行
#register
将请求封装成PollerEvent,放到events队列中,等待Poller#run,注册到Selector
PollerEvent
- 是一个线程,impl Runnable
- #run ,将事件注册到Selector上
解析Http报文
http协议分为: 请求行、请求头、请求体
请求行: 空格分割,有请求方法,url,协议版本
请求头: 冒号分割,key value形式
请求体: 前一行只有回车换行,取多少根据content-length决定
NIO知识
Selector
这个类是实现Java IO多路复用的重要类,它的功能在于两方面:
- 非阻塞的Channel可以使用#register方法注册感兴趣的阻塞事件,
- 可以通过
select方法选出已经准备就绪的阻塞事件做进一步的处理
#register
注册阻塞事件;
SelectionKey:
OP_READ;
OP_WRITE;
OP_CONNECT;
OP_ACCEPT;
#select
返回已经就绪的事件数量;
最好指定时间,避免与#register发生死锁;
#selectedKeys
会返回一个就绪的事件集合,List< SelectionKey >,循环处理事件,如socket读取