乞丐版servlet容器第3篇
4 EventListener接口
让我们继续看SocketConnector中的acceptConnect方法:
@Override
protected void acceptConnect() throws ConnectorException {
new Thread(() -> {
while (true && started) {
Socket socket = null;
try {
socket = serverSocket.accept();
LOGGER.info("新增连接:" + socket.getInetAddress() + ":" + socket.getPort());
} catch (IOException e) {
//单个Socket异常,不要影响整个Connector
LOGGER.error(e.getMessage(), e);
} finally {
IoUtils.closeQuietly(socket);
}
}
}).start();
}
注意socket = serverSocket.accept(),这里获取到socket之后只是打印日志,并没获取socket的输入输出进行操作。
操作socket的输入和输出是否应该在SocketConnector中?
这时大师又说话了,Connector责任是啥,就是管理connect的啊,connect怎么使用,关它屁事。
再看那个无限循环,像不像再等待事件来临啊,成功accept一个socket就是一个事件,对scoket的使用,其实就是事件响应嘛。
OK,让我们按照这个思路来重构一下,目的就是加入事件机制,并将对具体实现的依赖控制在那几个工厂类里面去。
新增接口EventListener接口进行事件监听
public interface EventListener<T> {
/**
* 事件发生时的回调方法
* @param event 事件对象
* @throws EventException 处理事件时异常都转换为该异常抛出
*/
void onEvent(T event) throws EventException;
}
为Socket事件实现一下,acceptConnect中打印日志的语句可以移动到这来
public class SocketEventListener implements EventListener<Socket> {
private static final Logger LOGGER = LoggerFactory.getLogger(SocketEventListener.class);
@Override
public void onEvent(Socket socket) throws EventException {
LOGGER.info("新增连接:" + socket.getInetAddress() + ":" + socket.getPort());
}
重构Connector,添加事件机制,注意whenAccept方法调用了eventListener
public class SocketConnector extends Connector<Socket> {
... ...
private final EventListener<Socket> eventListener;
public SocketConnector(int port, EventListener<Socket> eventListener) {
this.port = port;
this.eventListener = eventListener;
}
@Override
protected void acceptConnect() throws ConnectorException {
new Thread(() -> {
while (true && started) {
Socket socket = null;
try {
socket = serverSocket.accept();
whenAccept(socket);
} catch (Exception e) {
//单个Socket异常,不要影响整个Connector
LOGGER.error(e.getMessage(), e);
} finally {
IoUtils.closeQuietly(socket);
}
}
}).start();
}
@Override
protected void whenAccept(Socket socketConnect) throws ConnectorException {
eventListener.onEvent(socketConnect);
}
... ...
}
重构ServerFactory,添加对具体实现的依赖
public class ServerFactory {
public static Server getServer(ServerConfig serverConfig) {
List<Connector> connectorList = new ArrayList<>();
SocketEventListener socketEventListener = new SocketEventListener();
ConnectorFactory connectorFactory =
new SocketConnectorFactory(new SocketConnectorConfig(serverConfig.getPort()), socketEventListener);
connectorList.add(connectorFactory.getConnector());
return new SimpleServer(serverConfig, connectorList);
}
}
再运行所有单元测试,一切都OK。
现在让我们来操作socket,实现一个echo功能的server吧。
直接添加到SocketEventListener中
public class SocketEventListener implements EventListener<Socket> {
private static final Logger LOGGER = LoggerFactory.getLogger(SocketEventListener.class);
@Override
public void onEvent(Socket socket) throws EventException {
LOGGER.info("新增连接:" + socket.getInetAddress() + ":" + socket.getPort());
try {
echo(socket);
} catch (IOException e) {
throw new EventException(e);
}
}
private void echo(Socket socket) throws IOException {
InputStream inputstream = null;
OutputStream outputStream = null;
try {
inputstream = socket.getInputStream();
outputStream = socket.getOutputStream();
Scanner scanner = new Scanner(inputstream);
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.append("Server connected.Welcome to echo.\n");
printWriter.flush();
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
if (line.equals("stop")) {
printWriter.append("bye bye.\n");
printWriter.flush();
break;
} else {
printWriter.append(line);
printWriter.append("\n");
printWriter.flush();
}
}
} finally {
IoUtils.closeQuietly(inputstream);
IoUtils.closeQuietly(outputStream);
}
}
}
之前都是在单元测试里面启动Server的,这次需要启动Server后,用telnet去使用echo功能。
所以再为Server编写一个启动类,在其main方法里面启动Server
public class BootStrap {
public static void main(String[] args) throws IOException {
ServerConfig serverConfig = new ServerConfig();
Server server = ServerFactory.getServer(serverConfig);
server.start();
}
}
服务器启动后,使用telnet进行验证,打开cmd,然后输入telnet localhost 端口,端口是ServerConfig里面的默认端口或者其他,回车就可以交互了。

到现在为止,我们的服务器终于有了实际功能,下一步终于可以去实现请求静态资源的功能了。
完整代码:https://github.com/pkpk1234/BeggarServletContainer/tree/step4
5 EventHandler接口和FileEventHandler实现
首先重构代码,让事件监听和事件处理分离开,各自责任更加独立。
否则想将Echo功能替换为返回静态文件,又需要到处改代码。
将责任分开后,只需要传入不同的事件处理器,即可实现不同效果。
增加EventHandler接口专门进行事件处理,SocketEventListener类中事件处理抽取到专门的EchoEventHandler实现中。
提出AbstractEventListener类,规定了事件处理的模板
public abstract class AbstractEventListener<T> implements EventListener<T> {
/**
* 事件处理流程模板方法
* @param event 事件对象
* @throws EventException
*/
@Override
public void onEvent(T event) throws EventException {
EventHandler<T> eventHandler = getEventHandler(event);
eventHandler.handle(event);
}
/**
* 返回事件处理器
* @param event
* @return
*/
protected abstract EventHandler<T> getEventHandler(T event);
}
SocketEventListener重构为通过构造器传入事件处理器
public class SocketEventListener extends AbstractEventListener<Socket> {
private final EventHandler<Socket> eventHandler;
public SocketEventListener(EventHandler<Socket> eventHandler) {
this.eventHandler = eventHandler;
}
@Override
protected EventHandler<Socket> getEventHandler(Socket event) {
return eventHandler;
}
}
EchoEventHandler实现Echo
public class EchoEventHandler extends AbstractEventHandler<Socket> {
@Override
protected void doHandle(Socket socket) {
InputStream inputstream = null;
OutputStream outputStream = null;
try {
inputstream = socket.getInputStream();
outputStream = socket.getOutputStream();
Scanner scanner = new Scanner(inputstream);
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.append("Server connected.Welcome to echo.\n");
printWriter.flush();
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
if (line.equals("stop")) {
printWriter.append("bye bye.\n");
printWriter.flush();
break;
} else {
printWriter.append(line);
printWriter.append("\n");
printWriter.flush();
}
}
} catch (IOException e) {
throw new HandlerException(e);
} finally {
IoUtils.closeQuietly(inputstream);
IoUtils.closeQuietly(outputStream);
}
}
}
再次将对具体实现的依赖限制到Factory中
public class ServerFactory {
/**
* 返回Server实例
*
* @return
*/
public static Server getServer(ServerConfig serverConfig) {
List<Connector> connectorList = new ArrayList<>();
//传入Echo事件处理器
SocketEventListener socketEventListener = new SocketEventListener(new EchoEventHandler());
ConnectorFactory connectorFactory =
new SocketConnectorFactory(new SocketConnectorConfig(serverConfig.getPort()), socketEventListener);
connectorList.add(connectorFactory.getConnector());
return new SimpleServer(serverConfig, connectorList);
}
}
执行单元测试,一切正常。运行Server,用telnet进行echo,也是正常的。
现在添加返回静态文件功能。功能大致如下:
- 服务器使用user.dir作为根目录。
- 控制台输入文件路径,如果文件是目录,则打印目录中的文件列表;如果文件不是目录,且可读,则返回文件内容;如果不满足前面两种场景,返回文件找不到
新增FileEventHandler
public class FileEventHandler extends AbstractEventHandler<Socket>{
private final String docBase;
public FileEventHandler(String docBase) {
this.docBase = docBase;
}
@Override
protected void doHandler(Socket socket) {
getFile(socket);
}
private void getFile(Socket socket) {
InputStream inputStream = null;
OutputStream outputStream = null;
try{
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
Scanner scanner = new Scanner(inputStream, "UTF-8");
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.append("Server connected.Welcome to File Server.\n");
printWriter.flush();
while (scanner.hasNextLine()){
String line = scanner.nextLine();
if(line.equals("stop")){
printWriter.append("bye bye.\n");
printWriter.flush();
break;
}else {
Path filePath = Paths.get(this.docBase, line);
if(Files.isDirectory(filePath)){
printWriter.append("目录 ").append(filePath.toString()).append(" 下有文件: ").append("\n");
try{
DirectoryStream<Path> stream = Files.newDirectoryStream(filePath);
for (Path path: stream){
printWriter.append(path.getFileName().toString()).append("\n").flush();
}
}catch(IOException e){
e.printStackTrace();
}
//如果文件可读,就打印文件内容
} else if(Files.isReadable(filePath)){
printWriter.append("File: ").append(filePath.toString()).append(" 的内容是: ").append("\n").flush();
Files.copy(filePath, outputStream);
printWriter.append("\n");
//其他情况返回文件找不到
} else {
printWriter.append("File ").append(filePath.toString())
.append(" is not found.").append("\n").flush();
}
}
}
}catch (IOException e) {
throw new HandlerException(e);
} finally {
IoUtils.closeQuietly(inputStream);
IoUtils.closeQuietly(outputStream);
}
}
}
修改ServerFactory,使用FileEventHandler
public class ServerFactory {
/**
* 返回Server实例
* @return
*/
public static Server getServer(ServerConfig serverConfig) {
List<Connector> connectorList = new ArrayList<>();
//EventHandler eventHandler =new EchoEventHandler();
EventHandler eventHandler = new FileEventHandler(System.getProperty("user.dir"));
SocketEventListener socketEventListener = new SocketEventListener(eventHandler);
ConnectorFactory connectorFactory = new SocketConnectorFactory(new SocketConnectorConfig(serverConfig.getPort()), socketEventListener);
connectorList.add(connectorFactory.getConnector());
return new SimpleServer(serverConfig, connectorList);
}
}
运行BootStrap启动Server进行验证:

绿色框:输入回车,返回目录下文件列表。
黄色框:输入README.MD,返回文件内容
蓝色框:输入不存在的文件,返回文件找不到。
完整代码:https://github.com/pkpk1234/BeggarServletContainer/tree/step5
乞丐版servlet容器第3篇的更多相关文章
- 乞丐版servlet容器第1篇
本系列参照pkpk1234大神的BeggarServletContainer,具体请访问:https://github.com/pkpk1234/BeggarServletContainer. 一步一 ...
- 乞丐版servlet容器第4篇
6. NIOConnector 现在为Server添加NIOConnector,添加之前可以发现我们的代码其实是有问题的.比如现在的代码是无法让服务器支持同时监听多个端口和IP的,如同时监听 127. ...
- 乞丐版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,那么我们必须 ...
随机推荐
- leetcode342
public class Solution { public bool IsPowerOfFour(int num) { ) && ((num & (num - )) == ) ...
- mysql的时区错误的解决办法
十二月 02, 2018 9:16:19 下午 com.mchange.v2.resourcepool.BasicResourcePool 警告: Having failed to acquire a ...
- word 2013 自动保存太慢,下面读条起码3分钟
该问题有可能是应用干扰或者安全设置问题导致的. 建议您尝试以下方法: 方法一: 尝试使用干净启动来暂时禁用计算机启动时所加载的第三方程序来进一步做测试: 如何在Windows 中执行干净启动步骤 具体 ...
- J2SE 8的泛型
泛型的简单使用 1. 泛型一般用E表示集合中元素;k和v表示Map中的key和value;R表示return值;T/U/S表示任意类型 //(1) 简单单个元素的泛型 Box<String> ...
- request传递参数
当客户请求时,Servlet容器创建SrevletRequest对象(用于封装客户的请求信息),这个对象将被容器作为service()方法的参数之一传递给Srevlet,Servlet可以利用Serv ...
- http://dl-ssl.google.com/android/eclipse/ 无法访问的解决办法
很明显,国内网络和谐掉了google这个服务器,可以强制指向下Host ,以下是添加内容: 74.125.237.1 dl-ssl.google.com 具体操作方式: 点击桌面左下角开始菜单中的搜索 ...
- 一文看懂Stacking!(含Python代码)
一文看懂Stacking!(含Python代码) https://mp.weixin.qq.com/s/faQNTGgBZdZyyZscdhjwUQ
- Chi-Square Statistic/Distribution
. 1.What is a Chi Square Test? 卡方检验有两种类型.两者使用卡方统计量和分布的目的不同. 第一种:卡方拟合优度检验确定样本数据是否与总体匹配.(这里不介绍) 第二种:独立 ...
- JAVA——遍历
关于遍历,发现了个坑. 详见如下: package com.fxl.test; import java.util.ArrayList; import java.util.Iterator; impor ...
- SVN的基本操作
右键SVN Commit 提交成功了,我们把SVN的服务器端刷新一下 所有的操作如果只是删除本地的文件都不会影响服务器端的文件,除非右键SVN Commit删除文件或者是新增文件才会对服务器端的仓库里 ...