Connector在Jetty中是负责接收客户端请求,然后为每个请求分配一个对请求进行处理的线程,并且负责将处理完得到的响应发送给客户端。总共有两种类型的Connector,基于Socket的阻塞Connector,代表类是SocketConnector;另外就是基于非阻塞NIO的SelectChannelConnector,当然其实在Jetty中还有基于NIO的阻塞Connector。但现在使用最多的就是SelectChannelConnector。先看下它的类图:
可以看到它也实现了LifeCycle接口,并且对于一个监听请求的组件来说肯定是组件已启动就需要设置监听了,所以下面先看下doStart()方法。首先是SelectChannelConnector的:
@Override
protected void doStart() throws Exception
{
//设置最大的selector数量
_manager.setSelectSets(getAcceptors());
//设置selector上的最大超时等待实际
_manager.setMaxIdleTime(getMaxIdleTime());
//lowResourcesConnections: 连接数量达到该数值时,Jetty会认为服务器资源已被耗尽。
_manager.setLowResourcesConnections(getLowResourcesConnections());
//lowResourcesMaxIdleTime:表示可用线程稀少时或者当资源饱和时,连接最大等待时间,时间单位是毫秒,一般设置为<= maxIdleTime.
_manager.setLowResourcesMaxIdleTime(getLowResourcesMaxIdleTime());
super.doStart();
}
上面的代码主要是在操作_manager属性,给它设置不同的参数。这个_manager是一个SelectorManager对象,其实从名称中就可以看出来是用来管理selector的一个类。SelectorManegery继承了AbstractLifeCycle,并且也是抽象类,这里实际上是它的一个子类:ConnectorSelectorManager的实例。这里先不分析SelectorManager,先看下AbstractConnector的dostart()方法,主要的启动逻辑其实都在这个方法里。
@Override
protected void doStart() throws Exception
{
if (_server == null)
throw new IllegalStateException("No server");
// 打开端口进行监听
open();
if (_threadPool == null)
{ //如果线程池为空,从Server中取出设置好的线程池
_threadPool = _server.getThreadPool();
addBean(_threadPool,false);
}
//这一步是启动管理的其它组件,包括_manager
super.doStart();
// 启动Acceptor线程监听客户端请求
synchronized (this)
{
_acceptorThreads = new Thread[getAcceptors()];
for (int i = 0; i < _acceptorThreads.length; i++)
//在线程中给Acceptor分配执行线程
if (!_threadPool.dispatch(new Acceptor(i)))
throw new IllegalStateException("!accepting");
if (_threadPool.isLowOnThreads())
LOG.warn("insufficient threads configured for {}",this);
}
LOG.info("Started {}",this);
}
首先看下open()方法,这个方法负责创建一个用于监听的ServletSocketChannel对象,也就是_acceptChannel属性。
public void open() throws IOException
{
synchronized(this)
{
if (_acceptChannel == null)
{
// 创建一个新的ServerSocketChannel对象
_acceptChannel = ServerSocketChannel.open();
//设置为非阻塞模式
_acceptChannel.configureBlocking(true);
//给ServerSocket设定端口和IP _acceptChannel.socket().setReuseAddress(getReuseAddress());
InetSocketAddress addr = getHost()==null?new InetSocketAddress(getPort()):new InetSocketAddress(getHost(),getPort());
//这里的第二个参数表示最大请求长度 _acceptChannel.socket().bind(addr,getAcceptQueueSize());
_localPort=_acceptChannel.socket().getLocalPort();
if (_localPort<=0)
throw new IOException("Server channel not bound");
addBean(_acceptChannel);
}
}
}
设置好ServerSocketChannel之后就会启动Acceptor线程了,这里的Acceptor的数目是可以在配置文件中配置的,如果不进行配置,则会默认按下面的公式配置,Runtime.availableProcessors() 方法返回的是Java虚拟机的可用的处理器数量
Math.max(1,(Runtime.getRuntime().availableProcessors()+3)/4)
Acceptor就是一个Runnable对象,所以将其交给_threadPool.dispatch()方法之后,实际上就是为其启动一个线程处理它里面run()方法定义的逻辑。下面是其源码:
private class Acceptor implements Runnable
{
int _acceptor = 0;
Acceptor(int id)
{
_acceptor = id;
}
public void run()
{
Thread current = Thread.currentThread();
String name;
synchronized (AbstractConnector.this)
{
if (_acceptorThreads == null)
return;
_acceptorThreads[_acceptor] = current; //将_acceptorThreads数组中的第_acceptor号元素设置为当前线程
name = _acceptorThreads[_acceptor].getName();
current.setName(name + " Acceptor" + _acceptor + " " + AbstractConnector.this);
}
int old_priority = current.getPriority();
try
{
current.setPriority(old_priority - _acceptorPriorityOffset);
while (isRunning() && getConnection() != null) //监听循环,其实就是执行accept()方法
{
try
{
accept(_acceptor);
}
catch (EofException e)
{
LOG.ignore(e);
}
catch (IOException e)
{
LOG.ignore(e);
}
catch (InterruptedException x)
{
// Connector has been stopped
LOG.ignore(x);
}
catch (Throwable e)
{
LOG.warn(e);
}
}
}
finally
{ //当前线程结束之后,需要恢复原貌
current.setPriority(old_priority);
current.setName(name);
synchronized (AbstractConnector.this)
{
if (_acceptorThreads != null)
_acceptorThreads[_acceptor] = null;
}
}
}
}
这里面的acceptor()方法是在SelectChannelConnector中实现的:
@Override
public void accept(int acceptorID) throws IOException
{
ServerSocketChannel server;
//非基本类型的赋值操作其实并不是原子的,防止并发情况下的问题
synchronized(this)
{
server = _acceptChannel;
}
//如果已经是启动状态,则调用ServerSocketChannel的accept()方法监听请求的接入,没有请求则会一直阻塞在accept()方法上
if (server!=null && server.isOpen() && _manager.isStarted())
{
//接收到连接则返回SocketChannel对象
SocketChannel channel = server.accept();
//配置为非阻塞,这样才能使用selector进行管理
channel.configureBlocking(false);
Socket socket = channel.socket();
//对socket进行一些配置,主要是最大等待时间
configure(socket);
//将channel交给_manager其实也就是selector进行管理
_manager.register(channel);
}
}
上面的逻辑其实就是调用ServerSocketChannel的accept()方法获得新的连接,要注意的是Acceptor线程是可能有多个的,所以上面的这段逻辑可能会在多个线程中执行。
先总结下到目前为止的流程,在启动的时候创建一个ServerSocketChannel对象,并绑定好端口和IP,然后分配一个或多个Acceptor线程并发调用ServerSocketChannel对象的accept()方法来监听新请求的进入。如果有新请求则会创建一个SocketChannel对象,并配置为非阻塞模式然后传递给_manager的register()方法。
先看下_manager.register()方法中的具体逻辑:
public void register(SocketChannel channel)
{
//这里是可能出现并发修改问题的,但因为只是用来做负载均衡,对安全性要求不严格,所以不用加锁保护。
int s=_set++;
if (s<0)
s=-s;
s=s%_selectSets;
//SelectSet是Selector的一个包装类
SelectSet[] sets=_selectSet;
if (sets!=null)
{
//选一个Selector进行服务
SelectSet set=sets[s];
//并不是直接注册到Selector上去,而是
添加到SelectSet的_changes队列中去
set.addChange(channel);
//唤醒当前的Selector
set.wakeup();
}
}
首先是关于SelectSet的数目问题,在SelectChannelConnector的doStart()方法中进行的设定。
_manager.setSelectSets(getAcceptors());
所以SelectSet(即Selector)的数目和Acceptor的数目是一样的。然后在上面可以看到Channel被放到了SelectSet的_changes队列中去了并没有直接与Selector进行交互,所以逻辑就到了SelectSet里面了。
SelecSet是由SelectorManager来管理的,所以先来分析下SelectorManager。前面说过SelectorManager也是一个LifeCycle类型的组件,并且是由SelectChannelConnector来进行管理的组件,而SelectChannelConnector继承了AggregateLifeCycle这个会在自身启动时候调用所有管理的生命周期组件启动方法。所以在SelectChannelConnector启动的时候,SelectorManager也会启动,下面先看下它的doStart()方法:
@Override
protected void doStart() throws Exception
{
//先创建指定数量的SelectSet
_selectSet = new SelectSet[_selectSets];
for (int i=0;i<_selectSet.length;i++)
_selectSet[i]= new SelectSet(i);
super.doStart();
// 为每个SelectSet分配一个线程
for (int i=0;i<getSelectSets();i++)
{
final int id=i;
//这里的dispath其实就是调用的ThreadPool的dispatch()方法
boolean selecting=dispatch(new Runnable()
{
public void run()
{
String name=Thread.currentThread().getName();
int priority=Thread.currentThread().getPriority();
try
{
SelectSet[] sets=_selectSet;
if (sets==null)
return;
SelectSet set=sets[id];
Thread.currentThread().setName(name+" Selector"+id);
if (getSelectorPriorityDelta()!=0)
Thread.currentThread().setPriority(Thread.currentThread().getPriority()+getSelectorPriorityDelta());
LOG.debug("Starting {} on {}",Thread.currentThread(),this);
//在服务器运行的时候就不断进行doSelect()操作
while (isRunning())
{
try
{
set.doSelect();
}
catch(IOException e)
{
LOG.ignore(e);
}
catch(Exception e)
{
LOG.warn(e);
}
}
}
finally
{
Thread.currentThread().setName(name);
if (getSelectorPriorityDelta()!=0)
Thread.currentThread().setPriority(priority);
}
}
});
if (!selecting)
throw new IllegalStateException("!Selecting");
}
}
大体逻辑就是创建一定数量的SelectSet,然后每个都分配一个线程,在线程中主要的任务就是执行SelectSet的doSelect()方法。
在分析SelectSet的doSelect()方法之前,先看下SelectSet这个类的一些属性和它的构造器:
public class SelectSet implements Dumpable
{
private final int _setID;
private final Timeout _timeout;
//与外部进行交互的并发队列
private final ConcurrentLinkedQueue<Object> _changes = new ConcurrentLinkedQueue<Object>();
//持有的Selector对象
private volatile Selector _selector;
private volatile Thread _selecting;
private int _busySelects;
private long _monitorNext;
private boolean _pausing;
private boolean _paused;
private volatile long _idleTick;
private ConcurrentMap<SelectChannelEndPoint,Object> _endPoints = new ConcurrentHashMap<SelectChannelEndPoint, Object>();
SelectSet(int acceptorID) throws Exception
{
_setID=acceptorID;
_idleTick = System.currentTimeMillis();
_timeout = new Timeout(this);
_timeout.setDuration(0L);
// 在构造函数中创建的Selector对象
_selector = Selector.open();
_monitorNext=System.currentTimeMillis()+__MONITOR_PERIOD;
}
然后看下它的doSelect()方法:
public void doSelect() throws IOException
{
try
{
_selecting=Thread.currentThread();
final Selector selector=_selector;
// Stopped concurrently ?
if (selector == null)
return;
// Make any key changes required
Object change;
int changes=_changes.size();
while (changes-->0 && (change=_changes.poll())!=null)
{
Channel ch=null;
SelectionKey key=null;
try
{
if (change instanceof EndPoint)
{
// Update the operations for a key.
SelectChannelEndPoint endpoint = (SelectChannelEndPoint)change;
ch=endpoint.getChannel();
endpoint.doUpdateKey();
}
else if (change instanceof ChannelAndAttachment)
{
// finish accepting/connecting this connection
final ChannelAndAttachment asc = (ChannelAndAttachment)change;
final SelectableChannel channel=asc._channel;
ch=channel;
final Object att = asc._attachment;
if ((channel instanceof SocketChannel) && ((SocketChannel)channel).isConnected())
{
key = channel.register(selector,SelectionKey.OP_READ,att);
SelectChannelEndPoint endpoint = createEndPoint((SocketChannel)channel,key);
key.attach(endpoint);
endpoint.schedule();
}
else if (channel.isOpen())
{
key = channel.register(selector,SelectionKey.OP_CONNECT,att);
}
}
else if (change instanceof SocketChannel)
{
// Newly registered channel
final SocketChannel channel=(SocketChannel)change;
ch=channel;
key = channel.register(selector,SelectionKey.OP_READ,null);
SelectChannelEndPoint endpoint = createEndPoint(channel,key);
key.attach(endpoint);
endpoint.schedule();
}
else if (change instanceof ChangeTask)
{
((Runnable)change).run();
}
else if (change instanceof Runnable)
{
dispatch((Runnable)change);
}
else
throw new IllegalArgumentException(change.toString());
}
catch (CancelledKeyException e)
{
LOG.ignore(e);
}
catch (Throwable e)
{
if (isRunning())
LOG.warn(e);
else
LOG.debug(e);
try
{
if (ch!=null)
ch.close();
}
catch(IOException e2)
{
LOG.debug(e2);
}
}
}
// Do and instant select to see if any connections can be handled.
int selected=selector.selectNow();
long now=System.currentTimeMillis();
// if no immediate things to do
if (selected==0 && selector.selectedKeys().isEmpty())
{
// If we are in pausing mode
if (_pausing)
{
try
{
Thread.sleep(__BUSY_PAUSE); // pause to reduce impact of busy loop
}
catch(InterruptedException e)
{
LOG.ignore(e);
}
now=System.currentTimeMillis();
}
// workout how long to wait in select
_timeout.setNow(now);
long to_next_timeout=_timeout.getTimeToNext();
long wait = _changes.size()==0?__IDLE_TICK:0L;
if (wait > 0 && to_next_timeout >= 0 && wait > to_next_timeout)
wait = to_next_timeout;
// If we should wait with a select
if (wait>0)
{
long before=now;
selector.select(wait);
now = System.currentTimeMillis();
_timeout.setNow(now);
// If we are monitoring for busy selector
// and this select did not wait more than 1ms
if (__MONITOR_PERIOD>0 && now-before <=1)
{
// count this as a busy select and if there have been too many this monitor cycle
if (++_busySelects>__MAX_SELECTS)
{
// Start injecting pauses
_pausing=true;
// if this is the first pause
if (!_paused)
{
// Log and dump some status
_paused=true;
LOG.warn("Selector {} is too busy, pausing!",this);
}
}
}
}
}
// have we been destroyed while sleeping
if (_selector==null || !selector.isOpen())
return;
// Look for things to do
for (SelectionKey key: selector.selectedKeys())
{
SocketChannel channel=null;
try
{
if (!key.isValid())
{
key.cancel();
SelectChannelEndPoint endpoint = (SelectChannelEndPoint)key.attachment();
if (endpoint != null)
endpoint.doUpdateKey();
continue;
}
Object att = key.attachment();
if (att instanceof SelectChannelEndPoint)
{
if (key.isReadable()||key.isWritable())
((SelectChannelEndPoint)att).schedule();
}
else if (key.isConnectable())
{
// Complete a connection of a registered cha
channel = (SocketChannel)key.channel();
boolean connected=false;
try
{
connected=channel.finishConnect();
}
catch(Exception e)
{
connectionFailed(channel,e,att);
}
finally
{
if (connected)
{
key.interestOps(SelectionKey.OP_READ);
SelectChannelEndPoint endpoint = createEndPoint(channel,key);
key.attach(endpoint);
endpoint.schedule();
}
else
{
key.cancel();
}
}
}
else
{
// Wrap readable registered channel in an endpoint
channel = (SocketChannel)key.channel();
SelectChannelEndPoint endpoint = createEndPoint(channel,key);
key.attach(endpoint);
if (key.isReadable())
endpoint.schedule();
}
key = null;
}
catch (CancelledKeyException e)
{
LOG.ignore(e);
}
catch (Exception e)
{
if (isRunning())
LOG.warn(e);
else
LOG.ignore(e);
try
{
if (channel!=null)
channel.close();
}
catch(IOException e2)
{
LOG.debug(e2);
}
if (key != null && !(key.channel() instanceof ServerSocketChannel) && key.isValid())
key.cancel();
}
}
// Everything always handled
selector.selectedKeys().clear();
now=System.currentTimeMillis();
_timeout.setNow(now);
Task task = _timeout.expired();
while (task!=null)
{
if (task instanceof Runnable)
dispatch((Runnable)task);
task = _timeout.expired();
}
// Idle tick
if (now-_idleTick>__IDLE_TICK)
{
_idleTick=now;
final long idle_now=((_lowResourcesConnections>0 && selector.keys().size()>_lowResourcesConnections))
?(now+_maxIdleTime-_lowResourcesMaxIdleTime)
:now;
dispatch(new Runnable()
{
public void run()
{
for (SelectChannelEndPoint endp:_endPoints.keySet())
{
endp.checkIdleTimestamp(idle_now);
}
}
public String toString() {return "Idle-"+super.toString();}
});
}
// Reset busy select monitor counts
if (__MONITOR_PERIOD>0 && now>_monitorNext)
{
_busySelects=0;
_pausing=false;
_monitorNext=now+__MONITOR_PERIOD;
}
}
catch (ClosedSelectorException e)
{
if (isRunning())
LOG.warn(e);
else
LOG.ignore(e);
}
catch (CancelledKeyException e)
{
LOG.ignore(e);
}
finally
{
_selecting=null;
}
}
可以看到上面的逻辑非常长,这里不做具体分析。大致上是可以分为两个阶段:首先是从_changes队列中取出需要处理的事件,按照不同类型进行处理,如果是新接入的Channel则会将其注册到Selector上去进行监听;第二部分是对selector上事件进行监听,通过selector.selectNow()来判断是否有需要处理的IO事件,注意这里没有使用阻塞的selector.select()方法,如果有需要处理的事件则通过selector.selectedKeys()取出对应的事件进行处理。当然这个流程是非常简化之后的一个流程,上面其实还有很多的细节处理,比如各种异常的处理。
经过上面的分析之后,对于Jetty处理请求就有了一个大致的印象了:首先是创建一个ServerSocketChannel并配置为非阻塞模式,然后创建多个Acceptor线程来并发执行ServerSocketChannel的accept()来监听新的请求,得到代表新请求的Channel之后就将其配置为非阻塞的,然后将其随机交给一个selector的线程进行处理,在selector线程中会为其创建一个EndPoint,然后为其分配一个线程调用Server.handle()方法,进入前面介绍的Handler链流程,最后到达Servlet执行我们自定义的业务逻辑。这就是总体的处理流程了。

1453

被折叠的 条评论
为什么被折叠?



