如今爲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端口,功能是勉強實現了,可是架構設計是有重大缺陷的,進一步添加功能以前,須要重構好架構才行。線程
繼續抽象的過程,不管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部分基本就搞好了。