本文介绍基于tomcat7中默认的BIO方式
以前说过tomcat通过socket传输 接口请求之路
发送数据
大部分的HTTP请求一般都是长连接,可以从HTTP请求头中看到
一般发送HTTP请求之前会建立一个socket连接。如果tomcat判断需要对这个socket请求进行关闭,会在HTTP请求的响应头中写入Connection:close,当HTTP得到这样的响应就会关闭对应的连接。如果tomcat判断这个连接可以继续使用,保持长连接,响应头就不设置对应的内容,则会继续使用Connection:keep-alive。
注意,如果关闭的话是http发起关闭,而不是tomcat进行关闭
长连接表示请求完成后socket连接不关闭,还可以继续通过这个对应的socket连接继续发送请求。
发送http请求前需要建立一个socket连接,长连接表示请求完成后socket连接不关闭,还可以通过这个socket连接继续发送请求。如果tomcat如果这个socket请求需要关闭,那么http响应头中Connection:close。这样接收完响应之后就会关闭socke连接,如果保持长连接,http响应头就不设置。
接收数据
- Tomcat通过Processor(所有协议处理器的通用接口)处理请求。
- Processor内部会设置Endpoint,并通过Endpoint的内部类Acceptor接收socket连接。
- 客户端发送数据,在服务端的操作系统中有个RecvBuf(缓冲区),操作系统会将数据先放在recvBufferbyte中,如果recvbuf中填满了,就无法发送数据了。每个socket对应一个缓冲区。当客户端写socket的发送数据是,也会放在缓冲区sendbuf中。
参考如下源码内容:
- AbstractHttp11Processor类中的processSocketWapper方法
- JIOEndpoint类中有个Acceptor方法,会通过BIO的方式接受socket
- Socket=serverSocketFactory.accept
- setSocketOption设置socket参数的方法中有个soTimeout参数,这个参数是指当socke从recvbuf中read数据时,如果recvbuf中数据为空,则read阻塞,具体阻塞时间就是这个参数指定的时间。
- 第一次从socket中获取到数据写入InputBuffer中,然后进行解析对应的请求行(请求头),请求方法,请求协议等放入到Request中,并且设置一些参数。
处理socket
- BIO的方式,每接收到一个socket就将其交给一个线程,在tomcat的BIO里是每个请求都有一个对应的线程进行处理,当socket关闭后,这个线程也会被释放。
- NIO则是一个线程处理多个请求。
参考如下源码内容:
- JIoEndpoint类中的prcocessSocket方法的getExecutor.execute
- 在BIO的方式中最大请求数maxConnection和最大线程数MaxThread实际是一致的
- 在调用线程处理时根据run方法的process调用中的的内容,找到AbstarctProtocol中的process方法,方法会发现首先要调用createProcessor创建一个处理,根据不同的实现类里面设置了不同的属性,这个属性也可以通过标签来设置。maxKeepAliveRequests属性,表示这个长连接上能够处理的最大活动数(http请求)默认100
- 创建处理器完成后,调用对应process方法,解析http请求中的数据。
- 如果maxKeepAliveRequests==1,那么处理完后连接就会被关闭,意味着对应的设置keepalive=false
- 对应的判断,如,当前活跃的线程数占线程池最大线程数的比例大于 75%,那么则关闭KeepAlive,不再支持长连接,不支持长连接并不表示不支持连接处理,意味着75%之后的ssocket连接将会被当成短链接处理。但是当超过**100%**的socket连接数时,将不再处理后续的socke连接。
- getInputBuffer().parseRequest和getInputBuffer().parseHeaders()读取请求体和请求头
- 调用adapter进行服务处理,调用fill等一系列方法。
fill方法是将操作系统中的revfbuf读取到tomcat的缓冲区buf中(默认8kb)
- 处理过程中会将对应的内容放入响应response中,返回给客户端。同样的也是先放在缓冲区,最后将缓冲区的数据返回。
- 最后判断数据是否响应完成,把剩余的数据清除完,获取下个请求。
public SocketState process(SocketWrapper<S> socketWrapper)
throws IOException {
RequestInfo rp = request.getRequestProcessor();
// 设置请求状态为解析状态
rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);
// 设置IO
setSocketWrapper(socketWrapper);
//设置输入、输出缓冲区
//将socket的InputStream与InternalInputBuffer进行绑定(缓冲区内容)
getInputBuffer().init(socketWrapper, endpoint);
// 将socket的OutputStream与InternalOutputBuffer进行绑定
getOutputBuffer().init(socketWrapper, endpoint);
// 长连接等一系列标志
keepAlive = true;
// NioEndpoint返回true, BIO返回false
if (endpoint.getUsePolling()) {
keptAlive = false;
} else {
keptAlive = socketWrapper.isKeptAlive();
}
// 如果当前活跃的线程数占线程池最大线程数的比例大于75%(默认值),那么则关闭KeepAlive,不再支持长连接
if (disableKeepAlive()) {
socketWrapper.setKeepAliveLeft(0);
}
// keepAlive的值会从请求中读取,默认为true
while () {
// keepAlive如果为true,接下来需要从socket中不停的获取http请求
// 解析请求头
try {
// 第一次从socket中读取数据,并设置socket的读取数据的超时时间
// 对于BIO,一个socket连接建立好后,不一定马上就被Tomcat处理了,其中需要线程池的调度,所以这段等待的时间要算在socket读取数据的时间内;而对于NIO而言,没有阻塞
//第一次连接的时候用,超时时间=真实的时间-socket建立的时间
setRequestLineReadTimeout();
// 解析请求行
if (!getInputBuffer().parseRequestLine(keptAlive)) {
}
if (endpoint.isPaused()) {
//如果Endpoint被暂停了,则返回503
} else {
keptAlive = true;
// 每次处理一个请求就重新获取一下请求头和cookies的最大限制
request.getMimeHeaders().setLimit(endpoint.getMaxHeaderCount());//默认100
request.getCookies().setLimit(getMaxCookieCount());//默认200 }
}
//如果最大的保持连接请求数量==1,表示只允许一次请求
if (maxKeepAliveRequests == 1) {
keepAlive = false;//长连接参数直接设为false
} else if (maxKeepAliveRequests > 0 &&
socketWrapper.decrementKeepAlive() <= 0) {
// 如果已经达到了keepAlive的最大限制,也设置为false,则不会继续从socket中获取Http请求了
keepAlive = false;
}
//在适配器开始处理请求
if (!getErrorState().isError()) {
try {
rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE); // 设置请求状态为服务状态,表示正在处理请求
adapter.service(request, response); // 处理请求
}
}
// 完成请求处理
rp.setStage(org.apache.coyote.Constants.STAGE_ENDINPUT); // 设置请求的状态为处理请求结束
if (!isAsync() && !comet) {
// 当前http请求已经处理完了,做一些收尾工作
endRequest();
}
rp.setStage(org.apache.coyote.Constants.STAGE_ENDOUTPUT); // 请求状态为输出结束
if (!isAsync() && !comet || getErrorState().isError()) {
if (getErrorState().isIoAllowed()) {
// 准备处理下一个请求
getInputBuffer().nextRequest();
getOutputBuffer().nextRequest();
}
}
rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE);
// 如果处理完当前这个Http请求之后,发现socket里没有下一个请求了,那么就退出当前循环
// 如果是keepalive,就不会关闭socket, 如果是close就会关闭socket
// 对于keepalive的情况,因为是一个线程处理一个socket,当退出这个while后,当前线程就会介绍,
// 当时对于socket来说,它仍然要继续介绍连接,所以又会新开一个线程继续来处理这个socket
if (breakKeepAliveLoop(socketWrapper)) {
break;
}
}
rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
}