以前的一篇文章曾经分析过jetty的socketConnector,其实它的实现相对来说是比较简单的了,但是由于它是阻塞的io,所以在性能上并不好。。一般情况下都推荐使用SelectChannelConnector来替换掉。。。也就是所谓的NioConnector
SelectChannelConnector的实现就要复杂的多了。。而且因为将底层的io揉在了一起。。。。所以感觉这一块的代码不是很干净。。。又在想自己是否可以将底层的I/O全部替换掉,换成netty的实现。。。。。
好了。。先来看看SelectChannelConnector的类图吧:
在这里将比较重要的关联关系也展现了出来。。。
SelectorManager,从名字就可以看出它用来管理selector,它内部可能会有多个SelectSet,其实一般情况下都只有一个,SelectSet里面则是真正的管理selector的运行,以及注册事件的维护。。。
首先我们来看看SelectChannelConnector的一些重要属性吧:
- private transient ServerSocketChannel _acceptChannel;
- private long _lowResourcesConnections;
- private long _lowResourcesMaxIdleTime;
-
- private SelectorManager _manager = new SelectorManager()
- {
- protected SocketChannel acceptChannel(SelectionKey key) throws IOException
- {
-
- SocketChannel channel = ((ServerSocketChannel)key.channel()).accept();
- if (channel==null)
- return null;
- channel.configureBlocking(false);
- Socket socket = channel.socket();
- configure(socket);
- return channel;
- }
-
- public boolean dispatch(Runnable task) throws IOException
- {
- return getThreadPool().dispatch(task);
- }
-
- protected void endPointClosed(SelectChannelEndPoint endpoint)
- {
-
- connectionClosed((HttpConnection)endpoint.getConnection());
- }
-
- protected void endPointOpened(SelectChannelEndPoint endpoint)
- {
-
- connectionOpened((HttpConnection)endpoint.getConnection());
- }
-
-
- protected Connection newConnection(SocketChannel channel,SelectChannelEndPoint endpoint)
- {
- return SelectChannelConnector.this.newConnection(channel,endpoint);
- }
-
- protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey sKey) throws IOException
- {
- return SelectChannelConnector.this.newEndPoint(channel,selectSet,sKey);
- }
- };
首先最重要的当然是server端的监听器了。。。其次就是定义了SelectorManager,因为是一个抽象类,所以还具体的实现了一些他的方法,例如如何接收远程连接,如何dispatch任务等等。。。
我们知道,在server启动的时候,会启动内部的connector,那么我们这里来看看这个connector的启动过程吧:
-
- protected void doStart() throws Exception {
- _manager.setSelectSets(getAcceptors());
- _manager.setMaxIdleTime(getMaxIdleTime());
- _manager.setLowResourcesConnections(getLowResourcesConnections());
- _manager.setLowResourcesMaxIdleTime(getLowResourcesMaxIdleTime());
- _manager.start();
- open();
- _manager.register(_acceptChannel);
- super.doStart();
- }
首先是设置selectmanager的selectset的数量,因为只有一个监听,所以值也就是1了。。。
然后设置连接的最大空闲时间。。。低水平的链接数量啥的,然后就是selectormanager的启动,
open是打开当前的监听器。。然后将这个监听器注册到selectorManager上,。。。最后才是父类的dostart
那么我们在这里先来看看selectmanager的启动吧:
- protected void doStart() throws Exception {
- _selectSet = new SelectSet[_selectSets];
- for (int i=0;i<_selectSet.length;i++)
- _selectSet[i]= new SelectSet(i);
-
- super.doStart();
- }
其实没有太多的内容,无非就是根据监听器的数量来创建selectset,那么我们来看看这个selectset的创建和它的一些重要属性吧:
- private transient int _change;
- private transient List[] _changes;
- private transient Timeout _idleTimeout;
- private transient int _nextSet;
- private transient Timeout _retryTimeout;
- private transient Selector _selector;
- private transient int _setID;
- private transient boolean _selecting;
- private transient int _jvmBug;
-
-
- SelectSet(int acceptorID) throws Exception {
- _setID=acceptorID;
-
- _idleTimeout = new Timeout(this);
- _idleTimeout.setDuration(getMaxIdleTime());
- _retryTimeout = new Timeout(this);
- _retryTimeout.setDuration(0L);
-
-
- _selector = Selector.open();
- _changes = new ArrayList[] {new ArrayList(),new ArrayList()};
- _change=0;
- }
这里可以看到比较重要的selector的创建了。。也就可以知道,真正的select的流程是在selectSet里面执行的。。。
这里比较有意思的是change数组,它其实是用于临时保存所有的一些改动,例如selectkey上面关心的事件啥的。。。所有的这些改动都会在select执行之前被更新。。。
好了,SelectManager的启动就先到这吧,
回到上面,来看看SelectConnecor的open方法吧:
-
- public void open() throws IOException {
- synchronized(this) {
- if (_acceptChannel == null) {
-
- _acceptChannel = ServerSocketChannel.open();
-
-
- _acceptChannel.socket().setReuseAddress(getReuseAddress());
- InetSocketAddress addr = getHost()==null?new InetSocketAddress(getPort()):new InetSocketAddress(getHost(),getPort());
- _acceptChannel.socket().bind(addr,getAcceptQueueSize());
-
-
- _acceptChannel.configureBlocking(false);
-
- }
- }
- }
这里其实就可以看到首先是创建了serversocketChannel,然后设置了它的地址啥的。。最后还将其设置为非阻塞。。。那么接下来就应该看看它是如何被注册到selector上面去了。。。 _manager.register(_acceptChannel);
- _manager.register(_acceptChannel);
-
- public void register(ServerSocketChannel acceptChannel) throws IOException {
- int s=_set++;
- s=s%_selectSets;
- SelectSet set=_selectSet[s];
- set.addChange(acceptChannel);
- set.wakeup();
- }
这里其实可以看到并没有真正的立即将这个channel注册到selector上面去,而是先将其放到selectSet的change数组。。那么待会看select的流程的时候,在select执行之前,它将会被注册到selector上面去。。。
好了,这里先暂时不看。。
回到上面。。来看看父类的doStart方法干了什么事情吧:
-
- protected void doStart() throws Exception {
- if (_server==null)
- throw new IllegalStateException("No server");
-
- open();
-
- super.doStart();
-
- if (_threadPool==null)
- _threadPool=_server.getThreadPool();
- if (_threadPool!=_server.getThreadPool() && (_threadPool instanceof LifeCycle))
- ((LifeCycle)_threadPool).start();
-
-
- synchronized(this)
- {
- _acceptorThread=new Thread[getAcceptors()];
-
- for (int i=0;i<_acceptorThread.length;i++)
- {
- if (!_threadPool.dispatch(new Acceptor(i)))
- {
- Log.warn("insufficient maxThreads configured for {}",this);
- break;
- }
- }
- }
-
- Log.info("Started {}",this);
- }
这段代码就是我们以前分析过的了,它将会创建一个Acceptor对象,然后在threadPool里面调度它,而他要做的事情很简单,就是不断的执行在子类中实现的accept方法。。。。那么我们来看看SelectConnector中定义的accept方法吧:
-
- public void accept(int acceptorID) throws IOException {
- _manager.doSelect(acceptorID);
- }
其实这里又是直接调用的selectManager的doSelect方法。。那么继续来看看吧:
-
- public void doSelect(int acceptorID) throws IOException {
- SelectSet[] sets= _selectSet;
- if (sets!=null && sets.length>acceptorID && sets[acceptorID]!=null)
- sets[acceptorID].doSelect();
- }
好吧,其实又是调用的selectSet的doSelect方法。。。那么我们来看看这个方法吧(它就是整个select的执行流程):
- public void doSelect() throws IOException {
- SelectionKey key=null;
-
- try {
- List changes;
- synchronized (_changes)
- {
- changes=_changes[_change];
- _change=_change==0?1:0;
- _selecting=true;
- }
-
-
-
-
- for (int i = 0; i < changes.size(); i++) {
- try
- {
- Object o = changes.get(i);
- if (o instanceof EndPoint) {
- SelectChannelEndPoint endpoint = (SelectChannelEndPoint)o;
- endpoint.doUpdateKey();
- } else if (o instanceof Runnable) {
- dispatch((Runnable)o);
- } else if (o instanceof SocketChannel) {
-
- SocketChannel channel=(SocketChannel)o;
- Object att = changes.get(++i);
-
- if (channel.isConnected()) {
- key = channel.register(_selector,SelectionKey.OP_READ,att);
- SelectChannelEndPoint endpoint = newEndPoint(channel,this,key);
- key.attach(endpoint);
- endpoint.dispatch();
- } else {
- channel.register(_selector,SelectionKey.OP_CONNECT,att);
- }
-
- } else if (o instanceof ServerSocketChannel) {
- ServerSocketChannel channel = (ServerSocketChannel)o;
- channel.register(getSelector(),SelectionKey.OP_ACCEPT);
- } else {
- throw new IllegalArgumentException(o.toString());
- }
- } catch (CancelledKeyException e) {
- if (isRunning())
- Log.warn(e);
- else
- Log.debug(e);
- }
- }
-
- changes.clear();
-
- long idle_next = 0;
- long retry_next = 0;
- long now=System.currentTimeMillis();
- synchronized (this) {
- _idleTimeout.setNow(now);
- _retryTimeout.setNow(now);
- if (_lowResourcesConnections>0 && _selector.keys().size()>_lowResourcesConnections)
- _idleTimeout.setDuration(_lowResourcesMaxIdleTime);
- else
- _idleTimeout.setDuration(_maxIdleTime);
- idle_next=_idleTimeout.getTimeToNext();
- retry_next=_retryTimeout.getTimeToNext();
- }
-
-
- long wait = 1000L;
- if (idle_next >= 0 && wait > idle_next)
- wait = idle_next;
- if (wait > 0 && retry_next >= 0 && wait > retry_next)
- wait = retry_next;
-
-
- if (wait > 10) {
- long before=now;
- int selected=_selector.select(wait);
- now = System.currentTimeMillis();
- _idleTimeout.setNow(now);
- _retryTimeout.setNow(now);
-
-
- if (selected==0 && wait>0 && (now-before)<wait/2 && _selector.selectedKeys().size()==0)
- {
- if (_jvmBug++>5)
- {
-
-
- Iterator iter = _selector.keys().iterator();
- while(iter.hasNext())
- {
- key = (SelectionKey) iter.next();
- if (key.isValid()&&key.interestOps()==0)
- {
- key.cancel();
- }
- }
- try
- {
- Thread.sleep(20);
- }
- catch (InterruptedException e)
- {
- Log.ignore(e);
- }
- }
- }
- else
- _jvmBug=0;
- } else {
- _selector.selectNow();
- _jvmBug=0;
- }
-
-
- if (_selector==null || !_selector.isOpen())
- return;
-
-
- Iterator iter = _selector.selectedKeys().iterator();
-
- while (iter.hasNext()) {
- key = (SelectionKey) iter.next();
- 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 endpoint = (SelectChannelEndPoint)att;
- endpoint.dispatch();
- } else if (key.isAcceptable()) {
- SocketChannel channel = acceptChannel(key);
- if (channel==null)
- continue;
-
- channel.configureBlocking(false);
-
- _nextSet=++_nextSet%_selectSet.length;
-
- if (_nextSet==_setID) {
-
- SelectionKey cKey = channel.register(_selectSet[_nextSet].getSelector(), SelectionKey.OP_READ);
- SelectChannelEndPoint endpoint=newEndPoint(channel,_selectSet[_nextSet],cKey);
- cKey.attach(endpoint);
- if (endpoint != null)
- endpoint.dispatch();
- } else {
-
- _selectSet[_nextSet].addChange(channel);
- _selectSet[_nextSet].wakeup();
- }
- } else if (key.isConnectable()) {
-
- SocketChannel 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 = newEndPoint(channel,this,key);
- key.attach(endpoint);
- endpoint.dispatch();
- } else {
- key.cancel();
- }
- }
- } else {
- SocketChannel channel = (SocketChannel)key.channel();
- SelectChannelEndPoint endpoint = newEndPoint(channel,this,key);
- key.attach(endpoint);
- if (key.isReadable())
- endpoint.dispatch();
- }
- 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.interestOps(0);
-
- key.cancel();
- }
- }
- }
-
-
- _selector.selectedKeys().clear();
-
-
- _idleTimeout.tick(now);
- _retryTimeout.tick(now);
-
- }
- catch (CancelledKeyException e)
- {
- Log.ignore(e);
- }
- finally
- {
- synchronized(this)
- {
- _selecting=false;
- }
- }
- }
这部分的代码就属于比较关键的了,它执行了整个select的流程,
(1)在select之前,还先遍历了change数组,执行了一些更新的动作,前面说到的serversocketchannel的注册也将会在这里被搞定。。
(2)对于select出来的key,会对他们进行处理。。当然这里如果是SelectChannelEndPoint,那么直接就将其进行调度运行就好了。如果是accept事件,那么还需要accept的流程。。
(3)在这些搞定以后,还会进行超时的处理。。貌似所有的都是这么实现的吧。包括nginx都是在select之后进行超时的处理。。
这部分代码很多。。而且很重要。。。有很多的细节。。。这里就不一一解释了。。
先来看看accept吧:
- else if (key.isAcceptable()) {
- SocketChannel channel = acceptChannel(key);
- if (channel==null)
- continue;
-
- channel.configureBlocking(false);
-
- _nextSet=++_nextSet%_selectSet.length;
-
- if (_nextSet==_setID) {
-
- SelectionKey cKey = channel.register(_selectSet[_nextSet].getSelector(), SelectionKey.OP_READ);
- SelectChannelEndPoint endpoint=newEndPoint(channel,_selectSet[_nextSet],cKey);
- cKey.attach(endpoint);
- if (endpoint != null)
- endpoint.dispatch();
- } else {
-
- _selectSet[_nextSet].addChange(channel);
- _selectSet[_nextSet].wakeup();
- }
这里调用acceptChannel方法来获取远程的链接,然后还要将其注册到selectSet上面去。。还要对这个channel进行包装,生成endpoint。。。
这个acceptChannel的方法的实现如下:
- protected SocketChannel acceptChannel(SelectionKey key) throws IOException
- {
-
- SocketChannel channel = ((ServerSocketChannel)key.channel()).accept();
- if (channel==null)
- return null;
- channel.configureBlocking(false);
- Socket socket = channel.socket();
- configure(socket);
- return channel;
- }
没啥意思,无非就是调用accept方法,获取链接,然后对获取的链接进行初始化,设置非阻塞什么的。。
这里newEndPoint方法用于创建endPoint,来看看它的实现:
-
- protected SelectChannelEndPoint newEndPoint(SocketChannel channel, SelectSet selectSet, SelectionKey sKey) throws IOException
- {
- return SelectChannelConnector.this.newEndPoint(channel,selectSet,sKey);
- }
其实也就是创建ConnectorEndPoint,它的定义在SelectChannelConnector里面。。。另外在创建的过程中,其父类SelectChannelEndPoint还会创建httpConnection
这里就不详细的来讲ConnectorEndPoint的实现了。。。在前面我们可以看到在select中对于SelectChannelConnector的处理就是直接调用其dispatch方法。。。
-
- void dispatch() throws IOException {
- boolean dispatch_done = true;
- try {
- if (dispatch(_manager.isDelaySelectKeyUpdate())) {
- dispatch_done= false;
- dispatch_done = _manager.dispatch((Runnable)this);
- }
- }
- finally {
- if (!dispatch_done) {
- Log.warn("dispatch failed!");
- undispatch();
- }
- }
- }
这里就是在线程池中调度当前的runnable,
-
- public void run() {
- try {
- _connection.handle();
- } catch (ClosedChannelException e) {
- Log.ignore(e);
- } catch (EofException e) {
- Log.debug("EOF", e);
- try{close();}
- catch(IOException e2){Log.ignore(e2);}
- } catch (HttpException e) {
- Log.debug("BAD", e);
- try{close();}
- catch(IOException e2){Log.ignore(e2);}
- } catch (Throwable e) {
- Log.warn("handle failed", e);
- try{close();}
- catch(IOException e2){Log.ignore(e2);}
- } finally {
- undispatch();
- }
- }
到这里就很熟悉了吧。。就进入了http的处理流程。。因为这里的connection其实就是httpConnection。。。
由此。。整个SelectChannelConnector的运行流程就算比较的清晰了。。。。。
首先将监听器注册到selector上,。。。当有新的连接进来之后,获取新的channel,然后将其进行包装。。变成ConnectorEndPoint,将其注册到selector上面。。。
当selector感应到上面的事件之后,就直接进行httpConnection的处理流程。。。。
同时这里也感觉整个这部分I/O的处理不是很干净。。。。如果能够用netty将下面的这部分I/O替换掉就好多了。。当然这个还是有一定难度的。。。