趁还没上班,赶紧发出来,接上篇《IoAcceptor》
上一篇写的是IoAcceptor是服务器端的接收代码,今天要写的是IoConnector,是客户端的连接器。在昨天,我们还留下一些问题没有解决,这些问题今天同样会产生,但是都要等到讲到session的时候才能逐步揭开。先回顾一下问题:
l 我们已经在AbstractPollingIoAcceptor中看到了,mina是将连接(命令)和业务(读写)分不同线程处理的,但是我们还没有看到mina是如何实现对这些线程的管理的。
l 在昨天的最后,我们看到在NioSocketAcceptor中的具体NIO实现,但是我们还没有看到mina是在哪里调用了这些具体操作的。当然这也是mina对连接线程管理的一部分。
这些问题今天也会出现,因为从名字上就能看出,IoConnector和IoAcceptor的构造相差不大,所以在写connector的分析时,主要会从结构和差异上入手,最后再给出昨天没写完的删减版的Acceptor。先回顾一下客户端连接的代码:
// 创建一个非阻塞的客户端程序 IoConnector connector = new NioSocketConnector(); // 设置链接超时时间 connector.setConnectTimeout(30000); // 添加过滤器 connector.getFilterChain().addLast( "codec", new ProtocolCodecFilter(new MessageCodecFactory( new InfoMessageDecoder(Charset.forName("utf-8")), new InfoMessageEncoder(Charset.forName("utf-8"))))); // 添加业务逻辑处理器类 connector.setHandler(new ClientHandler()); IoSession session = null; try { ConnectFuture future = connector.connect(new InetSocketAddress( HOST, PORT));// 创建连接 future.awaitUninterruptibly();// 等待连接创建完成 session = future.getSession();// 获得session
还是先看IoConnector的结构图,图来自mina官网,用XMind绘制的。在写构成之前,我们还是先看一下mina官网对这些connector的介绍:
As we have to use an IoAcceptor for servers, you have to implement the IoConnector. Again, we have many implementation classes :
其中,NioSocketConnector是我们最常用到的,proxy方式虽然在mina的源码中也花了大篇幅去撰写,但可惜的是很少有相关的文档,所以学习的成本还挺高的。今天我们主要还是按照上图画的两条路来看NioSocketConnector。
和昨天一样,我们还是从左边的路走起,看interface IoConnector,这个接口主要定义了连接的方法以及socket连接时用到的参数。在mina中通过IoFuture来描述、侦听在IoSession上实现的异步IO操作,所以这IoConnector中的connect方法都返回了一个ConnectFuture实例。
而在SocketConnector接口中的定义中就显得更简单了,它和IoConnector之间也是接口的继承关系,在SocketConnector中就定义了两类方法,一个对远程地址的get和set,一个拿到session的配置。这些都容易理解。
再来看右边,AbstractIoConnector,这个抽象类主要作用就是实现IoConnector里定义的操作,至于他又继承了AbstractIoService,一是为了用到父类(AbstractIoService)的方法,二是为了将那些父类没有实现的方法继续传递下去,让它(AbstractIoConnector)的子类去实现。所以看多了,好多结构也能看明白了,这里我觉得主要要学习的还是接口、抽象类之间的引用关系。
继续看AbstractIoConnector,这个类主要是实现了connect的逻辑操作(封装了连接前后的一些必要执行步骤和check一些状态),具体的连接操作还是让子类去实现,这个和上篇写的AbstractIoAcceptor一模一样,在AbstractIoAcceptor中,主要也是封装了bind的逻辑操作,真正的bind过程是让子类去实现的简单看下代码:
public final ConnectFuture connect(SocketAddress remoteAddress, SocketAddress localAddress, IoSessionInitializer<? extends ConnectFuture> sessionInitializer) { if (isDisposing()) { throw new IllegalStateException("The connector has been disposed."); } if (remoteAddress == null) { throw new IllegalArgumentException("remoteAddress"); } if (!getTransportMetadata().getAddressType().isAssignableFrom(remoteAddress.getClass())) { throw new IllegalArgumentException("remoteAddress type: " + remoteAddress.getClass() + " (expected: " + getTransportMetadata().getAddressType() + ")"); } if (localAddress != null && !getTransportMetadata().getAddressType().isAssignableFrom(localAddress.getClass())) { throw new IllegalArgumentException("localAddress type: " + localAddress.getClass() + " (expected: " + getTransportMetadata().getAddressType() + ")"); } if (getHandler() == null) { if (getSessionConfig().isUseReadOperation()) { setHandler(new IoHandler() { public void exceptionCaught(IoSession session, Throwable cause) throws Exception { // Empty handler } public void messageReceived(IoSession session, Object message) throws Exception { // Empty handler } public void messageSent(IoSession session, Object message) throws Exception { // Empty handler } public void sessionClosed(IoSession session) throws Exception { // Empty handler } public void sessionCreated(IoSession session) throws Exception { // Empty handler } public void sessionIdle(IoSession session, IdleStatus status) throws Exception { // Empty handler } public void sessionOpened(IoSession session) throws Exception { // Empty handler } }); } else { throw new IllegalStateException("handler is not set."); } } return connect0(remoteAddress, localAddress, sessionInitializer); } protected abstract ConnectFuture connect0(SocketAddress remoteAddress, SocketAddress localAddress, IoSessionInitializer<? extends ConnectFuture> sessionInitializer);
Connect0才是最后具体的操作,而这一步操作在这个类中被没有给出实现。具体实现放在了AbstractPollingIoConnector上。和昨天一样,这些设计都是对称的,我们还是看三点:
l implementing client transport using a polling strategy
l A Executor will be used for running client connection, and an AbstractPollingIoProcessor will be used for processing connected client I/O operations like reading, writing and closing.
l All the low level methods for binding, connecting, closing need to be provided by the subclassing implementation
至于内部的具体实现,那跟acceptor中没什么区别,连使用的工具类都差别不大,这部分就很容易读懂了,只不过一个是bind一个是connect。
最后我们看NioSocketConnector,具体连接的实现类,只有一个成员变量和NioSocketAcceptor一样:
private volatile Selector selector; @Override protected SocketChannel newHandle(SocketAddress localAddress) throws Exception { SocketChannel ch = SocketChannel.open(); int receiveBufferSize = (getSessionConfig()).getReceiveBufferSize(); if (receiveBufferSize > 65535) { ch.socket().setReceiveBufferSize(receiveBufferSize); } if (localAddress != null) { ch.socket().bind(localAddress); } ch.configureBlocking(false); return ch; }
只是需要注意,这里面专门有个内部类来处理selectionkey,将遍历的过程都抽离出来了,这个和我们用NIO的一般写法稍有不同,这样做的好处也是为了复用:
private static class SocketChannelIterator implements Iterator<SocketChannel> { private final Iterator<SelectionKey> i; private SocketChannelIterator(Collection<SelectionKey> selectedKeys) { this.i = selectedKeys.iterator(); } /** * {@inheritDoc} */ public boolean hasNext() { return i.hasNext(); } /** * {@inheritDoc} */ public SocketChannel next() { SelectionKey key = i.next(); return (SocketChannel) key.channel(); } /** * {@inheritDoc} */ public void remove() { i.remove(); } }---------------------------------------------------------
补一个上篇就应该发的acceptor的阉割版,写这样的东西主要还是为了理清楚结构。我主要是把内容简化了,但是结构都没有变,核心的成员变量也没有少:
起点IoService:
package org.apache.mina.core.rewrite.service; /** * IO Service --handler/processor/acceptor/connector * * @author ChenHui * */ public interface IoService { /** 添加listener */ void addListener(IoServiceListener listener); /** 销毁 */ void dispose(boolean awaitTermination); /** 设置handler */ IoHandler getHandler(); void setHandler(IoHandler handler); /** 管理session */ int getManagedSessionCount(); boolean isActive(); }左边的路
package org.apache.mina.core.rewrite.service; import java.io.IOException; import java.net.SocketAddress; import java.util.Set; /** * 注意接口的继承,这里的方法都是新定义的 * * Acceptor 主要用于:Accepts incoming connection, communicates with clients, and * fires events to IoHandler * * @author ChenHui */ public interface IoAcceptor extends IoService { SocketAddress getLocalAddress(); Set<SocketAddress> getLocalAddresses(); void bind(SocketAddress localAddress) throws IOException; void bind(Iterable<? extends SocketAddress> localAddresses) throws IOException; void unbind(SocketAddress localAddress); /**没有写到IoSession 所以暂时不用*/ //IoSession newSession(SocketAddress remoteAddress,SocketAddress localAddress); }SocketAcceptor:
package org.apache.mina.rewrite.transport.socket; import java.net.InetSocketAddress; import org.apache.mina.core.rewrite.service.IoAcceptor; public interface SocketAcceptor extends IoAcceptor { InetSocketAddress getLocalAddress(); void setDefaultLocalAddress(InetSocketAddress localAddress); public boolean isReuseAddress(); // ... // SocketSessionConfig getSessionConfig(); }再看右边的
package org.apache.mina.core.rewrite.service; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; public abstract class AbstractIoService implements IoService { private static final AtomicInteger id = new AtomicInteger(); private final String threadName; private final Executor executor; private final boolean createdExecutor; private IoHandler handler; // 用于安全的关闭 protected final Object disposalLock = new Object(); private volatile boolean disposing; private volatile boolean disposed; /** * * @param param * sessionConfig IoSessionConfig * @param executor * used for handling execution of IO event. can be null */ protected AbstractIoService(Object param, Executor executor) { // TODO listener & session config if (executor == null) { this.executor = Executors.newCachedThreadPool(); createdExecutor = true; } else { this.executor = executor; createdExecutor = false; } threadName = getClass().getSimpleName() + "-" + id.incrementAndGet(); } @Override public void addListener(IoServiceListener listener) { // TODO add listener } /**注意这个不是override来的*/ protected final void ececuteWorker(Runnable worker, String suffix){ String actualThreadName=threadName; if(suffix!=null){ actualThreadName=actualThreadName+"-"+suffix; } executor.execute(worker); } @Override public void dispose(boolean awaitTermination) { if (disposed) { return; } synchronized (disposalLock) { if (!disposing) { disposing = true; try { /** 真正的关闭方法TODO */ dispose0(); } catch (Exception e) { e.printStackTrace(); } } } if (createdExecutor) { ExecutorService e = (ExecutorService) executor; e.shutdown(); if (awaitTermination) { try { e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS); } catch (InterruptedException e1) { // 注意异常时的中断处理 Thread.currentThread().interrupt(); } } } disposed = true; } protected abstract void dispose0() throws Exception; @Override public IoHandler getHandler() { return this.handler; } @Override public void setHandler(IoHandler handler) { if (handler == null) { throw new IllegalArgumentException("handler cannot be null"); } // TODO isActive: when service is active, cannot be set handler if(isActive()){ throw new IllegalStateException("when service is active, cannot be set handler"); } this.handler = handler; } }AbstractIoAcceptor:
package org.apache.mina.core.rewrite.service; import java.io.IOException; import java.net.SocketAddress; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.Executor; public abstract class AbstractIoAcceptor extends AbstractIoService implements IoAcceptor { private final List<SocketAddress> defaultLocalAddresses = new ArrayList<SocketAddress>(); private final List<SocketAddress> unmodifiableDeffaultLocalAddresses = Collections .unmodifiableList(defaultLocalAddresses); private final Set<SocketAddress> boundAddresses = new HashSet<SocketAddress>(); private boolean disconnectOnUnbind = true; /** 这里不是很明白,为什么要用protected 而 不是private */ protected final Object bindLock = new Object(); /** * 注意这个构造方法是一定要写的,否则编译不通过:抽象类继承时候,构造方法都要写,而且必须包含super * * @param param * sessionConfig * @param executor */ protected AbstractIoAcceptor(Object param, Executor executor) { super(param, executor); defaultLocalAddresses.add(null); } @Override public SocketAddress getLocalAddress() { Set<SocketAddress> localAddresses = getLocalAddresses(); if (localAddresses.isEmpty()) { return null; } return localAddresses.iterator().next(); } @Override public final Set<SocketAddress> getLocalAddresses() { Set<SocketAddress> localAddresses = new HashSet<SocketAddress>(); synchronized (boundAddresses) { localAddresses.addAll(boundAddresses); } return localAddresses; } @Override public void bind(SocketAddress localAddress) throws IOException { // TODO Auto-generated method stub } @Override public void bind(Iterable<? extends SocketAddress> localAddresses) throws IOException { // TODO isDisposing() if (localAddresses == null) { throw new IllegalArgumentException("localAddresses"); } List<SocketAddress> localAddressesCopy = new ArrayList<SocketAddress>(); for (SocketAddress a : localAddresses) { // TODO check address type localAddressesCopy.add(a); } if (localAddressesCopy.isEmpty()) { throw new IllegalArgumentException("localAddresses is empty"); } boolean active = false; synchronized (bindLock) { synchronized (boundAddresses) { if (boundAddresses.isEmpty()) { active = true; } } } /** implement in abstractIoService */ if (getHandler() == null) { throw new IllegalArgumentException("handler is not set"); } try { Set<SocketAddress> addresses = bindInternal(localAddressesCopy); synchronized (boundAddresses) { boundAddresses.addAll(addresses); } } catch (IOException e) { throw e; } catch (RuntimeException e) { throw e; } catch (Throwable e) { throw new RuntimeException("Filed ti bind"); } if(active){ //do sth } } protected abstract Set<SocketAddress> bindInternal( List<? extends SocketAddress> localAddress) throws Exception; @Override public void unbind(SocketAddress localAddress) { // TODO Auto-generated method stub } }polling:
package org.apache.mina.core.rewrite.polling; import java.net.SocketAddress; import java.nio.channels.ServerSocketChannel; import java.util.List; import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.Semaphore; import java.util.concurrent.atomic.AtomicReference; import org.apache.mina.core.rewrite.service.AbstractIoAcceptor; public abstract class AbstractPollingIoAcceptor extends AbstractIoAcceptor { private final Semaphore lock = new Semaphore(1); private volatile boolean selectable; private AtomicReference<Acceptor> acceptorRef = new AtomicReference<Acceptor>(); /** * define the num of sockets that can wait to be accepted. */ protected int backlog = 50; /** * 一样的,这个构造方法也要写 * * @param param * @param executor */ protected AbstractPollingIoAcceptor(Object param, Executor executor) { super(param, executor); // TODO Auto-generated constructor stub } /** * init the polling system. will be called at construction time * * @throws Exception */ protected abstract void init() throws Exception; protected abstract void destory() throws Exception; protected abstract int select() throws Exception; /**这里有点儿变动*/ protected abstract ServerSocketChannel open(SocketAddress localAddress) throws Exception; @Override protected Set<SocketAddress> bindInternal( List<? extends SocketAddress> localAddress) throws Exception { // ... try { lock.acquire(); Thread.sleep(10); } finally { lock.release(); } // ... return null; } /** * this class is called by startupAcceptor() method it's a thread accepting * incoming connections from client * * @author ChenHui * */ private class Acceptor implements Runnable { @Override public void run() { assert (acceptorRef.get() == this); int nHandles = 0; lock.release(); while (selectable) { try { int selected = select(); // nHandles+=registerHandles(); if (nHandles == 0) { acceptorRef.set(null); // ... } } catch (Exception e) { } } } } }好了最后看NioSoeketAcceptor:
package org.apache.mina.rewrite.transport.socket.nio; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.SocketAddress; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.util.concurrent.Executor; import org.apache.mina.core.rewrite.polling.AbstractPollingIoAcceptor; import org.apache.mina.rewrite.transport.socket.SocketAcceptor; public final class NioSocketAcceptor extends AbstractPollingIoAcceptor implements SocketAcceptor { private volatile Selector selector; protected NioSocketAcceptor(Object param, Executor executor) { super(param, executor); // TODO Auto-generated constructor stub } @Override public int getManagedSessionCount() { // TODO Auto-generated method stub return 0; } /** * 这个方法继承自AbstractIoAcceptor * * The type NioSocketAcceptor must implement the inherited abstract method * SocketAcceptor.getLocalAddress() to override * AbstractIoAcceptor.getLocalAddress() */ @Override public InetSocketAddress getLocalAddress() { // TODO Auto-generated method stub return null; } @Override public void setDefaultLocalAddress(InetSocketAddress localAddress) { // TODO Auto-generated method stub } @Override public boolean isReuseAddress() { // TODO Auto-generated method stub return false; } @Override protected void init() throws Exception { selector = Selector.open(); } @Override protected void destory() throws Exception { if (selector != null) { selector.close(); } } @Override protected int select() throws Exception { return selector.select(); } @Override protected void dispose0() throws Exception { // TODO Auto-generated method stub } protected ServerSocketChannel open(SocketAddress localAddress) throws Exception { ServerSocketChannel channel =ServerSocketChannel.open(); boolean success=false; try{ channel.configureBlocking(false); ServerSocket socket=channel.socket(); socket.setReuseAddress(isReuseAddress()); socket.bind(localAddress); channel.register(selector, SelectionKey.OP_ACCEPT); success=true; }finally{ if(!success){ //close(channel); } } return channel; } @Override public boolean isActive() { // TODO Auto-generated method stub return false; } }------------------------------------------------------
到此为止将连接部分都写完了,在连接部分还有些零碎的东西,比如handler、polling,这些都只是稍稍提了一下,具体后面会在介绍其他部分是肯定还会碰上,我还是想把重心放在最主要的部分去写,下一篇应该要写到session了。