这次重点分析两个方法,一个是SelectorSet.doSelect, 一个是EndPoint.schedule, 这两个方法代表最核心的JettyNIO实现过程。后面就是Http协议的具体处理业务逻辑了。
Jetty将各种IO模型混合在一个统一的框架中,并没有剥离开来,不知为何要采用如此复杂的设计?一个SelectorSet.doSelect简化的NIO流程如下所示:
public class SelectSet implements Dumpable
{
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 ChangeTask)
{
((Runnable)change).run();
}
else if (change instanceof Runnable)
{
dispatch((Runnable)change);
}
else
throw new IllegalArgumentException(change.toString());
}
catch (CancelledKeyException e)
{
}
catch (Throwable e)
{
}
}
// 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();
}
//--------------------------------------------------------------------------------------//
// 此段代码较为复杂,Timeout是一个拥有超时判断功能的任务队列,在Jetty中主要服务于异步Servlet,
// selector中注册的也就包含两个方向的事件,一个方向是判断客户端的连接是否可读,另一个方向就是判断
// 异步Servlet是否完成了后端的业务,可以向客户端输出结果。线程运行到此处是由于Selector中目前没有任何IO事件发生,
// 因此再查看Timeout中的下一个异步Servlet何时可以完成异步任务,如果此时_changes(前端IO事件)中再无待select的任务,则
// 等待下一个异步servlet完成,防止过度轮询造成的cpu浪费。
// 在轮询上的另一优化点是,如果忙select的次数过多(默认是10w次),则启用selector的pause模式(默认关闭),该模式下,
// 上次select无事件后,则会休眠固定时间后,再次执行select操作。
// 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;
// 接着一个一个来处理发生的IO事件
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();
// 该Channel之前已经发生过IO事件,因此key.attachment已经包含了一个EndPoint
if (att instanceof SelectChannelEndPoint)
{
if (key.isReadable()||key.isWritable())
((SelectChannelEndPoint)att).schedule();
}
// 服务器端SocketChannel已经和客户端完成了Http连接的建立
// 为该新连接创建EndPoint并加入Selector轮询
else if (key.isConnectable())
{
// Complete a connection of a registered channel
channel = (SocketChannel)key.channel();
boolean connected=false;
try
{
connected=channel.finishConnect();
}
catch(Exception e) {}
finally
{
if (connected)
{
key.interestOps(SelectionKey.OP_READ);
SelectChannelEndPoint endpoint = createEndPoint(channel,key);
key.attach(endpoint);
endpoint.schedule();
}
else
{
key.cancel();
}
}
}
// 该连接第一次有IO事件发生,为该连接建立EndPoint,上一步已经建立了,为什么要重复建立?
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) {}
catch (Exception e) {
try
{
if (channel!=null)
channel.close();
}
catch(IOException 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) {}
catch (CancelledKeyException e) {}
finally {
_selecting=null;
}
}