zoukankan      html  css  js  c++  java
  • 乞丐版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,也是正常的。

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

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

    新增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

  • 相关阅读:
    C# UDP实现通信的方法
    Leetcode 559. N叉树的最大深度
    101. 对称二叉树
    108. 将有序数组转换为二叉搜索树
    剑指 Offer 55
    Linux
    Linux
    Linux
    Linux
    Linux
  • 原文地址:https://www.cnblogs.com/stillcoolme/p/8458309.html
Copyright © 2011-2022 走看看