JavaNIO--2.实现ECHO服务器

本文介绍如何使用Java NIO技术实现一个简单的ECHO服务器,详细讲解了NIO核心组件Selector和Channel的使用方法,以及如何通过Buffer进行数据读写。此外,还提供了完整的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

JavaNIO技术实现ECHO服务器

所谓ECHO服务器就是客户端发送到服务器端什么内容,服务器端就返回什么内容的一种服务器,者几乎是最简单的网络服务器(当然还有更简单的抛弃服务器)

阅读需要基础:JavaNIO基础

1.NIO核心组件的使用

NIO核心组件主要包括SelectorChannel,而Buffer主要用于和Channel进行数据交互,所以不在此作详细的使用介绍。

1.1初始化NIO组件

public class NioServer {

    private Selector selector;
    private ServerSocketChannel serverSocketChannel;
    private int port;

    public NioServer(int port) throws IOException {
        // 打开一个ServerSocketChannel
        serverSocketChannel = ServerSocketChannel.open();
        // 设置为非阻塞模式才能注册到Selector
        serverSocketChannel.configureBlocking(false);
        // 打开一个选择器
        selector = Selector.open();
        this.port = port;   
    }

    // 启动服务器的方法
    private void startServer() {
        try {
            serverSocketChannel.bind(new InetSocketAddress(port));
            // 注册该通道到选择器,注意兴趣操作是SelectionKey.OP_ACCEPT
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            selectLoop();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }
}

这里写图片描述

  1. 需要了解的是Channel需要设置为非阻塞模式才能注册到选择器

  2. Channel调用register()方法时需要指定兴趣操作,意思就是该选择器会监听这个通道有没有准备好可以执行的操作,兴趣操作有:SelectionKey.OP_ACCEPTSelectionKey.OP_READSelectionKey.OP_WRITESelectionKey.OP_CONNECT,分别对应的是ServerSocketChannelaccept()方法可以执行(不需阻塞),SocketChannelread()/write()方法可以执行(不需阻塞),以及SocketChannel内含的Socketconnect()方法可以调用(不需阻塞)。

如果不太了解NIO对应的操作模型,可以去参考我的上一篇博客:IO多路复用和NIO

1.2Accept组件

    private void acceptClient(SelectionKey selectionKey) throws IOException {
        // 与对端Socket建立连接
        SocketChannel socketChannel = serverSocketChannel.accept();

        if (socketChannel != null) {
            System.err.println("接收到一个连接,对端IP为:"+socketChannel.socket().getInetAddress());
        }
        // 将接收到的SocketChannel注册到Selector,注意此时通道要设置为非阻塞模式,且兴趣操作为OP_READ
        socketChannel.configureBlocking(false);
        socketChannel.register(selector, SelectionKey.OP_READ);
    }

使用和传统ServerSocketaccept()方法流程一致,需要注意的是,传统的accept()调用时会阻塞直到建立一个TCP连接,而使用Selector选择器可以避免阻塞,确保调用该方法时一定有一个(或多个)Socket连接已经在等待建立。

1.3SelectLoop(核心组件)

可以看到一个java.nio.channels.Selector可以注册多个通道,Selector可以监听注册到自身的通道的状态。

    private void selectLoop() throws IOException {
        while(true) {
            // select()方法会阻塞,直到有注册到该选择器的通道有兴趣事件准备完毕
            selector.select();
            // selectedKeys()方法会获得有兴趣事件发生的通道,注册得到的SelectionKey的集合
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while(iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                // 循环判断其中的key
                if (selectionKey.isAcceptable()) {
                    // 如果key处于可接受状态,就进入接收函数
                    acceptClient(selectionKey);
                }else if(selectionKey.isReadable()) {
                    // 如果key处于可读状态,就进入读函数
                    readDate(selectionKey);
                }
            }
            // 每次处理完通道事件以后,要进行一次清空
            selectionKeys.clear();
        }
    }

可以看到,通过调用选择器的select()会不断的得到将要发生事件通道,只要是注册到该选择器的通道,都会被轮询一次,而我们通过while循环,可以做到单线程无阻塞I/O。

2.NIO通道读写(Buffer)

2.1读取通道内容

    private void readDate(SelectionKey selectionKey) throws IOException {

        // 每一次都先获取之前绑定在这个key上的buffer
        ByteBuffer oldBuffer = (ByteBuffer)selectionKey.attachment();

        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        ByteBuffer newBuffer = ByteBuffer.allocate(64);

        int read;
        while((read = socketChannel.read(newBuffer))<=0) {
            return;
        }

        newBuffer.flip();
        // 读取Buffer,看是否有换行符
        String line = readLine(newBuffer);
        if (line != null) {

            // 如果这次读到了行结束符,就将原来不含有行结束符的buffer合并位一行
            String sendData = readLine(mergeBuffer(oldBuffer, newBuffer));
            if (readLineContent(sendData).equalsIgnoreCase("exit")) { // 如果这一行的内容是exit就断开连接
                socketChannel.close();
                return;
            }
            // 然后直接发送回到客户端
            ByteBuffer sendBuffer = ByteBuffer.wrap(sendData.getBytes("utf-8"));
            while (sendBuffer.hasRemaining()) {
                socketChannel.write(sendBuffer);
            }
            selectionKey.attach(null);
        }else {
            // 如果这次没读到行结束付,就将这次读的内容和原来的内容合并,并刷新绑定到key对象上
            selectionKey.attach(mergeBuffer(oldBuffer, newBuffer));
        }

    }

这里写图片描述

2.2Buffer处理辅助方法

/**
     * 读取ByteBuffer直到一行的末尾
     * 返回这一行的内容,包括换行符
     * 
     * @param buffer
     * @return String 读取到行末的内容,包括换行符 ; null 如果没有换行符
     * @throws UnsupportedEncodingException
     */
    private String readLine(ByteBuffer buffer) throws UnsupportedEncodingException {
        // windows中的换行符表示手段 "\r\n"
        // 基于windows的软件发送的换行符是会是CR和LF
        char CR = '\r';
        char LF = '\n';

        boolean crFound = false;
        int index = 0;
        int len = buffer.limit();
        buffer.rewind();
        while(index < len) {
            byte temp = buffer.get();
            if (temp == CR) {
                crFound = true;
            }
            if (crFound && temp == LF) {
                // Arrays.copyOf(srcArr,length)方法会返回一个 源数组中的长度到length位 的新数组
                return new String(Arrays.copyOf(buffer.array(), index+1),"utf-8");
            }
            index ++;
        }
        return null;
    }

    /**
     * 获取一行的内容,不包括换行符
     * @param buffer
     * @return String 行的内容
     * @throws UnsupportedEncodingException
     */
    private String readLineContent(String line) throws UnsupportedEncodingException {
        return line.substring(0, line.length() - 2);
    }

    /**
     * 对传入的Buffer进行拼接
     * @param oldBuffer
     * @param newBuffer
     * @return ByteBuffer 拼接后的Buffer
     */
    public static ByteBuffer mergeBuffer(ByteBuffer oldBuffer,ByteBuffer newBuffer) {
        // 如果原来的Buffer是null就直接返回
        if (oldBuffer == null) {
            return newBuffer;
        }
        // 如果原来的Buffer的剩余长度可容纳新的buffer则直接拼接
        newBuffer.rewind();
        if (oldBuffer.remaining() > (newBuffer.limit()-newBuffer.position())) {
            return oldBuffer.put(newBuffer);
        }

        // 如果不是以上两种情况就构建新的Buffer进行拼接
        int oldSize = oldBuffer != null?oldBuffer.limit():0;
        int newSize = newBuffer != null?newBuffer.limit():0;
        ByteBuffer result = ByteBuffer.allocate(oldSize+newSize);

        result.put(Arrays.copyOfRange(oldBuffer.array(), 0, oldSize));
        result.put(Arrays.copyOfRange(newBuffer.array(), 0, newSize));

        return result;
    }

这些代码是为了实现ECHO返回而实现的辅助方法,主要是进行Buffer的处理。

3.测试结果

这里写代码片
这里写图片描述
这里写图片描述

使用telnet进行连接测试,实现了ECHO服务器的功能,而且输入exit会关闭该连接。

4.完整代码

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Set;

public class NioServer {

    private Selector selector;
    private ServerSocketChannel serverSocketChannel;
    private int port;

    public NioServer(int port) throws IOException {
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        selector = Selector.open();
        this.port = port;   
    }

    private void selectLoop() throws IOException {
        while(true) {
            // select()方法会阻塞,直到有注册到该选择器的通道有兴趣事件发生
            selector.select();
            // selectedKeys()方法会获得有兴趣事件发生的通道,注册得到的SelectionKey的集合
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while(iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                // 循环判断其中的key
                if (selectionKey.isAcceptable()) {
                    // 如果key处于可接受状态,就进入接收函数
                    acceptClient(selectionKey);
                }else if(selectionKey.isReadable()) {
                    // 如果key处于可读状态,就进入读函数
                    readDate(selectionKey);
                }
            }
            selectionKeys.clear();
        }
    }

    /**
     * 接收连接并将建立的通道注册到选择器
     * 
     * @param selectionKey
     * @throws IOException
     */
    private void acceptClient(SelectionKey selectionKey) throws IOException {
        // 与对端Socket建立连接
        SocketChannel socketChannel = serverSocketChannel.accept();

        if (socketChannel != null) {
            System.err.println("接收到一个连接,对端IP为:"+socketChannel.socket().getInetAddress());
        }
        // 将接收到的SocketChannel注册到Selector,注意此时通道要设置为非阻塞模式,且兴趣操作为OP_READ
        socketChannel.configureBlocking(false);
        socketChannel.register(selector, SelectionKey.OP_READ);
    }

    private void readDate(SelectionKey selectionKey) throws IOException {

        // 每一次都先获取之前绑定在这个key上的buffer
        ByteBuffer oldBuffer = (ByteBuffer)selectionKey.attachment();

        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        ByteBuffer newBuffer = ByteBuffer.allocate(64);

        int read;
        while((read = socketChannel.read(newBuffer))<=0) {
            return;
        }

        newBuffer.flip();
        String line = readLine(newBuffer);
        if (line != null) {

            // 如果这次读到了行结束符,就将原来不含有行结束符的buffer合并位一行
            String sendData = readLine(mergeBuffer(oldBuffer, newBuffer));
            if (readLineContent(sendData).equalsIgnoreCase("exit")) { // 如果这一行的内容是exit就断开连接
                socketChannel.close();
                return;
            }
            // 然后直接发送回到客户端
            ByteBuffer sendBuffer = ByteBuffer.wrap(sendData.getBytes("utf-8"));
            while (sendBuffer.hasRemaining()) {
                socketChannel.write(sendBuffer);
            }
            selectionKey.attach(null);
        }else {
            // 如果这次没读到行结束付,就将这次读的内容和原来的内容合并,并刷新绑定到key对象上
            selectionKey.attach(mergeBuffer(oldBuffer, newBuffer));
        }

    }

    /**
     * 读取ByteBuffer直到一行的末尾
     * 返回这一行的内容,包括换行符
     * 
     * @param buffer
     * @return String 读取到行末的内容,包括换行符 ; null 如果没有换行符
     * @throws UnsupportedEncodingException
     */
    private String readLine(ByteBuffer buffer) throws UnsupportedEncodingException {
        // windows中的换行符表示手段 "\r\n"
        // 基于windows的软件发送的换行符是会是CR和LF
        char CR = '\r';
        char LF = '\n';

        boolean crFound = false;
        int index = 0;
        int len = buffer.limit();
        buffer.rewind();
        while(index < len) {
            byte temp = buffer.get();
            if (temp == CR) {
                crFound = true;
            }
            if (crFound && temp == LF) {
                // Arrays.copyOf(srcArr,length)方法会返回一个 源数组中的长度到length位 的新数组
                return new String(Arrays.copyOf(buffer.array(), index+1),"utf-8");
            }
            index ++;
        }
        return null;
    }

    /**
     * 获取一行的内容,不包括换行符
     * @param buffer
     * @return String 行的内容
     * @throws UnsupportedEncodingException
     */
    private String readLineContent(String line) throws UnsupportedEncodingException {
        return line.substring(0, line.length() - 2);
    }

    /**
     * 对传入的Buffer进行拼接
     * @param oldBuffer
     * @param newBuffer
     * @return ByteBuffer 拼接后的Buffer
     */
    public static ByteBuffer mergeBuffer(ByteBuffer oldBuffer,ByteBuffer newBuffer) {
        // 如果原来的Buffer是null就直接返回
        if (oldBuffer == null) {
            return newBuffer;
        }
        // 如果原来的Buffer的剩余长度可容纳新的buffer则直接拼接
        newBuffer.rewind();
        if (oldBuffer.remaining() > (newBuffer.limit()-newBuffer.position())) {
            return oldBuffer.put(newBuffer);
        }

        // 如果不是以上两种情况就构建新的Buffer进行拼接
        int oldSize = oldBuffer != null?oldBuffer.limit():0;
        int newSize = newBuffer != null?newBuffer.limit():0;
        ByteBuffer result = ByteBuffer.allocate(oldSize+newSize);

        result.put(Arrays.copyOfRange(oldBuffer.array(), 0, oldSize));
        result.put(Arrays.copyOfRange(newBuffer.array(), 0, newSize));

        return result;
    }

    private void startServer() {
        try {
            serverSocketChannel.bind(new InetSocketAddress(port));
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            selectLoop();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }


    public static void main(String[] args) throws UnsupportedEncodingException {
        try {
            new NioServer(12345).startServer();

        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
------------------------ catalina.2025-06-26.log ------------------------ 26-Jun-2025 11:48:04.128 信息 [Thread-4] org.apache.coyote.AbstractProtocol.pause 暂停ProtocolHandler["http-nio-8080"] 26-Jun-2025 11:48:04.184 信息 [Thread-4] org.apache.catalina.core.StandardService.stopInternal 正在停止服务[Catalina] 26-Jun-2025 11:48:04.246 信息 [Thread-4] org.apache.coyote.AbstractProtocol.stop 正在停止ProtocolHandler ["http-nio-8080"] 26-Jun-2025 11:48:04.251 信息 [Thread-4] org.apache.coyote.AbstractProtocol.destroy 正在摧毁协议处理器 ["http-nio-8080"] 26-Jun-2025 11:48:04.268 警告 [Thread-4] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesJdbc Web应用程序 [KuCun2] 注册了JDBC驱动程序 [com.mysql.cj.jdbc.Driver],但在Web应用程序停止时无法注销它。 为防止内存泄漏,JDBC驱动程序已被强制取消注册。 26-Jun-2025 11:48:04.276 警告 [Thread-4] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads Web应用程序[KuCun2]似乎启动了一个名为[mysql-cj-abandoned-connection-cleanup]的线程,但未能停止它。这很可能会造成内存泄漏。线程的堆栈跟踪:[ java.base@11/java.lang.Object.wait(Native Method) java.base@11/java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:155) com.mysql.cj.jdbc.AbandonedConnectionCleanupThread.run(AbandonedConnectionCleanupThread.java:91) java.base@11/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) java.base@11/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) java.base@11/java.lang.Thread.run(Thread.java:834)] 26-Jun-2025 11:48:55.666 警告 [main] org.apache.catalina.core.AprLifecycleListener.init 基于APR的本地库加载失败.错误报告为[D:\apache-tomcat-9.0.37\bin\tcnative-1.dll: Can't load IA 32-bit .dll on a AMD 64-bit platform] java.lang.UnsatisfiedLinkError: D:\apache-tomcat-9.0.37\bin\tcnative-1.dll: Can't load IA 32-bit .dll on a AMD 64-bit platform at java.base/java.lang.ClassLoader$NativeLibrary.load0(Native Method) at java.base/java.lang.ClassLoader$NativeLibrary.load(ClassLoader.java:2430) at java.base/java.lang.ClassLoader$NativeLibrary.loadLibrary(ClassLoader.java:2487) at java.base/java.lang.ClassLoader.loadLibrary0(ClassLoader.java:2684) at java.base/java.lang.ClassLoader.loadLibrary(ClassLoader.java:2617) at java.base/java.lang.Runtime.load0(Runtime.java:767) at java.base/java.lang.System.load(System.java:1831) at org.apache.tomcat.jni.Library.<init>(Library.java:42) at org.apache.tomcat.jni.Library.initialize(Library.java:206) at org.apache.catalina.core.AprLifecycleListener.init(AprLifecycleListener.java:198) at org.apache.catalina.core.AprLifecycleListener.isAprAvailable(AprLifecycleListener.java:107) at org.apache.catalina.connector.Connector.<init>(Connector.java:83) at org.apache.catalina.startup.ConnectorCreateRule.begin(ConnectorCreateRule.java:67) at org.apache.tomcat.util.digester.Digester.startElement(Digester.java:1259) at java.xml/com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.startElement(AbstractSAXParser.java:510) at java.xml/com.sun.org.apache.xerces.internal.parsers.AbstractXMLDocumentParser.emptyElement(AbstractXMLDocumentParser.java:183) at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanStartElement(XMLDocumentFragmentScannerImpl.java:1377) at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:2708) at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:605) at java.xml/com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:534) at java.xml/com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:888) at java.xml/com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:824) at java.xml/com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:141) at java.xml/com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1216) at java.xml/com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:635) at org.apache.tomcat.util.digester.Digester.parse(Digester.java:1517) at org.apache.catalina.startup.Catalina.parseServerXml(Catalina.java:584) at org.apache.catalina.startup.Catalina.load(Catalina.java:675) at org.apache.catalina.startup.Catalina.load(Catalina.java:712) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:566) at org.apache.catalina.startup.Bootstrap.load(Bootstrap.java:302) at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:472) 26-Jun-2025 11:48:55.837 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log Server.服务器版本: Apache Tomcat/9.0.37 26-Jun-2025 11:48:55.839 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log 服务器构建: Jun 30 2020 20:09:49 UTC 26-Jun-2025 11:48:55.841 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log 服务器版本号(: 9.0.37.0 26-Jun-2025 11:48:55.844 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log 操作系统名称: Windows 7 26-Jun-2025 11:48:55.846 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log OS.版本: 6.1 26-Jun-2025 11:48:55.847 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log 架构: amd64 26-Jun-2025 11:48:55.850 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log Java 环境变量: D:\jdk11_jb51 26-Jun-2025 11:48:55.852 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log Java虚拟机版本: 11+28 26-Jun-2025 11:48:55.854 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log JVM.供应商: Oracle Corporation 26-Jun-2025 11:48:55.856 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_BASE: D:\apache-tomcat-9.0.37 26-Jun-2025 11:48:55.858 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log CATALINA_HOME: D:\apache-tomcat-9.0.37 26-Jun-2025 11:48:55.861 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log 命令行参数:--add-opens=java.base/java.lang=ALL-UNNAMED 26-Jun-2025 11:48:55.864 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log 命令行参数:--add-opens=java.base/java.io=ALL-UNNAMED 26-Jun-2025 11:48:55.867 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log 命令行参数:--add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED 26-Jun-2025 11:48:55.870 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log 命令行参数:-Djava.util.logging.config.file=D:\apache-tomcat-9.0.37\conf\logging.properties 26-Jun-2025 11:48:55.872 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log 命令行参数:-Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager 26-Jun-2025 11:48:55.874 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log 命令行参数:-Djdk.tls.ephemeralDHKeySize=2048 26-Jun-2025 11:48:55.876 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log 命令行参数:-Djava.protocol.handler.pkgs=org.apache.catalina.webresources 26-Jun-2025 11:48:55.879 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log 命令行参数:-Dignore.endorsed.dirs= 26-Jun-2025 11:48:55.881 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log 命令行参数:-Dcatalina.base=D:\apache-tomcat-9.0.37 26-Jun-2025 11:48:55.883 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log 命令行参数:-Dcatalina.home=D:\apache-tomcat-9.0.37 26-Jun-2025 11:48:55.885 信息 [main] org.apache.catalina.startup.VersionLoggerListener.log 命令行参数:-Djava.io.tmpdir=D:\apache-tomcat-9.0.37\temp 26-Jun-2025 11:48:56.667 信息 [main] org.apache.coyote.AbstractProtocol.init 初始化协议处理器 ["http-nio-8080"] 26-Jun-2025 11:48:56.844 信息 [main] org.apache.catalina.startup.Catalina.load 服务器在[1446]毫秒内初始化 26-Jun-2025 11:48:56.971 信息 [main] org.apache.catalina.core.StandardService.startInternal 正在启动服务[Catalina] 26-Jun-2025 11:48:56.973 信息 [main] org.apache.catalina.core.StandardEngine.startInternal 正在启动 Servlet 引擎:[Apache Tomcat/9.0.37] 26-Jun-2025 11:48:57.006 信息 [main] org.apache.catalina.startup.HostConfig.deployWAR 正在部署web应用程序存档文件[D:\apache-tomcat-9.0.37\webapps\KuCun2.war] 26-Jun-2025 11:49:01.871 信息 [main] org.apache.jasper.servlet.TldScanner.scanJars 至少有一个JAR被扫描用于TLD但尚未包含TLD。 为此记录器启用调试日志记录,以获取已扫描但未在其中找到TLD的完整JAR列表。 在扫描期间跳过不需要的JAR可以缩短启动时间和JSP编译时间。 26-Jun-2025 11:49:18.187 信息 [main] org.apache.catalina.startup.HostConfig.deployWAR web应用程序存档文件[D:\apache-tomcat-9.0.37\webapps\KuCun2.war]的部署已在[21,180]ms内完成 26-Jun-2025 11:49:18.190 信息 [main] org.apache.catalina.startup.HostConfig.deployDirectory 把web 应用程序部署到目录 [D:\apache-tomcat-9.0.37\webapps\docs] 26-Jun-2025 11:49:18.245 信息 [main] org.apache.catalina.startup.HostConfig.deployDirectory Web应用程序目录[D:\apache-tomcat-9.0.37\webapps\docs]的部署已在[56]毫秒内完成 26-Jun-2025 11:49:18.247 信息 [main] org.apache.catalina.startup.HostConfig.deployDirectory 把web 应用程序部署到目录 [D:\apache-tomcat-9.0.37\webapps\examples] 26-Jun-2025 11:49:18.654 信息 [main] org.apache.catalina.startup.HostConfig.deployDirectory Web应用程序目录[D:\apache-tomcat-9.0.37\webapps\examples]的部署已在[407]毫秒内完成 26-Jun-2025 11:49:18.658 信息 [main] org.apache.catalina.startup.HostConfig.deployDirectory 把web 应用程序部署到目录 [D:\apache-tomcat-9.0.37\webapps\host-manager] 26-Jun-2025 11:49:18.703 信息 [main] org.apache.catalina.startup.HostConfig.deployDirectory Web应用程序目录[D:\apache-tomcat-9.0.37\webapps\host-manager]的部署已在[45]毫秒内完成 26-Jun-2025 11:49:18.709 信息 [main] org.apache.catalina.startup.HostConfig.deployDirectory 把web 应用程序部署到目录 [D:\apache-tomcat-9.0.37\webapps\manager] 26-Jun-2025 11:49:18.758 信息 [main] org.apache.catalina.startup.HostConfig.deployDirectory Web应用程序目录[D:\apache-tomcat-9.0.37\webapps\manager]的部署已在[50]毫秒内完成 26-Jun-2025 11:49:18.764 信息 [main] org.apache.catalina.startup.HostConfig.deployDirectory 把web 应用程序部署到目录 [D:\apache-tomcat-9.0.37\webapps\ROOT] 26-Jun-2025 11:49:18.810 信息 [main] org.apache.catalina.startup.HostConfig.deployDirectory Web应用程序目录[D:\apache-tomcat-9.0.37\webapps\ROOT]的部署已在[47]毫秒内完成 26-Jun-2025 11:49:18.820 信息 [main] org.apache.coyote.AbstractProtocol.start 开始协议处理句柄["http-nio-8080"] 26-Jun-2025 11:49:18.845 信息 [main] org.apache.catalina.startup.Catalina.start [22000]毫秒后服务器启动 26-Jun-2025 11:49:56.871 信息 [Thread-4] org.apache.coyote.AbstractProtocol.pause 暂停ProtocolHandler["http-nio-8080"] 26-Jun-2025 11:49:56.912 信息 [Thread-4] org.apache.catalina.core.StandardService.stopInternal 正在停止服务[Catalina] 26-Jun-2025 11:49:57.187 警告 [Thread-4] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads Web应用程序[KuCun2]似乎启动了一个名为[mysql-cj-abandoned-connection-cleanup]的线程,但未能停止它。这很可能会造成内存泄漏。线程的堆栈跟踪:[ java.base@11/java.lang.Object.wait(Native Method) java.base@11/java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:155) com.mysql.cj.jdbc.AbandonedConnectionCleanupThread.run(AbandonedConnectionCleanupThread.java:91) java.base@11/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) java.base@11/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) java.base@11/java.lang.Thread.run(Thread.java:834)] 26-Jun-2025 11:49:57.214 信息 [Thread-4] org.apache.coyote.AbstractProtocol.stop 正在停止ProtocolHandler ["http-nio-8080"] 26-Jun-2025 11:49:57.223 信息 [Thread-4] org.apache.coyote.AbstractProtocol.destroy 正在摧毁协议处理器 ["http-nio-8080"] ------------------------ host-manager.2025-06-26.log ------------------------ ------------------------ localhost.2025-06-26.log ------------------------ 26-Jun-2025 11:48:04.218 信息 [Thread-4] org.apache.catalina.core.ApplicationContext.log SessionListener: contextDestroyed() 26-Jun-2025 11:48:04.218 信息 [Thread-4] org.apache.catalina.core.ApplicationContext.log ContextListener: contextDestroyed() 26-Jun-2025 11:49:02.103 信息 [main] org.apache.catalina.core.ApplicationContext.log 2 Spring WebApplicationInitializers detected on classpath 26-Jun-2025 11:49:07.916 信息 [main] org.apache.catalina.core.ApplicationContext.log Initializing Spring embedded WebApplicationContext 26-Jun-2025 11:49:18.643 信息 [main] org.apache.catalina.core.ApplicationContext.log ContextListener: contextInitialized() 26-Jun-2025 11:49:18.644 信息 [main] org.apache.catalina.core.ApplicationContext.log SessionListener: contextInitialized() 26-Jun-2025 11:49:18.646 信息 [main] org.apache.catalina.core.ApplicationContext.log ContextListener: attributeAdded('StockTicker', 'async.Stockticker@13bf8a36') 26-Jun-2025 11:49:56.945 信息 [Thread-4] org.apache.catalina.core.ApplicationContext.log SessionListener: contextDestroyed() 26-Jun-2025 11:49:56.945 信息 [Thread-4] org.apache.catalina.core.ApplicationContext.log ContextListener: contextDestroyed() 26-Jun-2025 11:49:56.970 信息 [Thread-4] org.apache.catalina.core.ApplicationContext.log Closing Spring root WebApplicationContext ------------------------ localhost_access_log.2025-06-26.txt ------------------------ ------------------------ manager.2025-06-26.log ------------------------
最新发布
06-27
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值