A: telnet 发起连接
x
B: Acceptor 线程接受连接
org.apache.tomcat.util.net.Acceptor
实现了 Runnable 接口, run() 方法内是一个循环。
1 | |
若已达到最大连接数,阻塞;否则通过。
LimitLatch门闩控制最大连接数。1
2
3
4
5
6
7
8
9protected void countUpOrAwaitConnection() throws InterruptedException {
if (maxConnections==-1) {
return;
}
LimitLatch latch = connectionLimitLatch;
if (latch!=null) {
latch.countUpOrAwait();
}
}执行
endpoint.serverSocketAccept()并阻塞;若有连接事件,产生socket。1
2
3
4
5
6
7protected SocketChannel serverSocketAccept() throws Exception {
// 这里是阻塞点, 直到有连接产生
SocketChannel result = serverSock.accept();
// Bug does not affect Windows platform and Unix Domain Socket. Skip the check.
// ...
return result;
}a. 执行
serverSock.accept(),阻塞直到有连接产生。操作文件描述符,完成 tcp 握手,会调用系统层方法
sun.nio.ch.ServerSocketChannelImpl#accept()。b. 其他。
执行
setSocketOptions(),将socket注册到Poller的队列中。org.apache.tomcat.util.net.NioEndpoint#setSocketOptions()`1
2
3
4
5
6
7protected boolean setSocketOptions(SocketChannel socket) {
// Allocate channel and wrapper
NioSocketWrapper socketWrapper = null;
NioChannel channel = null;
NioSocketWrapper newWrapper = new NioSocketWrapper(channel, this);
poller.register(socketWrapper);
}a. 初始化
NioSocketWrapper和NioChannel。
b. 将socket包装成PollerEvent注册给Poller,只关心读事件。回到循环头。
C: Poller 线程监听读事件
org.apache.tomcat.util.net``.NioEndpoint.Poller
实现了 Runnable 接口,循环执行 run() 方法,本质是 Selector。
之前的PollerEvent都存放在events中,是一个同步队列。SynchronizedQueue
1 | |
处理队列中的所有
PollerEvent。把刚建立的连接,真正注册到
Selector,开始监听读事件。用
selector进行一次select,如果有读事件发生,会被select到。如果
select到读事件,遍历所有的读事件,依次调用processKey(sk, socketWrapper)。1
2
3
4protected void processKey(SelectionKey sk, NioSocketWrapper socketWrapper) {
// ...
processSocket(socketWrapper, SocketEvent.OPEN_READ, true);
}1
2
3
4
5
6public boolean processSocket(SocketWrapperBase<S> socketWrapper,
SocketEvent event, boolean dispatch) {
sc = createSocketProcessor(socketWrapper, event);
Executor executor = getExecutor();
executor.execute(sc);
}获取
Executor,提交给工作线程。循环回到 1。
D: 进入工作线程
实现了 Runnable 接口,循环执行 run() 方法。
进入工作线程后,执行org.apache.tomcat.util.net``.NioEndpoint.SocketProcessor#doRun()。
这里有大量代码,最后会执行到org.apache.coyote.http11.Http11Processor#service()。
service()的主要逻辑1
2这里只读取请求头(通过换行符判断),进行请求格式校验;不读请求体。
请求体数据流:从网卡进入到内核 socket 接收缓冲区(RCV BUF);还没有进入用户进程缓冲区(Tomcat)。1
2
3
4
5
6
7
8何时读请求体?
Controller @RequestBody
↓
Spring 调用 inputStream.read()
↓
Tomcat 从内核缓冲区读取 Body 字节
↓
Jackson 转成 Java 对象初始化处理上下文
设置
socketWrapper、重置keepAlive/openSocket/readComplete,并记录请求处理阶段。进入主循环处理一个或多个请求(
keep-alive)循环条件包括:无错误、允许长连接、非异步、未升级、未暂停、
sendfile已完成。解析请求行与请求头
处理不完整读取、HTTP 协议版本识别(
HTTP/1.1、1.0、0.9)、暂停状态、读取超时设置等。1
2socketWrapper.read(buffer)
读取到连续两个换行符\r\n\r\n结束。换行符前面是请求头,后面是请求体。处理
Upgrade升级请求校验升级协议,克隆并补齐请求体,发送
101,切换到升级协议并返回UPGRADING。控制
keep-alive次数执行
getAdapter().service(request, response),并处理各类异常与错误状态。请求收尾与复用
结束输入输出、更新统计、在可复用时
nextRequest(),恢复读超时,处理sendfile。根据最终状态返回
SocketState按错误、异步、升级、
sendfile、openSocket/readComplete等条件返回CLOSED、LONG、OPEN、SENDFILE或UPGRADING。
总结

telnet 发送请求
HTTP1.1 tcp 复用
可以在一个 TCP 连接中连续发送请求。
请求 1 没有响应之前,tomcat 不处理请求 2。请求 1 响应之后,tomcat 才处理请求 2。
HTTP 1.1 同一个连接,是串行、排队、顺序执行!
这两个请求会在同一个线程执行。
HTTP 1.1 同一个连接,是串行、排队、顺序执行!
这两个请求会在同一个线程执行。


HTTP1.0 不能 tcp 复用
只能发一个请求,服务端会主动断开 TCP。

HTTP1.1 分批发送 Header 和 Body
tomcat 完成请求头的解析后,就会派发给 servlet;不必等 body 发送完成。
在 servlet 中,读请求体会阻塞,直到客户端发完。
先发送请求头

tomcat 收到可读事件,工作线程解析请求头。

tomcat 进入
Controller,read 阻塞。
客户端发送请求体

服务端 read 成功。
