1. Jetty核心抽象
2. SelectChannelConnector分析
2.1. 基本模型與流程
2.1.1. Acceptor
2.1.2. Reactor
2.1.3. Select的工作流程
1. Jetty核心抽象
Connector: 接受client連接,構造Connection並handle connection的組件。
Handler: 處理request併產生response。
Server: Connector和Handler的容器,它通過配置或者編程的方式組裝Connector和Handler集合,並管理它們的聲明週期。同時server還是線程池的管理者,向Connecotr和Handler提供線程池的服務。
EndPoint: 對server的通訊目標的抽象,其中封裝了讀、寫等IO操作。
Buffer: 顧名思義,Buffer是Jetty統一緩存接口所做的抽象,EndPoint的讀寫方法都接受Buffer作爲參數。
Connection: Connection是對連接的抽象,核心方法是handle,供Connector調用、處理連接。Connection的核心實現是HttpConnection,
它封裝了Request、Response,並將其關聯到EndPoint上。
2. SelectChannelConnector分析
2.1. 基本模型與流程
2.1.1. Acceptor
/*
------------------------------------------------------------ */
public
void
open()
throws
IOException
{
synchronized
(
this
)
{
if
(_acceptChannel
==
null
)
{
//
Create a new server socket
_acceptChannel
= ServerSocketChannel.open();
//
Set to blocking mode
_acceptChannel.configureBlocking(
true
);
//
Bind the server socket to the local host and port
_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"
);
}
}
}
一個SelectChannelConnector會啓動一個獨立的線程用來accept連接,要注意的是,雖然它是基於NIO的,但accept仍然採用阻塞模式。 這個線程不斷阻塞調用server.accept()來接受連接並想SelectorManager裏註冊(新建立的SocketChannel則被配置爲非阻塞模式)。
Acceptor線程的內部邏輯,循環調用accept來接受連接,並將連接Channel註冊到SelectorManager裏:
//
start a thread to accept new connections
_manager.dispatch(
new
Runnable()
{
public
void
run()
{
final
ServerSocketChannel
server=_acceptChannel;
while
(isRunning()
&& _acceptChannel==server && server.isOpen())
{
try
{
SocketChannel
channel = server.accept();
channel.configureBlocking(
false
);
Socket
socket = channel.socket();
configure(socket);
_manager.register(channel);
}
catch
(IOException
e)
{
Log.ignore(e);
}
}
}
});
2.1.2. Reactor
在父類AbstractConnector中啓動的N個acceptor線程會循環調用子類實現的accept方法,但是在SelectChannelConnector裏, 這個accept方法裏做的是調用SelectorManager的doSelect,而非accept,所以這裏將其命名爲Reactor。
AbstractConnector裏啓動了多個acceptor線程:
//
Start selector thread
synchronized
(
this
)
{
_acceptorThread
=
new
Thread[getAcceptors()];
for
(
int
i
=
0
;
i < _acceptorThread.length; i++)
_threadPool.dispatch(
new
Acceptor(i));
if
(_threadPool.isLowOnThreads())
Log.warn(
"insufficient
threads configured for {}"
,
this
);
}
Acceptor裏的邏輯就是簡單調用AbstractConnector中的抽象accept方法:
public
void
run()
{
/*
省略... */
try
{
current.setPriority(old_priority
- _acceptorPriorityOffset);
while
(isRunning() && getConnection() != null)
{
try
{
accept(_acceptor);
}
catch
(...) { /* 省略... */ }
}
}
finally
{ /* 省略... */
}
}
SelectChannelConnector裏的accept,實際上做的根本就不是accept連接的動作,相反,它調用了SelectorManager的doSelect方法。
/*
------------------------------------------------------------ */
@Override
public
void
accept(
int
acceptorID)
throws
IOException
{
_manager.doSelect(acceptorID);
}
SelectorManager的doSelect也僅僅是將調用委託給了acceptorID對應的SelectSet的doSelect方法。SelectManager裏的SelectSet是 一個數組,默認只有一個元素,但可以更改。在SelectChannelConnector裏,其數目和acceptor一致。
2.1.3. Select的工作流程
SelectSet的doSelect是核心,它主要完成了三大塊工作:
處理change queue裏的changes。change可能是EndPoint, ChannelAndAttachment, SocketChannel或者Runnalble,這裏會做對應的 處理。比如acceptor接受了一個新連接以後調用的register方法就會往change queue里加入對應的SocketChannel對象,在這裏,此對象 就會被包裝成SocketChannelEndPoint,並將其註冊到selector裏。
調用selectNow,如果沒有select到事件,可能會進一步調用select,阻塞一段時間。如果有事件,就拿到SelectionKey的attachment, 即EndPoint,做schedule和dispatch。
處理idle tick相關邏輯,此處不關心(TODO:瞭解這個設計的用途)
?
/*
------------------------------------------------------------ */
/**
*
Select and dispatch tasks found from changes and the selector.
*
*
@throws IOException
*/
public
void doSelect() throws IOException
{
try
{
_selecting=Thread.currentThread();
final
Selector selector=_selector;
//
Make any key changes required
Object
change;
int
changes=_changes.size();
while
(changes-->0 && (change=_changes.poll())!=null)
{
try
{
if
(change instanceof EndPoint)
{
//
Update the operations for a key.
SelectChannelEndPoint
endpoint = (SelectChannelEndPoint)change;
endpoint.doUpdateKey();
}
else
if (change instanceof ChannelAndAttachment)
{
//
finish accepting/connecting this connection
final
ChannelAndAttachment asc = (ChannelAndAttachment)change;
final
SelectableChannel channel=asc._channel;
final
Object att = asc._attachment;
SelectionKey
key = channel.register(selector,SelectionKey.OP_READ,att);
SelectChannelEndPoint
endpoint = createEndPoint((SocketChannel)channel,key);
key.attach(endpoint);
endpoint.schedule();
}
else
if (change instanceof SocketChannel)
{
//
Newly registered channel
final
SocketChannel channel=(SocketChannel)change;
SelectionKey
key = channel.register(selector,SelectionKey.OP_READ,null);
SelectChannelEndPoint
endpoint = createEndPoint(channel,key);
key.attach(endpoint);
endpoint.schedule();
}
else
if (change instanceof Runnable)
{
dispatch((Runnable)change);
}
else
throw
new IllegalArgumentException(change.toString());
}
catch
(Exception e)
{
if
(isRunning())
Log.warn(e);
else
Log.debug(e);
}
catch
(Error e)
{
if
(isRunning())
Log.warn(e);
else
Log.debug(e);
}
}
//
Do and instant select to see if any connections can be handled.
int
selected=selector.selectNow();
_selects++;
long
now=System.currentTimeMillis();
//
if no immediate things to do
if
(selected==0)
{
//
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;
selected=selector.select(wait);
_selects++;
now
= System.currentTimeMillis();
_timeout.setNow(now);
if
(__JVMBUG_THRESHHOLD>0)
checkJvmBugs(before,
now, wait, selected);
}
}
//
have we been destroyed while sleeping
if
(_selector==null || !selector.isOpen())
return;
//
Look for things to do
for
(SelectionKey key: selector.selectedKeys())
{
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)
{
((SelectChannelEndPoint)att).schedule();
}
else
{
//
Wrap readable registered channel in an endpoint
SocketChannel
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);
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
/*
... */
/
* We do not care this here. Refer to the source code */
}
catch
(CancelledKeyException
e)
{
Log.ignore(e);
}
finally
{
_selecting=
null
;
}
}
}