一、连接器介绍
在开始Connector探索之路之前,先看看Connector几个关键字
- NIO:Tomcat可以利用Java比较新的NIO技术,提升高并发下的Socket性能
- AJP:Apache JServ Protocol,AJP的提出当然还是为了解决java亘古不变的问题——性能,AJP协议是基于包的长连接协议,以减少前端Proxy与Tomcat连接Socket连接创建的代价,目前Apache通过JK和AJP_ROXY的方式支持AJP协议,需要注意的是,虽然Nginx作为代理服务器性能强劲,但其只能通过HTTP PROXY的方式与后端的Tomcat联系,因此如果从作为代理服务器的角度上讲,在这种情况下Nginx未必会比Apache体现出更优的性能
- APR/Native:Apache Portable Runtime,还是一个词,性能。APR的提出利用Native代码更好地解决性能问题,更好地与本地服务器(linux)打交道。让我们看看Tomcat文档对APR的介绍
These features allows making Tomcata general purpose webserver, will enable much better integration with other native web technologies, and overall make Java much more viable as a full fledged webserver platform. rather than simply a backend focused technology.
通过对如上名词的组合,Tomcat组成了如下的Connector系列:
- Http11Protocol:支持HTTP1.1协议的连接器
- Http11NioProtocol:支持HTTP1.1 协议+ NIO的连接器
- Http11AprProtocol:使用APR技术处理连接的连接器
- AjpProtocol:支持AJP协议的连接器
- AjpAprProtocol:使用APR技术处理连接的连接器
二、范例
我们以最简单的Http11Protocol为例,看看从请求进来到处理完毕,连接器部件是处理处理的。首先我们利用Tomcat组件组成我们一个最简单的WebServer,其具备如下功能:
- 监停某个端口,接受客户端的请求,并将请求分配给处理线程
- 处理线程处理请求,分析HTTP1.1请求,封装Request/Response对象,并将请求由请求处理器处理
- 实现最简单的请求处理器,向客户端打印Hello World
代码非常简单,首先是主功能(这里,我们利用JDK5.0的线程池,连接器不再管理线程功能):
- packageray.tomcat.test;
- importjava.util.concurrent.BlockingQueue;
- importjava.util.concurrent.LinkedBlockingQueue;
- importjava.util.concurrent.ThreadPoolExecutor;
- importjava.util.concurrent.TimeUnit;
- importorg.apache.coyote.http11.Http11Protocol;
- publicclassTomcatMainV2
- {
- publicstaticvoidmain(String[]args)throwsException
- {
- Http11Protocolprotocol=newHttp11Protocol();
- protocol.setPort(8000);
- ThreadPoolExecutorthreadPoolExecutor=createThreadPoolExecutor();
- threadPoolExecutor.prestartCoreThread();
- protocol.setExecutor(threadPoolExecutor);
- protocol.setAdapter(newMyHandler());
- protocol.init();
- protocol.start();
- }
- publicstaticThreadPoolExecutorcreateThreadPoolExecutor()
- {
- intcorePoolSize=2;
- intmaximumPoolSize=10;
- longkeepAliveTime=60;
- TimeUnitunit=TimeUnit.SECONDS;
- BlockingQueue<Runnable>workQueue=newLinkedBlockingQueue<Runnable>();
- ThreadPoolExecutorthreadPoolExecutor=newThreadPoolExecutor(
- corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue);
- returnthreadPoolExecutor;
- }
- }
请求处理器向客户端打引Hello World,代码如下
- packageray.tomcat.test;
- importjava.io.ByteArrayOutputStream;
- importjava.io.OutputStreamWriter;
- importjava.io.PrintWriter;
- importorg.apache.coyote.Adapter;
- importorg.apache.coyote.Request;
- importorg.apache.coyote.Response;
- importorg.apache.tomcat.util.buf.ByteChunk;
- importorg.apache.tomcat.util.net.SocketStatus;
- publicclassMyHandlerimplementsAdapter
- {
- //支持Comet,Servlet3.0将对Comet提供支持,Tomcat6目前是非标准的实现
- publicbooleanevent(Requestreq,Responseres,SocketStatusstatus)
- throwsException
- {
- System.out.println("event");
- returnfalse;
- }
- //请求处理
- publicvoidservice(Requestreq,Responseres)throwsException
- {
- System.out.println("service");
- ByteArrayOutputStreambaos=newByteArrayOutputStream();
- PrintWriterwriter=newPrintWriter(newOutputStreamWriter(baos));
- writer.println("HelloWorld");
- writer.flush();
- ByteChunkbyteChunk=newByteChunk();
- byteChunk.append(baos.toByteArray(),0,baos.size());
- res.doWrite(byteChunk);
- }
- }
运行主程序,在浏览器中输入http://127.0.0.1:8000,我们可以看到打印”Hello World”
三、分析
以如上Http11Protocol为例,我们可以看到,Tomcat实现一个最简单的处理Web请求的代码其实非常简单,其主要包括如下核心处理类:
- Http11Protocol:Http1.1协议处理入口类,其本身没有太多逻辑,对请求主要由JIoEndPoint类处理
- Http11Protocol$Http11ConnectionHandler:连接管理器,管理连接处理队列,并分配Http11Processor对请求进行处理
- Http11Processor:请求处理器,负责HTTP1.0协议相关的工作,包括解析请求和处理响应信息,并调用Adapter做实际的处理工作,如上我们看到了我们自定义的Adapter实现响应”Hello World”
- JIoEndPoint:监停端口,启动接受线程准备接收请求,在请求接受后转给工作线程处理
- JIoEndPoint$Acceptor:请求接收器,接收后将Socket分配给工作线程继续后续处理
- JIoEndPoint$Worker:工作线程,使用Handler来处理请求,对于我们的HTTP1.1协议来说,其实现是Http11Protocol$Http11ConnectionHandler。这部分不是必须的,也可以选择JDK的concurrent包的线程池
实际上各种连接器实现基本大同小异,基本上都是由如上部分组合而成
1.初始化:首先,还是从入口开始,先看看初始化init
- publicvoidinit()throwsException{
- endpoint.setName(getName());
- endpoint.setHandler(cHandler);//请求处理器,对于HTTP1.1协议,是Http11Protocol$Http11ConnectionHandler
- //初始化ServerSocket工厂类,如果需SSL/TLS支持,使用JSSESocketFactory/PureTLSSocketFactory
- ...(略)
- //主要的初始化过程实际是在endpoint(JIoEndpoint)
- endpoint.init();
- ...(略)
- }
Http11Protocol的初始化非常简单,准备好ServerSocket工厂,调用JIoEndPoint的初始化。让我们接下来看看JIoEndPoint的初始化过程
- publicvoidinit()
- throwsException{
- if(initialized)
- return;
- //Initializethreadcountdefaultsforacceptor
- //请求接收处理线程,这个值实际1已经足够
- if(acceptorThreadCount==0){
- acceptorThreadCount=1;
- }
- if(serverSocketFactory==null){
- serverSocketFactory=ServerSocketFactory.getDefault();
- }
- //创建监停ServerSocket,port为监听端口,address为监停地址
- //backlog为连接请求队列容量(@parambackloghowmanyconnectionsarequeued)
- if(serverSocket==null){
- try{
- if(address==null){
- serverSocket=serverSocketFactory.createSocket(port,backlog);
- }else{
- serverSocket=serverSocketFactory.createSocket(port,backlog,address);
- }
- }catch(BindExceptionbe){
- thrownewBindException(be.getMessage()+":"+port);
- }
- }
- //if(serverTimeout>=0)
- //serverSocket.setSoTimeout(serverTimeout);
- initialized=true;
- }
可以看到,监停端口在此处准备就绪
- publicvoidstart()
- throwsException{
- //Initializesocketifnotdonebefore
- if(!initialized){
- init();
- }
- if(!running){
- running=true;
- paused=false;
- //Createworkercollection
- //初始化工作线程池,有WorkerStack(Tomcat自实现)和Executor(JDKconcurrent包)两种实现
- if(executor==null){
- workers=newWorkerStack(maxThreads);
- }
- //Startacceptorthreads
- //启动请求连接接收处理线程
- for(inti=0;i<acceptorThreadCount;i++){
- ThreadacceptorThread=newThread(newAcceptor(),getName()+"-Acceptor-"+i);
- acceptorThread.setPriority(threadPriority);
- acceptorThread.setDaemon(daemon);//设置是否daemon参数,默认为true
- acceptorThread.start();
- }
- }
- }
2.准备好连接处理:初始化完毕,准备好连接处理,准备接收连接上来,同样的,Http11Protocol的start基本没干啥事,调用一下JIoEndPoint的start,我们来看看JIoEndPoint的start
- publicvoidstart()
- throwsException{
- //Initializesocketifnotdonebefore
- if(!initialized){
- init();
- }
- if(!running){
- running=true;
- paused=false;
- //Createworkercollection
- //初始化工作线程池,有WorkerStack(Tomcat自实现)和Executor(JDKconcurrent包)两种实现
- if(executor==null){
- workers=newWorkerStack(maxThreads);
- }
- //Startacceptorthreads
- //启动请求连接接收处理线程
- for(inti=0;i<acceptorThreadCount;i++){
- ThreadacceptorThread=newThread(newAcceptor(),getName()+"-Acceptor-"+i);
- acceptorThread.setPriority(threadPriority);
- acceptorThread.setDaemon(daemon);//设置是否daemon参数,默认为true
- acceptorThread.start();
- }
- }
- }
主要处理的事情无非就是准备和工作线程(处理具体请求的线程度池,可选,也可以使用JDK5.0的线程池),连接请求接收处理线程(代码中,一般acceptorThreadCount=1)
3.连接请求接收处理:准备就绪,可以连接入请求了。现在工作已经转到了Acceptor(JIoEndPoint$Acceptor)这里,我们看看Acceptor到底做了些啥
- publicvoidrun(){
- //Loopuntilwereceiveashutdowncommand
- while(running){
- ...(略)
- //阻塞等待客户端连接
- Socketsocket=serverSocketFactory.acceptSocket(serverSocket);
- serverSocketFactory.initSocket(socket);
- //Handthissocketofftoanappropriateprocessor
- if(!processSocket(socket)){
- //Closesocketrightaway
- try{
- socket.close();
- }catch(IOExceptione){
- //Ignore
- }
- }
- ...(略)
- }
- }
- ...(略)
- protectedbooleanprocessSocket(Socketsocket){
- try{
- //由工作线程继续后续的处理
- if(executor==null){
- getWorkerThread().assign(socket);
- }else{
- executor.execute(newSocketProcessor(socket));
- }
- }catch(Throwablet){
- ...(略)
- returnfalse;
- }
- returntrue;
- }
实际上也没有什么复杂的工作,无非就是有连接上来之后,将连接转交给工作线程(SocketProcessor)去处理
4.工作线程:SocketProcessor
- publicvoidrun(){
- //Processtherequestfromthissocket
- if(!setSocketOptions(socket)||!handler.process(socket)){
- //Closesocket
- try{
- socket.close();
- }catch(IOExceptione){
- }
- }
- //Finishupthisrequest
- socket=null;
- }
工作线程主要是设置一下Socket参数,然后将请求转交给handler去处理,需要注意一下如下几个连接参数的意义:
- SO_LINGER:若设置了SO_LINGER并确定了非零的超时间隔,则closesocket()调用阻塞进程,直到所剩数据发送完毕或超时。这种关闭称为“优雅的”关 闭。请注意如果套接口置为非阻塞且SO_LINGER设为非零超时,则closesocket()调用将以WSAEWOULDBLOCK错误返回。若在一个流类套接口上设置了SO_DONTLINGER,则closesocket()调用立即返回。但是,如果可能,排队的数据将在套接口关闭前发送。请注意,在这种情况下WINDOWS套接口实现将在 一段不确定的时间内保留套接口以及其他资源(TIME_WAIT),这对于想用所以套接口的应用程序来说有一定影响。默认此参数不打开
- TCP_NODELAY:是否打开Nagle,默认打开,使用Nagle算法是为了避免多次发送小的分组,而是累计到一定程度或者超过一定时间后才一起发送。对于AJP连接,可能需要关注一下这个选项。
- SO_TIMEOUT:JDK API注释如下,With this option set to a non-zero timeout,a read() call on the InputStream associated with this Socket will block for only this amount of time. If the timeout expires, a java.net.SocketTimeoutException is raised, though the Socket is still valid. The option must be enabled prior to entering the blocking operation to have effect. The timeout must be > 0。默认设置的是60秒
关于默认的设置,可以参见org.apache.coyote.http11.Constants定义
5.最终请求终于回到了Handler,此处的Handler实现是org.apache.coyote.http11.Http11Processor,其主要处理一些HTTP协议性细节的东西,此处代码不再列出,有兴趣可以自行读代码。最终请求终于回到了我们的Adapter对象,一个请求处理完毕,功德圆满。
(转载自:http://1632004.blog.163.com/blog/static/29991497201201912858468)