乞丐版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属性和方法
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。
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功能简单,如果是返回文件内容的功能,那样的话,大段大段的文件读写代码是完全无法复用的。
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,而不是每次都去改工厂。
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部分基本就搞好了。
乞丐版servlet容器第4篇的更多相关文章
- 乞丐版servlet容器第1篇
本系列参照pkpk1234大神的BeggarServletContainer,具体请访问:https://github.com/pkpk1234/BeggarServletContainer. 一步一 ...
- 乞丐版servlet容器第3篇
4 EventListener接口 让我们继续看SocketConnector中的acceptConnect方法: @Override protected void acceptConnect() t ...
- 乞丐版servlet容器第2篇
2. 监听端口接收请求 上一步中我们已经定义好了Server接口,并进行了多次重构,但是实际上那个Server是没啥毛用的东西. 现在要为其添加真正有用的功能. 大师说了,饭要一口一口吃,衣服要一件一 ...
- 对Servlet容器的补充和一个问题的请教
[0]README 0.1)本文是对 一个servlet容器 的补充: 0.2)发这个博文的最终目的是为了请教各位前辈,帮我解决一个问题,问题描述在文末, 谢谢: [1]Servlet容器 1.1) ...
- 【串线篇】spring boot使用外置的Servlet容器
嵌入式Servlet容器:应用打成可执行的jar 优点:简单.便携: 缺点:默认不支持JSP.优化定制比较复杂 (使用定制器[ServerProperties/自定义EmbeddedServletCo ...
- 【串线篇】spring boot嵌入式Servlet容器自动配置原理
EmbeddedServletContainerAutoConfiguration:嵌入式的Servlet容器自动配置? @AutoConfigureOrder(Ordered.HIGHEST_PREC ...
- 【串线篇】spring boot嵌入式Servlet容器启动原理;
什么时候创建嵌入式的Servlet容器工厂?什么时候获取嵌入式的Servlet容器并启动Tomcat: 获取嵌入式的Servlet容器工厂: 1).SpringBoot应用启动运行run方法 2).r ...
- 【串线篇】spring boot配置嵌入式servlet容器
SpringBoot默认使用Tomcat作为嵌入式的Servlet容器 问题? 一.如何定制和修改Servlet容器的相关配置 1.方法1修改和server有关的配置(ServerProperties ...
- 深入剖析tomcat之一个简单的servlet容器
上一篇,我们讲解了如果开发一个简单的Http服务器,这一篇,我们扩展一下,让我们的服务器具备servlet的解析功能. 简单介绍下Servlet接口 如果我们想要自定义一个Servlet,那么我们必须 ...
随机推荐
- HTML5 Canvas ( 图片绘制 转化为base64 ) drawImage,toDataURL
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- Java实现邮箱发送
- Spring MVC 基本储备
@RequestMapping value, method, params 必须存在参数, headers: 请求头部必须是啥样 @PathVariable 绑定URL参数 HiddenHttpMet ...
- AS3 os与version 区别 使用Capabilities类获取Flash Player的信息
AS3中flash.system.Capabilities类提供诸多静态的只读属性来描述应用程序当前所运行在的系统和运行时信息,如Flash Player,Adobe AIR,Flash Lite.通 ...
- JVM内存管理基础
JVM 虚拟机架构(图片来源: 浅析Java虚拟机结构与机制) JVM 内存区域 JVM会将Java进程所管理的内存划分为若干不同的数据区域. 这些区域有各自的用途.创建/销毁时间: (图片来源: ...
- Java并发测试
要求:模拟200个设备,尽量瞬间并发量达到200. 思路 第一种:线程池模拟200个线程——wait等待线程数达200——notifyAll唤醒所有线程 第二种:线程池模拟200个线程——阻塞线程—— ...
- 控制html元素的隐藏问题
控制元素隐藏的方式,有display:none.visibility:hidden以及不透明度设置. 一.display:none 被隐藏的元素,在页面中不占位,空出的位置会被相邻的元素占用. < ...
- 数据恢复软件extundelete介绍
linux下文件系统一般由文件名.Inode.Block三部分组成.当一个用户在Linux系统中试图访问一个文件时,系统会先根据文件名去查找它的inode,看该用户是否具有访问这个文件的权限.如果有, ...
- How to Pronounce OPPORTUNITY
How to Pronounce OPPORTUNITY Share Tweet Share Take the opportunity to learn this word! Learn how t ...
- Haskell语言学习笔记(25)MonadState, State, StateT
MonadState 类型类 class Monad m => MonadState s m | m -> s where get :: m s get = state (\s -> ...