tomcat源码阅读之connector

概述(是什么?)

    Tomcat的实现主要是依靠各种组件的配合使用,而连接器负责处理与客户端的通信。tomcat提供了多种连接器,可以在server.xml配置文件中指定想要使用的连接器,例如以下配置:

<Connector port="8080" protocol="org.apache.coyote.http11.Http11Nio2Protocol"  
        connectionTimeout="20000"
        redirectPort="8443"
        executor="tomcatThreadPool"
        enableLookups="false"
        acceptCount="100"
        maxPostSize="1024"
        compression="on"
        disableUploadTimeout="true"
        compressionMinSize="2048"
        acceptorThreadCount="1"
        compressableMimeType="text/html,text/xml,text/plain,text/css,text/javascript,application/javascript"   
        URIEncoding="utf-8"/>

    在tomcat8版本中,主要使用的是coyote包中提供的一些连接器:Http11NioProtocol、Http11AprProtocol等等,今天主要分析tomcat8默认的连接器--Http11NioProtocol。

    连接器的主要作用是接收请求,创建request与response,然后将请求交给processor处理,processor可以调用servlet中的处理逻辑,然后将返回的结果写入response,再响应客户端。以上是旧版本(4.0)的基本的逻辑,8.0版本的比这个逻辑要复杂许多,但是本质还是在干着几件事。接下来根据源码详细分析连接器的原理。

Connector(org.apache.catalina.connector.Connector)

    tomcat源码中connector既是连接器的最外层表示,首先来分析一下她的构造方法:

/**
 * Coyote Protocol handler class name.
 * Defaults to the Coyote HTTP/1.1 protocolHandler.
 * 默认的protocolHandler
 */
protected String protocolHandlerClassName =
	"org.apache.coyote.http11.Http11NioProtocol";

public Connector() {
	this(null);
}

//Implementation of a Coyote connector
//实现了coyote连接器
public Connector(String protocol) {
	setProtocol(protocol);
	// Instantiate protocol handler
	//协议处理器的实现
	ProtocolHandler p = null;
	try {
        //通过protocolHandlerClassName名称反射获取到class
		Class<?> clazz = Class.forName(protocolHandlerClassName);
        //实例化ProtocolHandler
		p = (ProtocolHandler) clazz.getConstructor().newInstance();
	} catch (Exception e) {
		log.error(sm.getString(
				"coyoteConnector.protocolHandlerInstantiationFailed"), e);
	} finally {
        //将实例化的handler保存在属性protocolHandler中
		this.protocolHandler = p;
	}

	if (Globals.STRICT_SERVLET_COMPLIANCE) {
		uriCharset = StandardCharsets.ISO_8859_1;
	} else {
		uriCharset = StandardCharsets.UTF_8;
	}
}

    在实例化connector时,如果传送的protocol属性为空,这个时候connector会获取默认的protocolHandlerClassName来实例化protocolHandler。

    接着在来分析connector是怎么初始化的,首先需要明确一个概念,tomcat中所有组件的生命周期都是由Lifecycle这个类来控制,connector也实现了这个类,该类的init、start、stop、destroy控制着connector的一生。LifecycleBase是抽象类,实现了Lifecycle的这些方法,她将init方法的核心逻辑封装进了initInternal方法(该方法可由子类实现),同样start方法的核心逻辑在startInternal中,所以,我们只要看一下connector的这两个方法是怎么实现的就知道,连接器是怎么初始化的。代码如下:

protected void initInternal() throws LifecycleException {

    //调用父类初始化方法将Connector名称注册到MBeanServer中托管。
	super.initInternal();

	// Initialize adapter
	//初始化coyote适配器,在后续处理请求时需要用到
	adapter = new CoyoteAdapter(this);
	protocolHandler.setAdapter(adapter);

	// Make sure parseBodyMethodsSet has a default
	//保证有一个请求方式 默认是post
	if (null == parseBodyMethodsSet) {
		setParseBodyMethods(getParseBodyMethods());
	}

	if (protocolHandler.isAprRequired() && !AprLifecycleListener.isAprAvailable()) {
		throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoApr",
				getProtocolHandlerClassName()));
	}
	if (AprLifecycleListener.isAprAvailable() && AprLifecycleListener.getUseOpenSSL() &&
			protocolHandler instanceof AbstractHttp11JsseProtocol) {
		AbstractHttp11JsseProtocol<?> jsseProtocolHandler =
				(AbstractHttp11JsseProtocol<?>) protocolHandler;
		if (jsseProtocolHandler.isSSLEnabled() &&
				jsseProtocolHandler.getSslImplementationName() == null) {
			// OpenSSL is compatible with the JSSE configuration, so use it if APR is available
			jsseProtocolHandler.setSslImplementationName(OpenSSLImplementation.class.getName());
		}
	}

	try {
	    //protocolHandler的初始化,重点
		protocolHandler.init();
	} catch (Exception e) {
		throw new LifecycleException(
				sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);
	}
}

    从代码中可以看出,初始化方法的核心是protocolHandler的初始化,而protocolHandler的初始化主要是在AbstractProtocol类的init方法中实现,其代码如下:

public void init() throws Exception {
	if (getLog().isInfoEnabled()) {
		getLog().info(sm.getString("abstractProtocolHandler.init", getName()));
	}

	if (oname == null) {
		// Component not pre-registered so register it
		oname = createObjectName();
		if (oname != null) {
			Registry.getRegistry(null, null).registerComponent(this, oname, null);
		}
	}

	if (this.domain != null) {
		rgOname = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getName());
		Registry.getRegistry(null, null).registerComponent(
				getHandler().getGlobal(), rgOname, null);
	}

	String endpointName = getName();
	endpoint.setName(endpointName.substring(1, endpointName.length()-1));
	endpoint.setDomain(domain);

	//重点是endpoint的初始化
	endpoint.init();
}

通过代码上的注解可以知道这段代码的核心是endpoint的初始化,而endpoint的初始化在AbstractEndpoint的init方法中实现:

public void init() throws Exception {
	if (bindOnInit) {
		//重点 socket服务监听
		bind();
		bindState = BindState.BOUND_ON_INIT;
	}
	if (this.domain != null) {
		// Register endpoint (as ThreadPool - historical name)
		oname = new ObjectName(domain + ":type=ThreadPool,name=\"" + getName() + "\"");
		Registry.getRegistry(null, null).registerComponent(this, oname, null);

		ObjectName socketPropertiesOname = new ObjectName(domain +
				":type=ThreadPool,name=\"" + getName() + "\",subType=SocketProperties");
		socketProperties.setObjectName(socketPropertiesOname);
		Registry.getRegistry(null, null).registerComponent(socketProperties, socketPropertiesOname, null);

		for (SSLHostConfig sslHostConfig : findSslHostConfigs()) {
			registerJmx(sslHostConfig);
		}
	}
}

终于看到熟悉的东西了,bind方法,清楚的告诉我们终于开始重要的socket的监听的初始化了。代码如下:

/**
 * Initialize the endpoint.
 * 实例化endpoint
 */
@Override
public void bind() throws Exception {

	if (!getUseInheritedChannel()) {
		//ServerSocketChannel(jdk nio包中的对象)socket服务管道打开
		serverSock = ServerSocketChannel.open();
		//socket配置
		socketProperties.setProperties(serverSock.socket());
		//socket监听地址+端口,默认监听本地地址+端口
		InetSocketAddress addr = (getAddress()!=null?new InetSocketAddress(getAddress(),getPort()):new InetSocketAddress(getPort()));
		//ServerSocketChannel绑定端口,并接收连接的数量
		serverSock.socket().bind(addr,getAcceptCount());
	} else {
		// Retrieve the channel provided by the OS
		Channel ic = System.inheritedChannel();
		if (ic instanceof ServerSocketChannel) {
			serverSock = (ServerSocketChannel) ic;
		}
		if (serverSock == null) {
			throw new IllegalArgumentException(sm.getString("endpoint.init.bind.inherited"));
		}
	}
	//配置accept为阻塞模式
	serverSock.configureBlocking(true); //mimic APR behavior

	// Initialize thread count defaults for acceptor, poller
	//初始化acceptor与poller线程数
	if (acceptorThreadCount == 0) {
		// FIXME: Doesn't seem to work that well with multiple accept threads
		acceptorThreadCount = 1;
	}
	if (pollerThreadCount <= 0) {
		//minimum one poller thread
		pollerThreadCount = 1;
	}
	//处理线程计数器,初始化
	setStopLatch(new CountDownLatch(pollerThreadCount));

	// Initialize SSL if needed
	initialiseSsl();

	//开启NioBlockingSelector
	selectorPool.open();
}

    可以看出bind中完成了,最核心的初始化任务,socket监听服务ip端口绑定,acceptor(请求接收者线程)与poller(轮询处理线程)线程数的确定,stopLatch线程计数器的实例化,selectorPool(NioSelectorPool)的开启。

    注意到selectorPool这个池子,其具有两个属性:NioBlockingSelector和Selector(这个是个共享selector,selector是多路复用对象,她可以与内核进行沟通,告知用户线程socket准备好读了),进入NioBlockingSelector类发现,其仍然拥有一个selector与blockpoller(这个selector其实就是selectorPool的共享selector),进入blockpoller发现,还是拥有selector(这个selector其实就是NioBlockingSelector的共享selector)和一个保存事件的queue。如果将poller中的selector作为主,则这个blockpoller为辅助,当poller的主selector获取到准备好的请求,开始处理该请求,处理完成后,需要将响应内容返回给客户端,这个辅助blockpoller就是做这么一件事:轮询内核,是否准备好将response返回给客户端,如果准备好了就将请求响应返回。

    poller是在请求(socket)接收后,具体处理之前,存放待处理请求的线程,其线程数一般为计算机的cpu数量,poller线程会轮询(selector会与内核沟通轮询socket状态)待处理的读写请求,如果内核准备好数据了,就开始将内核的数据copy到用户线程,接着开始请求的解析,处理,返回。这里有必要讲解一下I/O多路复用的原理,tomcat的I/O请求使用了该模型。

    首先需要解释一下,线程分为好几种状态,这里涉及到用户态和内核态,网络I\O读写其实本质上是由内核线程在做,那么将数据从内核线程复制到用户线程就是中间的桥梁,这就是selector,连接器接收到请求后,马上会响应(三次握手成功),然后客户端发送请求报文,连接器处理请求时,会阻塞在select方法上,直到轮询到内核线程发出一个标识:接收到了完整的请求可以开始数据的读取了,这个时候,socket准备好读取请求的数据了,数据可以read了,数据read后就可以进行请求的进一步解析。从这个过程可以了解到,单个的用户线程,可以接受多个的tcp请求,这便是多路复用。

    init完毕后就开始start了,接下来看一下connector的startInternal方法:

/**
 * Begin processing requests via this Connector.
 *
 * @exception LifecycleException if a fatal startup error occurs
 */
@Override
protected void startInternal() throws LifecycleException {

	// Validate settings before starting
	if (getPort() < 0) {
		throw new LifecycleException(sm.getString(
				"coyoteConnector.invalidPort", Integer.valueOf(getPort())));
	}

	setState(LifecycleState.STARTING);

	try {
		//重点 protocolHandler的开始。
		protocolHandler.start();
	} catch (Exception e) {
		throw new LifecycleException(
				sm.getString("coyoteConnector.protocolHandlerStartFailed"), e);
	}
}

    同样的套路,connector.start的重点是protocolHandler的start,接下来直接贴代码有些不再详细讲解了。

//abstractProtocol.java

@Override
public void start() throws Exception {
	if (getLog().isInfoEnabled()) {
		getLog().info(sm.getString("abstractProtocolHandler.start", getName()));
	}

    //重点 endpoint的开始
	endpoint.start();

	// Start async timeout thread
    // 开启一个异步的超时线程
	asyncTimeout = new AsyncTimeout();
	Thread timeoutThread = new Thread(asyncTimeout, getNameInternal() + "-AsyncTimeout");
	int priority = endpoint.getThreadPriority();
	if (priority < Thread.MIN_PRIORITY || priority > Thread.MAX_PRIORITY) {
		priority = Thread.NORM_PRIORITY;
	}
	timeoutThread.setPriority(priority);
	timeoutThread.setDaemon(true);
	timeoutThread.start();
}
//abstractendpoint.java

public final void start() throws Exception {
    //检查绑定状态,防止未绑定就开启连接器服务
	if (bindState == BindState.UNBOUND) {
		bind();
		bindState = BindState.BOUND_ON_START;
	}
    //重点方法
	startInternal();
}
/**
 * Start the NIO endpoint, creating acceptor, poller threads.
 * 开始非阻塞endpoint,创建接收者线程,轮询处理线程
 */
@Override
public void startInternal() throws Exception {

	if (!running) {
		running = true;
		paused = false;

		//Cache for SocketProcessor objects
		//缓存请求具体处理方法(当poller轮询完毕,数据copy完毕,接着处理请求时的处理方法)
		processorCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
				socketProperties.getProcessorCache());
		//Cache for poller events
		//缓存poller的事件(socket请求包装在里面)。
		eventCache = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
						socketProperties.getEventCache());
		//Bytebuffer cache, each channel holds a set of buffers (two, except for SSL holds four)
		//客户端请求的具体实现类。
		nioChannels = new SynchronizedStack<>(SynchronizedStack.DEFAULT_SIZE,
				socketProperties.getBufferPool());

		// Create worker collection
		// 创建工作者
		if ( getExecutor() == null ) {
			createExecutor();
		}

		//初始化连接限制 accept有限制最大连接数
		initializeConnectionLatch();

		// Start poller threads
		// 开启poller线程
		pollers = new Poller[getPollerThreadCount()];
		for (int i=0; i<pollers.length; i++) {
			pollers[i] = new Poller();
			Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);
			pollerThread.setPriority(threadPriority);
			pollerThread.setDaemon(true);
            //thread.start 其实调用的是poller类的run方法
			pollerThread.start();
		}

		//开启accept线程
		startAcceptorThreads();
	}
}

//abstractendpoint.java
protected final void startAcceptorThreads() {
	int count = getAcceptorThreadCount();
	acceptors = new Acceptor[count];

	for (int i = 0; i < count; i++) {
		acceptors[i] = createAcceptor();
		String threadName = getName() + "-Acceptor-" + i;
		acceptors[i].setThreadName(threadName);
		Thread t = new Thread(acceptors[i], threadName);
		t.setPriority(getAcceptorThreadPriority());
		t.setDaemon(getDaemon());
        //同理start() -> run()
		t.start();
	}
}

这里需要重点讲解一下connector的工作,主要由accept、poller、worker来实现如下图:

由此图可以清楚的看到,一个请求从产生,接收到流转给具体工作者的整个过程。

转载于:https://my.oschina.net/Spider001/blog/3037774

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值