1、HTTP协议的简单介绍
网络通信协议的本质就是规则,软件和硬件必须遵循的共同守则。我们先看下HTTP协议的请求体和响应体是什么样子:
GET /servlet/myServlet HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: Idea-368c88e1=0ab9bad3-7c85-4ff3-8eb9-43b05fdcc241; onstarcar_username=pepsi; onstarcar_token=dd54ae011558e6fd; JSESSIONID=1FABECE8924A8D29B064221474BF165E
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/html
Data: Thu, 27 Dec 2018 08:18:48 GMT
Content-Length: 22
<h1>Pepsi is Cool</h1>
请求体和响应体中的参数含义可以参照这篇文章。
tomcat官方中文解释:一个轻量级应用服务器,是支持运行Servlet/JSP应用程序的容器,运行在jvm上,绑定IP地址并监听TCP端口。那么按这个解释无非就是干了以下2件事情:
1、对于网络请求的接受、处理。
2、请求分发到对应Servlet上进行业务处理、返回结果。
那么下面就看下tomcat对于请求的处理。
2、tomcat容器对请求的处理(NIO)
- 先看下tomcat整体的结构和一些组件介绍(盗来的一张图): 其实可以更直接一点看tomcat中conf/server.xml文件,结构很清晰。这里面最主要的部分是Connector和Container。Connector用于客户端的链接,而针对不同的协议可以有不同的实现方式,如HTTP和AJP等。Container主要作用处理业务逻辑,包含Engine,Host,Context,Wrapper等。这2个组件可以理解为一个对外接受请求,一个对内处理请求交给对应的业务处理(Servlet service方法),组合在一起就可以提供对外服务了。其他的几个组件这里就不介绍了。
|-->Container(Enginer、Wapper等)
tomcat启动时的初始化时是按层级来,如下:Catalina-->Server-->Service|-->executor
|-->Connetor-->ProtocolHandler
基本初始化就这样一个流程(这个流程图是不是很赞?),通过Digester读取server.xml、挨个初始化组件,下面主要以这2个组件为主叙说一下请求的链路。 - Connetor组件:
Connector的功能上面做过描述就是接受网络请求、根据协议转换成我们想要的请求和响应对象。Connetor的初始化是由它的上层Service初始化中拉起(详情见StandardService#startInternal())的。此方法最终的实现是调用了AbstractEndpoint.start()方法,这个方法有2个动作,一个绑定端口bind(),另一个是调用了NioEndpoint#startInternal()方法,下面我们看下NioEndpoint#startInternal()这个方法:public void startInternal() throws Exception { if (!running) { running = true; paused = false; processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE, socketProperties.getProcessorCache()); eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE, socketProperties.getEventCache()); nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE, socketProperties.getBufferPool()); // Create worker collection if ( getExecutor() == null ) { createExecutor(); } initializeConnectionLatch(); // Start poller threads 轮询线程 pollers = new Poller[getPollerThreadCount()]; for (int i=0; i<pollers.length; i++) { pollers[i] = new Poller(); Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i); pollerThread.setPriority(threadPriority); pollerThread.setDaemon(true); pollerThread.start(); } //开启接受线程 startAcceptorThreads(); } }
这个方法执行了如下2个操作:新建唤起Poller线程和开启接受线程,疯狂接受网络请求。这2个玩意组合起来是不是很像生产者和消费模式,还有这里面初始化了很多cache,主要作用就是对象重复使用(通过reset属性值实现),避免频繁GC回收。下面我们看下这acceptor和poller:
Acceptor 接受者:是一个用于接受网络请求,将accept到的SocketChannel封装成NioSocketChannel,再将NioSocketChannel封装成PollerEvent,最后register到poller的队列中。接受和注册的代码如下:
###NioEndpoint.Acceptor#run() if (running && !paused) { //socket给我交了 。 // setSocketOptions() will hand the socket off to // an appropriate processor if successful if (!setSocketOptions(socket)) { closeSocket(socket); } } else { closeSocket(socket); } } ###NioEndpoint.setSocketOptions protected boolean setSocketOptions(SocketChannel socket) { // Process the connection try { //disable blocking, APR style, we are gonna be polling it socket.configureBlocking(false); Socket sock = socket.socket(); socketProperties.setProperties(sock); NioChannel channel = nioChannels.pop(); if (channel == null) { SocketBufferHandler bufhandler = new SocketBufferHandler( socketProperties.getAppReadBufSize(), socketProperties.getAppWriteBufSize(), socketProperties.getDirectBuffer()); if (isSSLEnabled()) { channel = new SecureNioChannel(so