/**
*作者:annegu
*日期:2009-06-20
*/
在这第三部分里面我们主要看一下tomcat是如何接收客户端请求,并把这个请求一层一层的传递到子容器中,并最后交到应用程序中进行处理的。
首先,我们来了解一下什么叫做NIO和BIO。
在前面的解读tomcat里面,我们已经说到过了线程池。线程池,顾名思义,里面存放了一定数量的线程,这些线程用来处理用户请求。现在我们要讨论的NIO和BIO就是如何分配线程池中的线程来处理用户请求的方式。
BIO(Block IO):阻塞式IO。在tomcat6之前一直都是采用的这种方式。当一个客户端的连接请求到达的时候,ServerSocket.accept负责接收连接,然后会从线程池中取出一个空闲的线程,在该线程读取InputStream并解析HTTP协议,然后把输入流中的内容封装成Request和Response,在线程中执行这个请求的Servlet ,最后把Response的内容发送到客户端连接并关闭本次连接。这就完成了一个客户端的请求过程。我们要注意的是在阻塞式IO中,tomcat是直接从线程池中取出一个线程来处理客户端请求的,那么如果这些处理线程在执行网络操作期间发生了阻塞的话,那么线程将一直阻塞,导致新的连接一直无法分配到空闲线程,得不到响应。
NIO(Non-blocking IO):tomcat中的非阻塞式IO与阻塞式的不同,它采用了一个主线程来读取InputStream。也就是说当一个客户端请求到达的时候,这个主线程会负责从网络中读取字节流,把读入的字节流放入channel中。然后这个主线程就会到线程池中去找有没有空闲的线程,如果找到了,那么就会由空闲线程来负责从channel中取出字节,然后解析Http,转换成request和response,进行处理。当处理线程把要返回给客户端的内容放在Response之后,处理线程就可以把处理结束的字节流也放入channel中,最后主线程会给这个channel加个标识,表示现在需要操作系统去进行io操作,要把这个channel中的内容返回给客户端。这样的话,线程池中的处理线程的任务就集中在如何处理用户请求上了,而把与网络有交互的操作都交给主线程去处理。
对于这个非阻塞式IO,anne我想了一个很有趣的比喻,就好像去饭店吃饭点菜一样。餐馆的接待员就好像我们的操作系统,客人来了,他要负责记下客人的点菜,然后传达给厨房,厨房里面有好几位烧菜厨师(处理线程),主厨(主线程)呢比较懒,不下厨,只负责分配厨师的工作。现在来了一个客人,跟接待员说要吃宫宝鸡丁,然后接待员就写了张纸条,上面写了1号桌客人要点宫宝鸡丁,从厨房柜台上的一摞盘子里面拿了一个空的,把点菜单放在盘子里面。然后主厨就时刻关注这这些盘子,看到有盘子里面有点菜单的,就从厨房里面喊一个空闲的厨子说,你来把这菜给烧一下,这个厨子就从这个盘子里面拿出点菜单去烧菜了,好了这下这个盘子又空了,如果这时候还有客人来,那么接待员还可以把点菜单放到这个盘子里面去。等厨师烧好了菜,他就从柜台上找一个空盘子,把菜盛在里面,贴上纸条说这个是1号桌客人点的宫宝鸡丁。然后主厨看看,嗯,烧的还不错,可以上菜了,就在盘子上贴个字条说这盘菜烧好了,上菜去吧。最后接待员就来了,他一直在这候着呢,看到终于有菜可以上了,赶紧端去。嗯,自我感觉挺形象的,你们说呢?
因此,我们可以分析出tomcat中的阻塞式IO与非阻塞式IO的主要区别就是这个主线程。Tomcat6之前的版本都是只采用的阻塞式IO的方式,服务器接收了客户端连接之后,马上分配处理线程来处理这个连接;tomcat6中既提供了阻塞式的IO,也提供了非阻塞式IO处理方式,非阻塞式IO把接收连接的工作都交给主线程处理,处理线程只关心具体的如何处理请求。
好了,我们现在知道了tomcat是采用非阻塞式IO来分配请求的了。那么接下来我们就可以从发出一个请求开始看看tomcat是怎么把它传递到我们自己的应用程序中的。
程序员最爱看类图了,所以anne画了个类图,我们来照着类图,一个一个类来看。
我们首先从NioEndPoint开始,这个类是实际处理tcp连接的类,它里面包括一个操作线程池,socket接收线程(acceptorThread),socket轮询线程(pollerThread)。
首先我们看到的是start()方法,在这个方法里面我们可以看到启动了线程池,acceptorThread和pollerThread。然后,在这个类中还定义了一些子类,包括SocketProcessor,Acceptor,Poller,Worker,NioBufferHandler等等。SocketProcessor,Acceptor,Poller和Worker都实现了Runnable接口。
我想还是按照接收请求的调用顺序来讲会比较清楚,所以我们从Acceptor开始。
1、Acceptor负责接收socket,一旦得到一个tcp连接,它就会尝试去从nioChannels中去取出一个空闲的nioChannel,然后把这个连接的socket交给它,接着它会告诉轮询线程poller,我这里有个channel已经准备好了,你注意着点,可能不久之后就要有数据过来啦。下面的事情它就不管了,接着等待下一个tcp连接的到来。
我们可以看一下它是怎么把socket交给channel的:
protected boolean setSocketOptions(SocketChannel socket) {
try {
... //ignore
//从nioChannels中取出一个channel
NioChannel channel = nioChannels.poll();
//若没有可用的channel,根据不同情况新建一个channel
if ( channel == null ) {
if (sslContext != null) {
...//ignore
channel = new SecureNioChannel(...);
} else {
...// normal tcp setup
channel = new NioChannel(...);
}
} else {
channel.setIOChannel(socket);
...//根据channel的类型做相应reset
}
getPoller0().register(channel); // 把channel交给poller
} catch (Throwable t) {
try {
return false; // 返回false,关闭socket
}
return true;
}
要说明的是,Acceptor这个类在BIO的endpoint类中也是存在的。对于BIO来说acceptor就是用来接收请求,然后给这个请求分配一个空闲的线程来处理它,所以是起到了一个连接请求与处理线程的作用。现在在NIO中,我们可以看到Acceptor.run()里面是把processSocket(socket);给注释掉了(processSocket这个方法就是分配线程来处理socket的方法,这个anne打算在后面讲)。
2、Poller这个类其实就是我们在前面说到的nio的主线程。它里面也有一个run()方法,在这里我们就会轮询channel啦。看下面的代码:
Iterator iterator = keyCount > 0 ? selector.selectedKeys().iterator() : null;
while (iterator != null && iterator.hasNext()) {
SelectionKey sk = (SelectionKey) iterator.next();
KeyAttachment attachment = (KeyAttachment)sk.attachment();
attachment.access();
iterator.remove();
processKey(sk, attachment);
}
我们可以看到,程序遍历了所有selectedKeys,这个SelectionKey就是一种可以用来读取channel的钥匙。这个KeyAttachment又是个什么类型的对象呢?其实它记录了包括channel信息在内的又与这个channel息息相关的一些附加信息。MS很长的一句话,这么说吧,它里面有channel对象,还有lastAccess(最近一次访问时间),error(错误信息),sendfileData(发送的文件数据)等等。然后在processKey这个方法里面我们就可以把channel里面的字节流交给处理线程去处理了。
然后我们来看一下这个processKey方法:
protected boolean processKey(SelectionKey sk, KeyAttachment attachment) {
boolean result = true;
try {
if ( close ) {
cancelledKey(sk, SocketStatus.STOP, false);
} else if ( sk.isValid() && attachment != null ) {
attachment.access();
sk.attach(attachment);
NioChannel channel = attachment.getChannel();
① if (sk.isReadable() || sk.isWritable() ) {
② if ( attachment.getSendfileData() != null ) {
processSendfile(sk,attachment,true);
} else if ( attachment.getComet() ) {
if ( isWorkerAvailable() ) {
reg(sk, attachment, 0);
if (sk.isReadable()) {
if (!processSocket(channel, SocketStatus.OPEN))
processSocket(channel, SocketStatus.DISCONNECT);
} else {
if (!processSocket(channel, SocketStatus.OPEN))
processSocket(channel, SocketStatus.DISCONNECT);
}
} else {
result = false;
}
} else {
if ( isWorkerAvailable() ) {
unreg(sk, attachment,sk.readyOps());
③ boolean close = (!processSocket(channel));
if (close) {
cancelledKey(sk,SocketStatus.DISCONNECT,false);
}
} else {
result = false;
}
} }
}
} else {
//invalid key
cancelledKey(sk, SocketStatus.ERROR,false);
}
} catch ( CancelledKeyException ckx ) {
cancelledKey(sk, SocketStatus.ERROR,false);
} catch (Throwable t) {
log.error("",t);
}
return result;
}
首先是判断一下这个selection key是否可用,没有超时,然后从sk中取出channel备用。然后看一下这个sk的状态是否是可读的,或者可写的,代码①处。代码②处是返回阶段,要往客户端写数据时候的路径,程序会判断是否有要发送的数据,这部分我们后面再看,先往下看request进来的情况。然后我们就可以在下面看到开始进行真正的处理socket的工作了,代码③处,进入processSocket()方法了。
protected boolean processSocket(NioChannel socket, SocketStatus status, boolean dispatch) {
try {
KeyAttachment attachment = (KeyAttachment)socket.getAttachment(false);
attachment.setCometNotify(false);
if (executor == null) {
④ getWorkerThread().assign(socket, status);
} else {
SocketProcessor sc = processorCache.poll();
if ( sc == null ) sc = new SocketProcessor(socket,status);
else sc.reset(socket,status);
if ( dispatch ) executor.execute(sc);
else sc.run();
}
} catch (Throwable t) {
return false;
}
return true;
}
从④处可以看到明显是取了线程池中的一个线程来操作这个channel,也就是说在这个方法里面我们就开始进入线程池了。那么executor呢?executor可以算是一个配置项,如果使用了executor,那么线程池就使用java自带的线程池,如果不使用executor的话,就使用tomcat的线程池WorkerStack,这个WrokerStack我在后面有专门写它,现在先跳过。我们可以看到在start()方法里面,是这样写的:
if (getUseExecutor()) {
if ( executor == null ) {
executor = new ThreadPoolExecutor(...);
}
} else if ( executor == null ) {
workers = new WorkerStack(maxThreads);
}
好了,现在回到processSocket(),我们先来看有executor的情况,就是使用java自己的线程池。首先从processorCache中取出一个线程socketProcessor,然后把channel交给这个线程,启动线程的run方法。于是我们终于脱离主线程,进入了SocketProcessor的run方法啦!
- 大小: 3.2 KB
- 大小: 4.9 KB
- 大小: 99.2 KB
分享到:
相关推荐
在 Tomcat 中,SSL 连接器是负责处理 SSL 加密连接的组件。需要正确地配置连接器的端口号、协议、证书文件和密码等信息。 再次,需要正确地配置 Tomcat 的 APR 模块。在 Tomcat 6.0.33 版本中,默认启用了 APR 模块...
【标题】:“解析Tomcat处理请求的类Connector<三>” 在Java的Web服务器领域,Tomcat无疑是最为广泛使用的轻量级应用服务器之一。它以其开源、免费、高效的特点深受开发者喜爱。在这个系列的第三部分,我们将深入...
tomcat中server配置文件的结构,以及处理一个http请求的全过程
Apache和Tomcat是两种常用的Web服务器,Apache主要处理静态内容,而Tomcat是Java Servlet和JSP的容器。为了在Apache上运行Java应用,通常会使用一种名为`mod_jk`的模块进行整合。`mod_jk`是Apache HTTP服务器的一个...
### JSP中常见的Tomcat报错错误解析 在Java Server Pages (JSP) 开发过程中,经常遇到Apache Tomcat服务器抛出的各种错误代码。这些错误不仅会影响应用的正常运行,而且有时还会导致服务不可用。因此,理解这些错误...
1. **Apache Tomcat**:Apache Tomcat是开源的Java Servlet容器,支持Servlet和JSP标准,常用于搭建Web服务器,运行Java Web应用。 2. **HTML与JavaScript**:HTML负责网页内容的结构,JavaScript负责网页的动态行为...
1. **Apache Tomcat**:Apache Tomcat是一款开源的Java Servlet容器,用于部署和运行Java Web应用程序。它实现了Java EE的Servlet和JSP规范,是许多开发者和企业的首选Web服务器。 2. **AJP协议**:AJP协议允许Web...
JSP_JSF_Tomcat Web编程从入门到精通》这本书主要涵盖了Java服务器页面(JSP)、JavaServer Faces(JSF)以及Tomcat应用服务器在Web开发中的应用,旨在帮助初学者及有一定基础的开发者深入理解和掌握这三大技术。...
处理request对象涉及多个步骤,包括解析连接、解析请求等。 - **解析连接**:创建Socket连接并读取客户端请求。 - **解析request**:提取请求中的关键信息。 - **解析请求头**:处理请求中的HTTP头信息。 #### 第5...
`org.apache.tomcat.ajp`包提供了处理AJP请求的相关类,使得非Java应用服务器可以与Tomcat共享Java应用。 4. **Tomcat Lang API**: 这可能是Tomcat中特定的实用工具类库,比如在处理国际化、字符串操作、异常处理等...
### Tomcat请求处理UML序列图解析 #### 一、概述 Apache Tomcat是一个开源的Servlet容器,主要用于执行Java Servlet和展示JSP页面。在Tomcat中,HTTP请求的处理流程是一个复杂的过程,涉及到多个组件之间的交互。...
这里`LoadModule`加载了mod_jk模块,`JkWorkersFile`指定了workers.properties的位置,`JkMount`则将所有对`/app`路径的请求转发给名为`worker1`的Tomcat实例处理。 最后,mod_ssl模块使Apache支持HTTPS协议,提供...
当 Apache 收到请求时,它会根据配置判断是否应该转发给 Tomcat 处理,如果是,则通过 AJP 协议将请求发送给 Tomcat,Tomcat 处理完毕后再通过 AJP 返回结果给 Apache,最后由 Apache 将结果发送给客户端。...
这个模块允许IIS将动态请求转发给Tomcat处理。访问http://tomcat.apache.org/download-connectors.cgi,选择JK 1.2的稳定版本进行下载。注意,旧版本可能存在严重安全漏洞,因此建议使用最新稳定版本。 然后,进入...
4. 设置Tomcat,确保它可以处理来自IIS的请求,可能需要配置`server.xml`文件中的Connector元素。 5. 测试整合环境,确保IIS能够正确转发请求至Tomcat,且Tomcat可以返回响应给IIS。 这样的集成方式有助于优化资源...
标题"BBS1.rar_java oracle tomcat_java 后台 oracle_oracle_oracle java to"和描述中的关键词揭示了这个压缩包包含的是一个基于Java、Oracle数据库和Tomcat服务器的后台留言板系统的开发资源。以下是对这些技术栈...
1. **Endpoint 组件**:这是 Tomcat 的网络层面,负责处理连接请求。Endpoint 监听特定端口(如 `8080`),接收客户端通过 TCP/IP 协议发送的 HTTP 请求。它会创建线程来处理每个新的连接,并将接收到的数据传递给下...
1. **Catalina**:这是Tomcat的核心组件,负责Servlet容器的主要功能,如容器管理、生命周期管理和请求处理。Catalina包含Context、Host和Engine等层次结构,它们分别对应于Web应用、虚拟主机和整个服务器。 2. **...
《Tomcat架构解析》这本书由刘光瑞撰写,由人民邮电出版社出版,是一本深入探讨Tomcat服务器架构的专业书籍。Tomcat作为Apache软件基金会的项目之一,是世界上最流行的Java应用服务器,尤其在轻量级Web应用领域,其...
标题“Tomcat分配请求之——Socket获取请求”主要涉及的是Tomcat服务器在处理HTTP请求时的核心机制。在Web服务器中,Tomcat是一个广泛使用的开源应用服务器,它负责解析并响应来自客户端(如浏览器)的HTTP请求。这...