u014731752的专栏



/** *作者: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的:

Java代码

protected boolean setSocketOptions(SocketChannel socket) {try {… //ignore//从nioChannels中取出一个channelNioChannel channel = nioChannels.poll();//若没有可用的channel,根据不同情况新建一个channelif ( channel == null ) {if (sslContext != null) {…//ignorechannel = new SecureNioChannel(…);} else {…// normal tcp setupchannel = 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啦。看下面的代码:

那段岁月,无论从何种角度读你,你都完美无缺,

u014731752的专栏

相关文章:

你感兴趣的文章:

标签云: