乞丐版servlet容器第4篇

6. NIOConnector

如今爲Server添加NIOConnector,添加以前能夠發現咱們的代碼實際上是有問題的。好比如今的代碼是沒法讓服務器支持同時監聽多個端口和IP的,如同時監聽 127.0.0.1:18080和0.0.0.0:18443如今是沒法作到的。由於當期的端口號是Server的屬性,而且只有一個,可是端口其實應該是Connector的屬性,由於Connector專門負責了Server的IO。設計模式

重構一下,將端口號從Server中去掉,取而代之的是Connector列表;將當期的Connector抽象類重命名爲AbstractConnector,再新建接口Connector,添加getPort和getHost兩個方法,讓Connector支持將監聽綁定到不一樣IP的功能。服務器

去掉getPort方法架構

public interface Server {
    /**
     * 啓動服務器
     */
    void start() throws IOException;

    /**
     * 關閉服務器
     */
    void stop();

    /**
     * 獲取服務器啓停狀態
     * @return
     */
    ServerStatus getStatus();

    /**
     * 獲取服務器管理的Connector列表
     * @return
     */
    List<Connector> getConnectorList();
}

去掉port屬性和方法socket

public class SimpleServer implements Server {
    private static Logger logger = LoggerFactory.getLogger(SimpleServer.class);
    private volatile ServerStatus serverStatus = ServerStatus.STOPED;
    private final List<AbstractConnector> connectorList;

    public SimpleServer(List<AbstractConnector> connectorList) {
        this.connectorList = connectorList;
    }
    ... ...
}

添加HOST屬性綁定IP,添加backLog屬性設置ServerSocket的TCP屬性SO_BACKLOG。修改init方法,支持ServerSocket綁定IP。ide

public class SocketConnector extends AbstractConnector<Socket> {
    private static final Logger LOGGER = LoggerFactory.getLogger(SocketConnector.class);
    private static final String LOCALHOST = "localhost";
    private static final int DEFAULT_BACKLOG = 50;
    private final int port;
    private final String host;
    private final int backLog;
    private ServerSocket serverSocket;
    private volatile boolean started = false;
    private final EventListener<Socket> eventListener;

    public SocketConnector(int port, EventListener<Socket> eventListener) {
        this(port, LOCALHOST, DEFAULT_BACKLOG, eventListener);
    }

    public SocketConnector(int port, String host, int backLog, EventListener<Socket> eventListener) {
        this.port = port;
        this.host = StringUtils.isBlank(host) ? LOCALHOST : host;
        this.backLog = backLog;
        this.eventListener = eventListener;
    }


    @Override
    protected void init() throws ConnectorException {

        //監聽本地端口,若是監聽不成功,拋出異常
        try {
            InetAddress inetAddress = InetAddress.getByName(this.host);
            this.serverSocket = new ServerSocket(this.port, backLog, inetAddress);
            this.started = true;
        } catch (IOException e) {
            throw new ConnectorException(e);
        }
    }

執行單元測試,一切OK。如今能夠開始添加NIO了。單元測試

根據前面一步一步搭建的架構,須要添加支持NIO的EventListener和EventHandler兩個實現便可。測試

NIOEventListener中莫名其妙出現了SelectionKey,表面這個類和SelectionKey是強耦合的,說明Event這塊的架構設計是很爛的,勢必又要重構,今天先不改了,完成功能先。

public class NIOEventListener extends AbstractEventListener<SelectionKey> {
    private final EventHandler<SelectionKey> eventHandler;

    public NIOEventListener(EventHandler<SelectionKey> eventHandler) {
        this.eventHandler = eventHandler;
    }

    @Override
    protected EventHandler<SelectionKey> getEventHandler(SelectionKey event) {
        return this.eventHandler;
    }
}

贊成的道理,NIOEchoEventHandler也不該該和SelectionKey強耦合,echo功能簡單,若是是返回文件內容的功能,那樣的話,大段大段的文件讀寫代碼是徹底沒法複用的。this

public class NIOEchoEventHandler extends AbstractEventHandler<SelectionKey> {
    @Override
    protected void doHandle(SelectionKey key) {
        try {
            if (key.isReadable()) {
                SocketChannel client = (SocketChannel) key.channel();
                ByteBuffer output = (ByteBuffer) key.attachment();
                client.read(output);
            } else if (key.isWritable()) {
                SocketChannel client = (SocketChannel) key.channel();
                ByteBuffer output = (ByteBuffer) key.attachment();
                output.flip();
                client.write(output);
                output.compact();
            }
        } catch (IOException e) {
            throw new HandlerException(e);
        }
    }
}

修改ServerFactory,添加NIO功能,這裏的代碼也是有很大設計缺陷的,ServerFactory只應該根據傳入的config信息構造Server,而不是每次都去改工廠。spa

public class ServerFactory {
    /**
     * 返回Server實例
     *
     * @return
     */
    public static Server getServer(ServerConfig serverConfig) {
        List<Connector> connectorList = new ArrayList<>();
        SocketEventListener socketEventListener =
                new SocketEventListener(new FileEventHandler(System.getProperty("user.dir")));
        ConnectorFactory connectorFactory =
                new SocketConnectorFactory(new SocketConnectorConfig(serverConfig.getPort()), socketEventListener);
        //NIO
        NIOEventListener nioEventListener = new NIOEventListener(new NIOEchoEventHandler());
        //監聽18081端口
        SocketChannelConnector socketChannelConnector = new SocketChannelConnector(18081,nioEventListener);

        connectorList.add(connectorFactory.getConnector());
        connectorList.add(socketChannelConnector);
        return new SimpleServer(connectorList);
    }
}

運行BootStrap,啓動Server,telnet訪問18081端口,功能是勉強實現了,可是架構設計是有重大缺陷的,進一步添加功能以前,須要重構好架構才行。線程

7. Connection接口

繼續抽象的過程,不管Socket仍是SocketChannle,其實均可以抽象爲一個表示通訊鏈接的Connection接口。每當Connector監聽到端口有請求時,即創建了一個Connection。

NIO的接口和BIO的接口差異實在太大了,沒辦法只能加了一個不三不四的ChannelConnection接口,確定有更好的方案,可是以我如今的水平暫時只能這樣設計下了。等之後看了Netty或者Undertow的源碼再重構吧。

重構後UML大體以下:

Server包含了1個或者多個Connector,Connector包含一個EventListener,一個EventListener包含一個EventHandler。

每當Connector接受到請求時,就構造一個Connection,Connector將Connection傳遞給EventListener,EventListener再傳遞給EventHandler。EventHandler調用Connection獲取請求數據,並寫入響應數據。

以後若是須要加入Servlet的功能,則須要添加對於的EventHandler,再經過EventHandler將請求Dispatcher到相應的Servlet中,而服務器的其他部分基本不用修改。

面向對象的設計模式功力比較弱,先設計一個勉強能用的架構先。這樣單線程Server的IO部分基本就搞好了。

相關文章
相關標籤/搜索