4 EventListener接口

让我们继续看SocketConnector中的acceptConnect方法:

  1. @Override
  2. protected void acceptConnect() throws ConnectorException {
  3. new Thread(() -> {
  4. while (true && started) {
  5. Socket socket = null;
  6. try {
  7. socket = serverSocket.accept();
  8. LOGGER.info("新增连接:" + socket.getInetAddress() + ":" + socket.getPort());
  9. } catch (IOException e) {
  10. //单个Socket异常,不要影响整个Connector
  11. LOGGER.error(e.getMessage(), e);
  12. } finally {
  13. IoUtils.closeQuietly(socket);
  14. }
  15. }
  16. }).start();
  17. }

注意socket = serverSocket.accept(),这里获取到socket之后只是打印日志,并没获取socket的输入输出进行操作。

操作socket的输入和输出是否应该在SocketConnector中?

这时大师又说话了,Connector责任是啥,就是管理connect的啊,connect怎么使用,关它屁事。

再看那个无限循环,像不像再等待事件来临啊,成功accept一个socket就是一个事件,对scoket的使用,其实就是事件响应嘛。

OK,让我们按照这个思路来重构一下,目的就是加入事件机制,并将对具体实现的依赖控制在那几个工厂类里面去。

新增接口EventListener接口进行事件监听

  1. public interface EventListener<T> {
  2. /**
  3. * 事件发生时的回调方法
  4. * @param event 事件对象
  5. * @throws EventException 处理事件时异常都转换为该异常抛出
  6. */
  7. void onEvent(T event) throws EventException;
  8. }

为Socket事件实现一下,acceptConnect中打印日志的语句可以移动到这来

  1. public class SocketEventListener implements EventListener<Socket> {
  2. private static final Logger LOGGER = LoggerFactory.getLogger(SocketEventListener.class);
  3. @Override
  4. public void onEvent(Socket socket) throws EventException {
  5. LOGGER.info("新增连接:" + socket.getInetAddress() + ":" + socket.getPort());
  6. }

重构Connector,添加事件机制,注意whenAccept方法调用了eventListener

  1. public class SocketConnector extends Connector<Socket> {
  2. ... ...
  3. private final EventListener<Socket> eventListener;
  4. public SocketConnector(int port, EventListener<Socket> eventListener) {
  5. this.port = port;
  6. this.eventListener = eventListener;
  7. }
  8. @Override
  9. protected void acceptConnect() throws ConnectorException {
  10. new Thread(() -> {
  11. while (true && started) {
  12. Socket socket = null;
  13. try {
  14. socket = serverSocket.accept();
  15. whenAccept(socket);
  16. } catch (Exception e) {
  17. //单个Socket异常,不要影响整个Connector
  18. LOGGER.error(e.getMessage(), e);
  19. } finally {
  20. IoUtils.closeQuietly(socket);
  21. }
  22. }
  23. }).start();
  24. }
  25. @Override
  26. protected void whenAccept(Socket socketConnect) throws ConnectorException {
  27. eventListener.onEvent(socketConnect);
  28. }
  29. ... ...
  30. }

重构ServerFactory,添加对具体实现的依赖

  1. public class ServerFactory {
  2. public static Server getServer(ServerConfig serverConfig) {
  3. List<Connector> connectorList = new ArrayList<>();
  4. SocketEventListener socketEventListener = new SocketEventListener();
  5. ConnectorFactory connectorFactory =
  6. new SocketConnectorFactory(new SocketConnectorConfig(serverConfig.getPort()), socketEventListener);
  7. connectorList.add(connectorFactory.getConnector());
  8. return new SimpleServer(serverConfig, connectorList);
  9. }
  10. }

再运行所有单元测试,一切都OK。

现在让我们来操作socket,实现一个echo功能的server吧。

直接添加到SocketEventListener中

  1. public class SocketEventListener implements EventListener<Socket> {
  2. private static final Logger LOGGER = LoggerFactory.getLogger(SocketEventListener.class);
  3. @Override
  4. public void onEvent(Socket socket) throws EventException {
  5. LOGGER.info("新增连接:" + socket.getInetAddress() + ":" + socket.getPort());
  6. try {
  7. echo(socket);
  8. } catch (IOException e) {
  9. throw new EventException(e);
  10. }
  11. }
  12. private void echo(Socket socket) throws IOException {
  13. InputStream inputstream = null;
  14. OutputStream outputStream = null;
  15. try {
  16. inputstream = socket.getInputStream();
  17. outputStream = socket.getOutputStream();
  18. Scanner scanner = new Scanner(inputstream);
  19. PrintWriter printWriter = new PrintWriter(outputStream);
  20. printWriter.append("Server connected.Welcome to echo.\n");
  21. printWriter.flush();
  22. while (scanner.hasNextLine()) {
  23. String line = scanner.nextLine();
  24. if (line.equals("stop")) {
  25. printWriter.append("bye bye.\n");
  26. printWriter.flush();
  27. break;
  28. } else {
  29. printWriter.append(line);
  30. printWriter.append("\n");
  31. printWriter.flush();
  32. }
  33. }
  34. } finally {
  35. IoUtils.closeQuietly(inputstream);
  36. IoUtils.closeQuietly(outputStream);
  37. }
  38. }
  39. }

之前都是在单元测试里面启动Server的,这次需要启动Server后,用telnet去使用echo功能。

所以再为Server编写一个启动类,在其main方法里面启动Server

  1. public class BootStrap {
  2. public static void main(String[] args) throws IOException {
  3. ServerConfig serverConfig = new ServerConfig();
  4. Server server = ServerFactory.getServer(serverConfig);
  5. server.start();
  6. }
  7. }

服务器启动后,使用telnet进行验证,打开cmd,然后输入telnet localhost 端口,端口是ServerConfig里面的默认端口或者其他,回车就可以交互了。

到现在为止,我们的服务器终于有了实际功能,下一步终于可以去实现请求静态资源的功能了。

完整代码:https://github.com/pkpk1234/BeggarServletContainer/tree/step4

5 EventHandler接口和FileEventHandler实现

首先重构代码,让事件监听和事件处理分离开,各自责任更加独立

否则想将Echo功能替换为返回静态文件,又需要到处改代码。

将责任分开后,只需要传入不同的事件处理器,即可实现不同效果。

增加EventHandler接口专门进行事件处理,SocketEventListener类中事件处理抽取到专门的EchoEventHandler实现中。

提出AbstractEventListener类,规定了事件处理的模板

  1. public abstract class AbstractEventListener<T> implements EventListener<T> {
  2. /**
  3. * 事件处理流程模板方法
  4. * @param event 事件对象
  5. * @throws EventException
  6. */
  7. @Override
  8. public void onEvent(T event) throws EventException {
  9. EventHandler<T> eventHandler = getEventHandler(event);
  10. eventHandler.handle(event);
  11. }
  12. /**
  13. * 返回事件处理器
  14. * @param event
  15. * @return
  16. */
  17. protected abstract EventHandler<T> getEventHandler(T event);
  18. }

SocketEventListener重构为通过构造器传入事件处理器

  1. public class SocketEventListener extends AbstractEventListener<Socket> {
  2. private final EventHandler<Socket> eventHandler;
  3. public SocketEventListener(EventHandler<Socket> eventHandler) {
  4. this.eventHandler = eventHandler;
  5. }
  6. @Override
  7. protected EventHandler<Socket> getEventHandler(Socket event) {
  8. return eventHandler;
  9. }
  10. }

EchoEventHandler实现Echo

  1. public class EchoEventHandler extends AbstractEventHandler<Socket> {
  2. @Override
  3. protected void doHandle(Socket socket) {
  4. InputStream inputstream = null;
  5. OutputStream outputStream = null;
  6. try {
  7. inputstream = socket.getInputStream();
  8. outputStream = socket.getOutputStream();
  9. Scanner scanner = new Scanner(inputstream);
  10. PrintWriter printWriter = new PrintWriter(outputStream);
  11. printWriter.append("Server connected.Welcome to echo.\n");
  12. printWriter.flush();
  13. while (scanner.hasNextLine()) {
  14. String line = scanner.nextLine();
  15. if (line.equals("stop")) {
  16. printWriter.append("bye bye.\n");
  17. printWriter.flush();
  18. break;
  19. } else {
  20. printWriter.append(line);
  21. printWriter.append("\n");
  22. printWriter.flush();
  23. }
  24. }
  25. } catch (IOException e) {
  26. throw new HandlerException(e);
  27. } finally {
  28. IoUtils.closeQuietly(inputstream);
  29. IoUtils.closeQuietly(outputStream);
  30. }
  31. }
  32. }

再次将对具体实现的依赖限制到Factory中

  1. public class ServerFactory {
  2. /**
  3. * 返回Server实例
  4. *
  5. * @return
  6. */
  7. public static Server getServer(ServerConfig serverConfig) {
  8. List<Connector> connectorList = new ArrayList<>();
  9. //传入Echo事件处理器
  10. SocketEventListener socketEventListener = new SocketEventListener(new EchoEventHandler());
  11. ConnectorFactory connectorFactory =
  12. new SocketConnectorFactory(new SocketConnectorConfig(serverConfig.getPort()), socketEventListener);
  13. connectorList.add(connectorFactory.getConnector());
  14. return new SimpleServer(serverConfig, connectorList);
  15. }
  16. }

执行单元测试,一切正常。运行Server,用telnet进行echo,也是正常的。

现在添加返回静态文件功能。功能大致如下:

  1. 服务器使用user.dir作为根目录。
  2. 控制台输入文件路径,如果文件是目录,则打印目录中的文件列表;如果文件不是目录,且可读,则返回文件内容;如果不满足前面两种场景,返回文件找不到

新增FileEventHandler

  1. public class FileEventHandler extends AbstractEventHandler<Socket>{
  2. private final String docBase;
  3. public FileEventHandler(String docBase) {
  4. this.docBase = docBase;
  5. }
  6. @Override
  7. protected void doHandler(Socket socket) {
  8. getFile(socket);
  9. }
  10. private void getFile(Socket socket) {
  11. InputStream inputStream = null;
  12. OutputStream outputStream = null;
  13. try{
  14. inputStream = socket.getInputStream();
  15. outputStream = socket.getOutputStream();
  16. Scanner scanner = new Scanner(inputStream, "UTF-8");
  17. PrintWriter printWriter = new PrintWriter(outputStream);
  18. printWriter.append("Server connected.Welcome to File Server.\n");
  19. printWriter.flush();
  20. while (scanner.hasNextLine()){
  21. String line = scanner.nextLine();
  22. if(line.equals("stop")){
  23. printWriter.append("bye bye.\n");
  24. printWriter.flush();
  25. break;
  26. }else {
  27. Path filePath = Paths.get(this.docBase, line);
  28. if(Files.isDirectory(filePath)){
  29. printWriter.append("目录 ").append(filePath.toString()).append(" 下有文件: ").append("\n");
  30. try{
  31. DirectoryStream<Path> stream = Files.newDirectoryStream(filePath);
  32. for (Path path: stream){
  33. printWriter.append(path.getFileName().toString()).append("\n").flush();
  34. }
  35. }catch(IOException e){
  36. e.printStackTrace();
  37. }
  38. //如果文件可读,就打印文件内容
  39. } else if(Files.isReadable(filePath)){
  40. printWriter.append("File: ").append(filePath.toString()).append(" 的内容是: ").append("\n").flush();
  41. Files.copy(filePath, outputStream);
  42. printWriter.append("\n");
  43. //其他情况返回文件找不到
  44. } else {
  45. printWriter.append("File ").append(filePath.toString())
  46. .append(" is not found.").append("\n").flush();
  47. }
  48. }
  49. }
  50. }catch (IOException e) {
  51. throw new HandlerException(e);
  52. } finally {
  53. IoUtils.closeQuietly(inputStream);
  54. IoUtils.closeQuietly(outputStream);
  55. }
  56. }
  57. }

修改ServerFactory,使用FileEventHandler

  1. public class ServerFactory {
  2. /**
  3. * 返回Server实例
  4. * @return
  5. */
  6. public static Server getServer(ServerConfig serverConfig) {
  7. List<Connector> connectorList = new ArrayList<>();
  8. //EventHandler eventHandler =new EchoEventHandler();
  9. EventHandler eventHandler = new FileEventHandler(System.getProperty("user.dir"));
  10. SocketEventListener socketEventListener = new SocketEventListener(eventHandler);
  11. ConnectorFactory connectorFactory = new SocketConnectorFactory(new SocketConnectorConfig(serverConfig.getPort()), socketEventListener);
  12. connectorList.add(connectorFactory.getConnector());
  13. return new SimpleServer(serverConfig, connectorList);
  14. }
  15. }

运行BootStrap启动Server进行验证:



绿色框:输入回车,返回目录下文件列表。

黄色框:输入README.MD,返回文件内容

蓝色框:输入不存在的文件,返回文件找不到。

完整代码:https://github.com/pkpk1234/BeggarServletContainer/tree/step5

乞丐版servlet容器第3篇的更多相关文章

  1. 乞丐版servlet容器第1篇

    本系列参照pkpk1234大神的BeggarServletContainer,具体请访问:https://github.com/pkpk1234/BeggarServletContainer. 一步一 ...

  2. 乞丐版servlet容器第4篇

    6. NIOConnector 现在为Server添加NIOConnector,添加之前可以发现我们的代码其实是有问题的.比如现在的代码是无法让服务器支持同时监听多个端口和IP的,如同时监听 127. ...

  3. 乞丐版servlet容器第2篇

    2. 监听端口接收请求 上一步中我们已经定义好了Server接口,并进行了多次重构,但是实际上那个Server是没啥毛用的东西. 现在要为其添加真正有用的功能. 大师说了,饭要一口一口吃,衣服要一件一 ...

  4. 对Servlet容器的补充和一个问题的请教

    [0]README 0.1)本文是对 一个servlet容器  的补充: 0.2)发这个博文的最终目的是为了请教各位前辈,帮我解决一个问题,问题描述在文末, 谢谢: [1]Servlet容器 1.1) ...

  5. 【串线篇】spring boot使用外置的Servlet容器

    嵌入式Servlet容器:应用打成可执行的jar 优点:简单.便携: 缺点:默认不支持JSP.优化定制比较复杂 (使用定制器[ServerProperties/自定义EmbeddedServletCo ...

  6. 【串线篇】spring boot嵌入式Servlet容器自动配置原理

    EmbeddedServletContainerAutoConfiguration:嵌入式的Servlet容器自动配置? @AutoConfigureOrder(Ordered.HIGHEST_PREC ...

  7. 【串线篇】spring boot嵌入式Servlet容器启动原理;

    什么时候创建嵌入式的Servlet容器工厂?什么时候获取嵌入式的Servlet容器并启动Tomcat: 获取嵌入式的Servlet容器工厂: 1).SpringBoot应用启动运行run方法 2).r ...

  8. 【串线篇】spring boot配置嵌入式servlet容器

    SpringBoot默认使用Tomcat作为嵌入式的Servlet容器 问题? 一.如何定制和修改Servlet容器的相关配置 1.方法1修改和server有关的配置(ServerProperties ...

  9. 深入剖析tomcat之一个简单的servlet容器

    上一篇,我们讲解了如果开发一个简单的Http服务器,这一篇,我们扩展一下,让我们的服务器具备servlet的解析功能. 简单介绍下Servlet接口 如果我们想要自定义一个Servlet,那么我们必须 ...

随机推荐

  1. apache http get 和 post 请求

    1.首先要把jar依赖进项目 <dependency> <groupId>org.apache.httpcomponents</groupId> <artif ...

  2. 打印机 KX-MB788CN 佳能

    打印机 KX-MB788CN http://panasonic.cn/oa/help/download.asp?type=drivers&pid=1066 佳能打印机 腾彩 PIXMA MP2 ...

  3. 可视化库-seaborn-热力图(第五天)

    1. 画一个基本的热力图, 通过热力图用来观察样本的分布情况 import matplotlib.pyplot as plt import numpy as np np.random.seed(0) ...

  4. 锋利的BFC

    在初学前端的时候,我们会经常碰到各种各样的布局问题,尤其当使用浮动的时候,然而学习了BFC之后,其中的一些怪异现象,也因此成为理所当然,会有一种拨开云雾的快感. 下面简单介绍下BFC,究竟什么是BFC ...

  5. 扩展C#与元编程(二)

    如果你对Windows Workflow Foundation(WF)一无所知,当看到扩展C#与元编程(一)中由MW编译器生成的FirstLook.mw.cs时,也许这么在想:我KAO,这是C#版的汇 ...

  6. Musle比对软件

    下载地址:http://www.drive5.com/muscle/downloads.htm 1)运行: win+R然后输入cmd,然后cd进入muscle目录 2) 比对: muscle3.8.3 ...

  7. dubbo-admin 管理平台

    一.前言 dubbo的使用,其实只需要有注册中心,消费者,提供者这三个就可以使用了,但是并不能看到有哪些消费者和提供者,为了更好的调试,发现问题,解决问题,因此引入dubbo-admin.通过dubb ...

  8. sqlserver批量导出存储过程、函数、视图

    select text from syscomments s1 join sysobjects s2 on s1.id=s2.id  where xtype = 'V' xtype V   视图 P  ...

  9. 为什么JAVA要提供 wait/notify 机制?是为了避免轮询带来的性能损失

    wait/notify  机制是为了避免轮询带来的性能损失. 为了说清道理,我们用“图书馆借书”这个经典例子来作解释. 一本书同时只能借给一个人.现在有一本书,图书馆已经把这本书借了张三. 在简单的s ...

  10. Guava Cache -- MapMaker.makeComputingMap测试

    canal中很多处使用了MigrateMap.makeComputingMap(Function<? super K, ? extends V> computingFunction)方法, ...