`
IXHONG
  • 浏览: 451527 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

模拟tomcat连接器

阅读更多

Tomcat中的连接器是一个独立的模块,可以被插入到servlet容器中,而且还有很多连接器可以使用。例如Coyote,mod_jk,mod_jk2,mod_webapp等。Tomcat中使用的连接器必须满足以下条件: 

 

1.实现org.apache.catalina.Connector接口

 

2.负责创建实现了org.apache.catalina.Request接口的request对象

 

3.负责创建实现了org.apache.catalina.Response接口的response对象

 

Tomcat的连接器等待引入的HTTP请求,创建request对象和response对象,然后调用org.apache.catalina.Container接口的invoke()方法,将request对象和response对象传给servlet容器。

 

package com.whatsmars.tomcat.connector;

/**
 * Created by shenhongxi on 16/4/11.
 */
public final class Bootstrap {

    public static void main(String[] args) {
        HttpConnector connector = new HttpConnector();
        Container container = new SimpleContainer();
        connector.setContainer(container);
        connector.setBufferSize(2048);
        connector.start();
    }
}

 

package com.whatsmars.tomcat.connector;

import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * Created by shenhongxi on 16/4/11.
 */
public class HttpConnector implements Runnable {

    private Container container;

    boolean stopped;

    private String scheme = "http";

    private int bufferSize;

    public void run() {
        ServerSocket serverSocket = null;
        int port = 8080;
        try {
            serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1"));
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(1);
        }
        while (!stopped) {
            // Accept the next incoming connection from the server socket
            Socket socket = null;
            try {
                socket = serverSocket.accept();
            } catch (Exception e) {
                continue;
            }
            // Hand this socket off to an HttpProcessor
            HttpProcessor processor = new HttpProcessor(this);
            processor.process(socket);
        }
    }

    public void start() {
        new Thread(this).start();
    }

    public String getScheme() {
        return scheme;
    }

    public int getBufferSize() {
        return bufferSize;
    }

    public void setBufferSize(int bufferSize) {
        this.bufferSize = bufferSize;
    }

    public Container getContainer() {
        return container;
    }

    public void setContainer(Container container) {
        this.container = container;
    }
}

 

package com.whatsmars.tomcat.connector;

import org.apache.catalina.util.Enumerator;
import org.apache.catalina.util.ParameterMap;
import org.apache.catalina.util.RequestUtil;

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.Principal;
import java.util.*;

/**
 * Created by shenhongxi on 16/4/11.
 */
public class HttpRequest implements HttpServletRequest {

    private String requestURI;
    private int contentLength;
    private String contentType;
    private String queryString;
    private String method;
    private String protocol;

    protected Map headers = new HashMap();

    protected SocketInputStream input;

    /**
     * The parsed parameters for this request.  This is populated only if
     * parameter information is requested via one of the
     * <code>getParameter()</code> family of method calls.  The key is the
     * parameter name, while the value is a String array of values for this
     * parameter.
     * <p>
     * <strong>IMPLEMENTATION NOTE</strong> - Once the parameters for a
     * particular request are parsed and stored here, they are not modified.
     * Therefore, application level access to the parameters need not be
     * synchronized.
     */
    protected ParameterMap parameters = null; // extends LinkedHashMap has a boolean var 'locked'

    /**
     * Have the parameters for this request been parsed yet?
     */
    protected boolean parsed = false;

    public HttpRequest(SocketInputStream input) {
        this.input = input;
    }

    public void addHeader(String name, String value) {
        name = name.toLowerCase();
        synchronized (headers) {
            ArrayList values = (ArrayList) headers.get(name);
            if (values == null) {
                values = new ArrayList();
                headers.put(name, values);
            }
            values.add(value);
        }
    }

    public String getParameter(String name) {
        parseParameters();
        String values[] = (String[]) parameters.get(name);
        if (values != null)
            return (values[0]);
        else
            return (null);
    }

    public Map getParameterMap() {
        parseParameters();
        return (this.parameters);
    }

    public Enumeration getParameterNames() {
        parseParameters();
        return (new Enumerator(parameters.keySet()));
    }

    public String[] getParameterValues(String name) {
        parseParameters();
        String values[] = (String[]) parameters.get(name);
        if (values != null)
            return (values);
        else
            return null;
    }

    /**
     * Parse the parameters of this request, if it has not already occurred.
     * If parameters are present in both the query string and the request
     * content, they are merged.
     */
    protected void parseParameters() {
        if (parsed) return;
        ParameterMap results = parameters;
        if (results == null)
            results = new ParameterMap();
        results.setLocked(false);
        String encoding = getCharacterEncoding();
        if (encoding == null)
            encoding = "ISO-8859-1";

        // Parse any parameters specified in the query string
        String queryString = getQueryString();
        try {
            RequestUtil.parseParameters(results, queryString, encoding);
        } catch (Exception e) {
            ;
        }

        // Parse any parameters specified in the input stream
        String contentType = getContentType();
        if (contentType == null)
            contentType = "";
        int semicolon = contentType.indexOf(';');
        if (semicolon >= 0) {
            contentType = contentType.substring(0, semicolon).trim();
        } else {
            contentType = contentType.trim();
        }
        if ("POST".equals(getMethod()) && (getContentLength() > 0)
                && "application/x-www-form-urlencoded".equals(contentType)) {
            try {
                int max = getContentLength();
                int len = 0;
                byte buf[] = new byte[getContentLength()];
                ServletInputStream is = getInputStream();
                while (len < max) {
                    int next = is.read(buf, len, max - len);
                    if (next < 0 ) {
                        break;
                    }
                    len += next;
                }
                is.close();
                if (len < max) {
                    throw new RuntimeException("Content length mismatch");
                }
                RequestUtil.parseParameters(results, buf, encoding);
            } catch (UnsupportedEncodingException ue) {
                ;
            } catch (IOException e) {
                throw new RuntimeException("Content read fail");
            }
        }

        // Store the final results
        results.setLocked(true);
        parsed = true;
        parameters = results;
    }

    public void setRequestURI(String requestURI) {
        this.requestURI = requestURI;
    }

    public void setContentLength(int contentLength) {
        this.contentLength = contentLength;
    }

    public void setContentType(String contentType) {
        this.contentType = contentType;
    }

    public void setQueryString(String queryString) {
        this.queryString = queryString;
    }

    public void setMethod(String method) {
        this.method = method;
    }

    public void setProtocol(String protocol) {
        this.protocol = protocol;
    }

    // ... other methods
}

 

package com.whatsmars.tomcat.connector;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.Locale;

/**
 * Created by shenhongxi on 16/4/11.
 */
public class HttpResponse implements HttpServletResponse {
    OutputStream output;
    HttpRequest request;
    PrintWriter writer;

    public HttpResponse(OutputStream output) {
        this.output = output;
    }

    public void setRequest(HttpRequest request) {
        this.request = request;
    }

    /**
     * call this method to send headers and response to the output
     */
    public void finishResponse() {
        // sendHeaders();
        // Flush and close the appropriate output mechanism
        if (writer != null) {
            writer.flush();
            writer.close();
        }
    }

    public void addCookie(Cookie cookie) {

    }

    public boolean containsHeader(String name) {
        return false;
    }

    public String encodeURL(String url) {
        return null;
    }

    public String encodeRedirectURL(String url) {
        return null;
    }

    public String encodeUrl(String url) {
        return null;
    }

    public String encodeRedirectUrl(String url) {
        return null;
    }

    public void sendError(int sc, String msg) throws IOException {

    }

    public void sendError(int sc) throws IOException {

    }

    public void sendRedirect(String location) throws IOException {

    }

    // ... other methods
}

 

package com.whatsmars.tomcat.connector;

import com.whatsmars.tomcat.servlet.StaticResourceProcessor;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

/**
 * Created by shenhongxi on 16/4/11.
 */
public class HttpProcessor {

    private HttpConnector connector;
    private HttpRequest request;
    private HttpResponse response;
    private HttpRequestLine requestLine = new HttpRequestLine();

    public HttpProcessor(HttpConnector connector) {
        this.connector = connector;
    }

    public void process(Socket socket) {
        SocketInputStream input = null;
        OutputStream output = null;
        try {
            input = new SocketInputStream(socket.getInputStream(), connector.getBufferSize()); // 1.读取套接字的输入流
            output = socket.getOutputStream();

            // create HttpRequest object and parse
            request = new HttpRequest(input);
            response = new HttpResponse(output);
            response.setRequest(request);
            response.setHeader("Server", "Mars Servlet Container");
            
            parseRequest(input, output); // 解析请求行,即HTTP请求的第一行内容
            parseHeaders(input); // 解析请求头

            if (request.getRequestURI().startsWith("/servlet/")) {
                connector.getContainer().invoke((HttpServletRequest) request, (HttpServletResponse) response);
            } else {
                StaticResourceProcessor processor = new StaticResourceProcessor();
                //processor.process(request, response);
            }

            socket.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void parseHeaders(SocketInputStream input) throws IOException, ServletException{
        while (true) { // 一行一行解析完header
            HttpHeader header = new HttpHeader();
            // Read the next header
            input.readHeader(header);
            if (header.nameEnd == 0) {
                if (header.valueEnd == 0) {
                    return;
                } else {
                    throw new ServletException("httpProcessor parseHeaders colon");
                }
            }
            String name = new String(header.name, 0, header.nameEnd);
            String value = new String(header.value, 0, header.valueEnd);
            request.addHeader(name, value);
            // do something for some headers, ignore others.
            if (name.equals("cookie")) {
                // ...
                // request.addCookie(cookies[i]);
            } else if (name.equals("content-length")) {
                int n = -1;
                try {
                    n = Integer.parseInt(value);
                } catch (Exception e) {
                    throw new ServletException("httpProcessor.parseHeaders.contentLength");
                }
                request.setContentLength(n);
            } else if (name.equals("content-type")) {
                request.setContentType(value);
            }
        }
    }

    private void parseRequest(SocketInputStream input, OutputStream output) throws IOException, ServletException {
        input.readRequestLine(requestLine);

        String method = new String(requestLine.method, 0, requestLine.methodEnd);
        String uri = null;
        String protocol = new String(requestLine.protocol, 0, requestLine.protocolEnd);

        // Validate the incoming request line
        if (method.length() < 1) {
            throw new ServletException("Missing HTTP request method");
        } else if (requestLine.uriEnd < 1) {
            throw new ServletException("Missing HTTP request URI");
        }
        // Parse any query parameters out of the request URI
        int question = requestLine.indexOf("?");
        if (question >= 0) {
            request.setQueryString(new String(requestLine.uri, question + 1,
                    requestLine.uriEnd - question - 1));
            uri = new String(requestLine.uri, 0, question);
        } else {
            request.setQueryString(null);
            uri = new String(requestLine.uri, 0, requestLine.uriEnd);
        }
        String normalizedUri = normalize(uri);

        ((HttpRequest) request).setMethod(method);
        request.setProtocol(protocol);
        if (normalizedUri != null) {
            ((HttpRequest) request).setRequestURI(normalizedUri);
        }
        else {
            ((HttpRequest) request).setRequestURI(uri);
        }

        if (normalizedUri == null) {
            throw new ServletException("Invalid URI: " + uri + "'");
        }
    }

    // Return a context-relative path, beginning with a "/"
    protected String normalize(String path) {
        if (path == null) return null;
        String normalized = path;
        // ...
        return path;
    }
}

 

package com.whatsmars.tomcat.connector;

/**
 * Created by shenhongxi on 16/4/13.
 */
public final class HttpHeader {

    public static final int INITIAL_NAME_SIZE = 32;
    public static final int INITIAL_VALUE_SIZE = 64;
    public static final int MAX_NAME_SIZE = 128;
    public static final int MAX_VALUE_SIZE = 4096;

    public char[] name;
    public int nameEnd;
    public char[] value;
    public int valueEnd;
    protected int hashCode = 0;

    public HttpHeader() {
        this(new char[INITIAL_NAME_SIZE], 0, new char[INITIAL_VALUE_SIZE], 0);
    }


    public HttpHeader(char[] name, int nameEnd, char[] value, int valueEnd) {
        this.name = name;
        this.nameEnd = nameEnd;
        this.value = value;
        this.valueEnd = valueEnd;
    }


    public HttpHeader(String name, String value) {
        this.name = name.toLowerCase().toCharArray();
        this.nameEnd = name.length();
        this.value = value.toCharArray();
        this.valueEnd = value.length();
    }

    /**
     * Release all object references, and initialize instance variables, in
     * preparation for reuse of this object.
     */
    public void recycle() {
        nameEnd = 0;
        valueEnd = 0;
        hashCode = 0;
    }

}

 

package com.whatsmars.tomcat.connector;

/**
 * Created by shenhongxi on 16/4/13.
 */
public final class HttpRequestLine {

    public static final int INITIAL_METHOD_SIZE = 8;
    public static final int INITIAL_URI_SIZE = 64;
    public static final int INITIAL_PROTOCOL_SIZE = 8;
    public static final int MAX_METHOD_SIZE = 1024;
    public static final int MAX_URI_SIZE = 32768;
    public static final int MAX_PROTOCOL_SIZE = 1024;

    public char[] method;
    public int methodEnd;
    public char[] uri;
    public int uriEnd;
    public char[] protocol;
    public int protocolEnd;

    public HttpRequestLine() {
        this(new char[INITIAL_METHOD_SIZE], 0, new char[INITIAL_URI_SIZE], 0,
                new char[INITIAL_PROTOCOL_SIZE], 0);
    }


    public HttpRequestLine(char[] method, int methodEnd,
                           char[] uri, int uriEnd,
                           char[] protocol, int protocolEnd) {
        this.method = method;
        this.methodEnd = methodEnd;
        this.uri = uri;
        this.uriEnd = uriEnd;
        this.protocol = protocol;
        this.protocolEnd = protocolEnd;
    }

    public int indexOf(String str) {
        // ...
        return -1;
    }

    /**
     * Release all object references, and initialize instance variables, in
     * preparation for reuse of this object.
     */
    public void recycle() {
        methodEnd = 0;
        uriEnd = 0;
        protocolEnd = 0;
    }

}

 

package com.whatsmars.tomcat.connector;

import java.io.IOException;
import java.io.InputStream;

/**
 * Created by shenhongxi on 16/4/11.
 * Extends InputStream to be more efficient reading lines during HTTP header processing.
 */
public class SocketInputStream extends InputStream {

    /**
     * Underlying input stream.
     */
    private InputStream input;

    /**
     * Internal buffer.
     */
    protected byte[] buf;


    /**
     * Last valid byte.
     */
    protected int count;


    /**
     * Position in the buffer.
     */
    protected int pos;

    public SocketInputStream(InputStream input, int bufferSize) {
        this.input = input;
        this.buf = new byte[bufferSize];
    }

    // input => buf => HttpRequestLine
    public void readRequestLine(HttpRequestLine requestLine) throws IOException {
        // Recycling check
        if (requestLine.methodEnd != 0)
            requestLine.recycle();

        // Checking for a blank line

        // Reading the method name

        // Reading URI

        // Reading protocol
    }

    // input => buf => HttpHeader
    public void readHeader(HttpHeader header) throws IOException {
        // Recycling check
        if (header.nameEnd != 0)
            header.recycle();

        // Checking for a blank line

        // Reading the header name

        // Reading the header value (which can be spanned over multiple lines)
    }

    @Override
    public int read() throws IOException {
        if (pos >= count) {
            fill();
            if (pos >= count)
                return -1;
        }
        return buf[pos++] & 0xff;
    }

    /**
     * Fill the internal buffer using data from the undelying input stream.
     */
    protected void fill()
            throws IOException {
        pos = 0;
        count = 0;
        int nRead = input.read(buf, 0, buf.length);
        if (nRead > 0) {
            count = nRead;
        }
    }
}

 

package com.whatsmars.tomcat.connector;

import com.whatsmars.tomcat.servlet.Constants;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandler;

/**
 * Created by shenhongxi on 16/4/14.
 */
public class SimpleContainer implements Container {

    public void invoke(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        String servletName = ( (HttpServletRequest) request).getRequestURI();
        servletName = servletName.substring(servletName.lastIndexOf("/") + 1);
        URLClassLoader loader = null;
        try {
            URL[] urls = new URL[1];
            URLStreamHandler streamHandler = null;
            File classPath = new File(Constants.WEB_ROOT);
            String repository = (new URL("file", null, classPath.getCanonicalPath() + File.separator)).toString() ;
            urls[0] = new URL(null, repository, streamHandler);
            loader = new URLClassLoader(urls);
        } catch (IOException e) {
            System.out.println(e.toString() );
        }
        Class myClass = null;
        try {
            myClass = loader.loadClass(servletName);
        }
        catch (ClassNotFoundException e) {
            System.out.println(e.toString());
        }

        Servlet servlet = null;

        try {
            servlet = (Servlet) myClass.newInstance();
            servlet.service((HttpServletRequest) request, (HttpServletResponse) response);
        }
        catch (Exception e) {
            System.out.println(e.toString());
        }
        catch (Throwable e) {
            System.out.println(e.toString());
        }
    }
}

 

package com.whatsmars.tomcat.connector;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * Created by shenhongxi on 16/4/14.
 */
public interface Container {

    public void invoke(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException;
}

 最后,关于HttpProcessor池,连接器线程和处理器线程的等待与唤醒,请参考多线程知识自行脑补。

 

Request和Response的外观类(包装类)参见http://wely.iteye.com/blog/2290575

 

 

 

0
3
分享到:
评论

相关推荐

    模拟tomcat的工作原理

    在文件"模拟Tomcat"中,可能包含了实现上述过程的源代码,包括连接器的实现、HTTP请求的解析、Servlet的调度、线程管理等模块。通过阅读和分析这些代码,我们可以更深入地学习Tomcat的工作原理,这对于理解和开发Web...

    模拟Tomcat运行机制

    《模拟Tomcat运行机制》是一本深入解析Tomcat内核的书籍,主要针对Tomcat4.1.12和5.0.18版本。Tomcat作为一款流行的、开源的Servlet容器,其复杂性在于由多个组件组成。本书旨在帮助读者理解Tomcat的工作原理,无论...

    模拟TOMCAT工作的全部过程

    【TOMCAT工作原理详解】 ...通过模拟TOMCAT工作过程,可以更深入地理解这些概念,为实际开发打下坚实基础。在`mvcdemo`这样的实践中,你可以亲自体验和学习如何在TOMCAT上部署和运行一个简单的MVC应用。

    模拟tomcat、servlet服务器

    在IT行业中,构建一个模拟Tomcat或Servlet服务器是一项高级...通过以上知识点的学习和实践,你将能够构建一个基本的模拟Tomcat和Servlet服务器,这将帮助你深入理解Web服务器的工作原理,提升你的Java网络编程技能。

    模拟tomcat工作原理

    【标题】:“模拟Tomcat工作原理” 【描述】:这篇博文详细探讨了Tomcat服务器的工作流程,通过解析和模拟其内部机制,帮助读者深入理解Web应用的部署与运行过程。虽然描述部分没有提供具体信息,但我们可以从标签...

    java模拟tomcat的web服务器源码

    Java模拟Tomcat Web服务器源码解析 在Java世界中,Tomcat是一款广泛应用的开源Web服务器和Servlet容器。它遵循Java Servlet和JavaServer Pages(JSP)规范,为开发者提供了轻量级、高效的运行环境。本篇文章将深入...

    简单的tomcat实现1

    在手写Tomcat时,我们可以模拟一个简单的基于 BIO(阻塞I/O)的连接器。这涉及到监听特定端口,接受连接请求,读取HTTP请求头和正文,然后将其传递给容器进行处理。 2. 容器(Container):Tomcat的容器分为多个...

    tomcat-redis依赖jar包

    总结来说,"tomcat-redis依赖jar包"涉及到的是将Tomcat应用服务器与Redis缓存系统整合的过程,包括连接器选择、配置、缓存策略、性能优化、安全措施以及监控与故障排查等多个方面。正确地集成和使用这些jar包,能够...

    tomcat connectors

    如果你需要深入理解Tomcat连接器的工作原理,或者想要对其进行定制,你可以编译这个源代码。这通常涉及到下载源码,配置构建环境(如Apache Maven或Ant),然后执行构建命令来生成可部署的二进制文件。 了解Tomcat ...

    优化提高tomcat性能.Tomcat参数调优

    Tomcat默认使用两种连接器:BIO(Blocking I/O)和NIO(Non-blocking I/O)。在`nginx+tomcat8开始默认NIO方式.png`中,我们可以看到Tomcat 8及以上版本默认采用NIO模式,这种模式相比BIO更能处理大量并发连接,因为...

    Tomcat6/Tomcat7/httpwatch

    - 提升了性能,增强了线程池和连接器的管理。 **HttpWatch** HttpWatch是一个集成在浏览器中的工具,用于分析网页加载速度和性能。它提供了详细的HTTP请求和响应信息,帮助开发者识别和优化网页的瓶颈。以下是一些...

    Tomcat培训学习资料

    优化Tomcat主要是通过调整相关参数和配置,以提高系统的性能和稳定性,例如修改JVM参数、调整连接器的设置、配置缓存大小、启用异步处理等。 Tomcat的安装和配置涉及多个步骤,包括下载合适的Tomcat版本、设置环境...

    Tomcat源码分析

    1. **Coyote**: 这是Tomcat的网络连接器,负责接收和发送HTTP请求/响应。 2. **AprLifecycleListener**: 使用Apache Portable Runtime(APR)库提高性能。 3. **StandardContext**: 负责管理Web应用,加载和卸载应用...

    Tomcat WEB服务器实战

    #### 六、Tomcat连接器选择 - **选择合适的连接器**: - **BIO (Blocking I/O)**: 传统的同步阻塞I/O模型。 - **NIO (Non-blocking I/O)**: 支持非阻塞I/O模型,适用于高并发场景。 - **APR (Apache Portable ...

    2024年Tomcat安装和配置(超详细)

    内容包括Tomcat的基础概念、Java JDK的安装、Tomcat的下载、环境变量的配置、Tomcat的启动和验证、Web应用程序的部署、Tomcat的配置文件编辑、虚拟主机的配置、连接器的设置、会话管理、安全性配置、性能监控以及...

    tomcat优化调优文档

    - **监控界面**:部署完成后,通过访问LambdaProbe提供的Web界面,可以直观地查看到详细的Tomcat性能数据以及连接器的状态等信息。 使用LambdaProbe可以更细致地了解Tomcat各方面的运行状态,有助于定位潜在的...

    apache-tomcat-8.0.46.zip

    这包括设置防火墙规则、配置正确的连接器设置、更新到无漏洞的Tomcat版本等。 安装和运行Apache Tomcat 8.0.46在Linux环境中的步骤大致如下: 1. 解压下载的zip文件:`unzip apache-tomcat-8.0.46.zip` 2. 设置...

    Tomcat7优化.docx

    5. **连接器(Connector)** Connector是Tomcat接收请求的入口,每个Connector都有自己的监听端口。配置HTTP和AJP Connector,关注`maxThreads`、`minSpareThreads`等属性,以适应不同类型的请求负载。 **JVM参数...

    Nginx+Tomcat9搭建负载均衡实例,解压即可运行

    本实例将详细讲解如何使用Nginx作为反向代理和负载均衡器,结合Tomcat9作为Java应用服务器来搭建一个负载均衡系统。Nginx以其高性能、轻量级的特性,常被用作前端服务器,负责将用户请求分发到后端多个Tomcat实例上...

Global site tag (gtag.js) - Google Analytics