Tomcat源码:SocketProcessor、ConnectionHandler与Http11Processor

news2024/12/24 21:43:31

 前文:

《Tomcat源码:启动类Bootstrap与Catalina的加载》

《Tomcat源码:容器的生命周期管理与事件监听》

《Tomcat源码:StandardServer与StandardService》

《Tomcat源码:Container接口》

《Tomcat源码:StandardEngine、StandardHost、StandardContext、StandardWrapper》

 《Tomcat源码:Pipeline与Valve》

《Tomcat源码:连接器与Executor、Connector》

《Tomcat源码:ProtocolHandler与Endpoint》

《Tomcat源码:Acceptor与Poller、PollerEvent》

前言

        前文中我们介绍了Acceptor与Poller,其中Acceptor负责监听socket连接,并将请求转交到Poller中调用processSocket方法处理。

        结合我们之前介绍连接器时的讲解,EndPoint 接收到 Socket 连接后,生成一个 SocketProcessor 任务提交到线程池去处理,SocketProcessor 的 Run 方法会调用 Processor 组件去解析应用层协议,这一操作的起点就是processSocket方法,下面我们就从该方法开始讲起。

目录

前言

一、SocketProcessor

        1、processSocket

        2、createSocketProcessor

        3、doRun

        3.1、isHandshakeComplete        

        3.2、ConnectionHandler        

         3.3、registerReadInterest、registerWriteInterest        

二、ConnectionHandler

        1、构造方法

        2、process

        2.1、removeWaitingProcessor、addWaitingProcessor

        2.2、longPoll

        2.3、release

三、Http11Processor

        1、Http11Processor的创建

        1.1、createProcessor

        1.2、构造方法

        2、process

        3、service


一、SocketProcessor

        1、processSocket

        首先尝试从processorCache(AbstractEndpoint中的成员变量,前文中有过介绍,类型为SynchronizedStack<SocketProcessorBase<S>>)中获取现有的SocketProcessorBase对象,如果没有则调用createSocketProcessor方法创建,有的话则调用rest方法重置socket包装类与监听事件。

        在SocketProcessor创建完了后,根据dispatch是否为true与 executor 是否为空来判断是否将当前连接的处理放入线程池中等待处理还是直接进行方法调用处理。

public abstract class AbstractEndpoint<S,U> {
    public boolean processSocket(SocketWrapperBase<S> socketWrapper,
            SocketEvent event, boolean dispatch) {
        try {
            if (socketWrapper == null) {
                return false;
            }
            SocketProcessorBase<S> sc = null;
            if (processorCache != null) {
                sc = processorCache.pop();
            }
            if (sc == null) {
                sc = createSocketProcessor(socketWrapper, event);
            } else {
                sc.reset(socketWrapper, event);
            }
            Executor executor = getExecutor();
            if (dispatch && executor != null) {
                executor.execute(sc);
            } else {
                sc.run();
            }
        } catch (RejectedExecutionException ree) {
            getLog().warn(sm.getString("endpoint.executor.fail", socketWrapper) , ree);
            return false;
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            getLog().error(sm.getString("endpoint.process.fail"), t);
            return false;
        }
        return true;
    }
}

        2、createSocketProcessor

        可以看到这个方法直接new了一个SocketProcessor对象出来

public class NioEndpoint extends AbstractJsseEndpoint<NioChannel,SocketChannel> {
    protected SocketProcessorBase<NioChannel> createSocketProcessor(
        SocketWrapperBase<NioChannel> socketWrapper, SocketEvent event) {
            return new SocketProcessor(socketWrapper, event);
        }
    }
}

         SocketProcessor的构造方法使用了super来调用父类构造方法,而父类SocketProcessorBase的构造方法则是通过rest方法给socketWrapper与event这两个成员变量来赋值。

protected class SocketProcessor extends SocketProcessorBase<NioChannel> {
    public SocketProcessor(SocketWrapperBase<NioChannel> socketWrapper, SocketEvent event) {
         super(socketWrapper, event);
        }
}

public abstract class SocketProcessorBase<S> implements Runnable {
    protected SocketWrapperBase<S> socketWrapper;
    protected SocketEvent event;

    public SocketProcessorBase(SocketWrapperBase<S> socketWrapper, SocketEvent event) {
        reset(socketWrapper, event);
    }

    public void reset(SocketWrapperBase<S> socketWrapper, SocketEvent event) {
        Objects.requireNonNull(event);
        this.socketWrapper = socketWrapper;
        this.event = event;
    }
}

        3、doRun

        run方法的实现在父类SocketProcessorBase中,可以看到这里将处理逻辑交给了doRun来实现

public abstract class SocketProcessorBase<S> implements Runnable {
    public final void run() {
        synchronized (socketWrapper) {
            if (socketWrapper.isClosed()) {
                return;
            }
            doRun();
        }
    }
}

        doRun方法的实现在子类SocketProcessor 中,步骤比较长,但可以简单归结为两步,首先是根据socketWrapper与event的状态给handshake 赋值,然后根据handshake 的值走不同的处理逻辑。

protected class SocketProcessor extends SocketProcessorBase<NioChannel> {
        protected void doRun() {
            Poller poller = NioEndpoint.this.poller;
            if (poller == null) {
                socketWrapper.close();
                return;
            }

            try {
                int handshake = -1;
                try {
                    if (socketWrapper.getSocket().isHandshakeComplete()) {
                        handshake = 0;
                    } else if (event == SocketEvent.STOP || event == SocketEvent.DISCONNECT ||
                            event == SocketEvent.ERROR) {
                        handshake = -1;
                    } else {
                        handshake = socketWrapper.getSocket().handshake(event == SocketEvent.OPEN_READ, event == SocketEvent.OPEN_WRITE);
                        event = SocketEvent.OPEN_READ;
                    }
                } catch (IOException x) {
                    handshake = -1;
                    if (logHandshake.isDebugEnabled()) {
                        logHandshake.debug(sm.getString("endpoint.err.handshake",
                                socketWrapper.getRemoteAddr(), Integer.toString(socketWrapper.getRemotePort())), x);
                    }
                } catch (CancelledKeyException ckx) {
                    handshake = -1;
                }
                if (handshake == 0) {
                    SocketState state = SocketState.OPEN;
                    // Process the request from this socket
                    if (event == null) {
                        state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ);
                    } else {
                        state = getHandler().process(socketWrapper, event);
                    }
                    if (state == SocketState.CLOSED) {
                        poller.cancelledKey(getSelectionKey(), socketWrapper);
                    }
                } else if (handshake == -1 ) {
                    getHandler().process(socketWrapper, SocketEvent.CONNECT_FAIL);
                    poller.cancelledKey(getSelectionKey(), socketWrapper);
                } else if (handshake == SelectionKey.OP_READ){
                    socketWrapper.registerReadInterest();
                } else if (handshake == SelectionKey.OP_WRITE){
                    socketWrapper.registerWriteInterest();
                }
            } catch (CancelledKeyException cx) {
                poller.cancelledKey(getSelectionKey(), socketWrapper);
            } catch (VirtualMachineError vme) {
                ExceptionUtils.handleThrowable(vme);
            } catch (Throwable t) {
                log.error(sm.getString("endpoint.processing.fail"), t);
                poller.cancelledKey(getSelectionKey(), socketWrapper);
            } finally {
                socketWrapper = null;
                event = null;
                //return to cache
                if (running && processorCache != null) {
                    processorCache.push(this);
                }
            }
        }
}

        3.1、isHandshakeComplete        

        isHandshakeComplete分别在NioChannel 与其子类SecureNioChannel中进行了重写,这里我们按标准的请求流程来看下NioChannel中的实现,可以看到这里始终为true。后续调用的handshake方法也一样,在NioChannel中始终为0。

public class NioChannel implements ByteChannel, ScatteringByteChannel, GatheringByteChannel {
    public boolean isHandshakeComplete() {
        return true;
    }

    public int handshake(boolean read, boolean write) throws IOException {
        return 0;
    }
}

        3.2、ConnectionHandler        

        看过了上面NioChannel中的两个方法实现后,可以确定在doRun方法中handshake为0。然后我们看到这里如果event为空则默认当作读事件处理,处理方法为调用Handler的process方法。然后得到一个 SocketState 对象 state,如果 state 的值为SocketState.CLOSED,则执行 close(socket, key) 方法。

      if (handshake == 0) {
            SocketState state = SocketState.OPEN;
            // Process the request from this socket
            if (event == null) {
                state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ);
             } else {
                state = getHandler().process(socketWrapper, event);
             }
             if (state == SocketState.CLOSED) {
                poller.cancelledKey(getSelectionKey(), socketWrapper);
             }
      } else if (handshake == -1 ) {
            getHandler().process(socketWrapper, SocketEvent.CONNECT_FAIL);
            poller.cancelledKey(getSelectionKey(), socketWrapper);
      } else if (handshake == SelectionKey.OP_READ){
            socketWrapper.registerReadInterest();
      } else if (handshake == SelectionKey.OP_WRITE){
            socketWrapper.registerWriteInterest();
       }

        这里的getHandler获取的是AbstractProtocol构造方法中传入的ConnectionHandler,该类我们会在下文介绍。

    public AbstractProtocol(AbstractEndpoint<S, ?> endpoint) {
        this.endpoint = endpoint;
        ConnectionHandler<S> cHandler = new ConnectionHandler<>(this);
        setHandler(cHandler);
        getEndpoint().setHandler(cHandler);
        setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
        setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
    }

         3.3、registerReadInterest、registerWriteInterest        

        如果 handshake 的值是 SelectionKey.OP_READ 或者 SelectionKey.OP_WRITE 的话,就调用 socketWrapper.registerReadInterest() 或者 socketWrapper.registerWriteInterest() 通过poller重新注册感兴趣事件。

public static class NioSocketWrapper extends SocketWrapperBase<NioChannel> {
        public void registerReadInterest() {
            if (log.isDebugEnabled()) {
                log.debug(sm.getString("endpoint.debug.registerRead", this));
            }
            getPoller().add(this, SelectionKey.OP_READ);
        }

        public void registerWriteInterest() {
            if (log.isDebugEnabled()) {
                log.debug(sm.getString("endpoint.debug.registerWrite", this));
            }
            getPoller().add(this, SelectionKey.OP_WRITE);
        }
}

        可以看出,SocketProcessor这里的作用是作为连接池中的一个执行单位来提交poller中传递过来的请求,而在其异步线程的处理步骤中,又会将任务递交给ConnectionHandler。

 

二、ConnectionHandler

        1、构造方法

        ConnectionHandler的构造方法中传入了Http11NioProtocol对象

    protected static class ConnectionHandler<S> implements AbstractEndpoint.Handler<S> {
        private final AbstractProtocol<S> proto;
        public ConnectionHandler(AbstractProtocol<S> proto) {
            this.proto = proto;
        }
    }

        2、process

        process的过程比较复杂,这里单独拎出来我们本次要讲的标准HTTP1.1的流程来看。

        首先是获取processor 对象,如果有的话将其从waitingProcessors中剔除,如果没有再尝试从缓存recycledProcessors中获取,都没有则调用createProcessor创建一个。processor对象有了之后调用其process方法,并返回方法结果state,后续根据该结果进行不同的处理。

protected static class ConnectionHandler<S> implements AbstractEndpoint.Handler<S> {
    public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) {
		// ...
        S socket = wrapper.getSocket();
        Processor processor = (Processor) wrapper.takeCurrentProcessor();
		// ...
		try{
			// ...
            if (processor != null) {
                getProtocol().removeWaitingProcessor(processor);
            } else if (status == SocketEvent.DISCONNECT || status == SocketEvent.ERROR) {
                return SocketState.CLOSED;
            }
			// ...
            if (processor == null) {
                processor = recycledProcessors.pop();
            }
            if (processor == null) {
                processor = getProtocol().createProcessor();
                register(processor);
            }
			// ...
            SocketState state = SocketState.CLOSED;
            do {
                state = processor.process(wrapper, status);
                if (state == SocketState.UPGRADING) {
                        // ...
                }
            } while (state == SocketState.UPGRADING);

            if (state == SocketState.LONG) {
                longPoll(wrapper, processor);
                if (processor.isAsync()) {
                    getProtocol().addWaitingProcessor(processor);
                }
            } else if (state == SocketState.OPEN) {
                release(processor);
                processor = null;
                wrapper.registerReadInterest();
            } else if (state == SocketState.SENDFILE) {
            } else if (state == SocketState.UPGRADED) {
                if (status != SocketEvent.OPEN_WRITE) {
                   longPoll(wrapper, processor);
                   getProtocol().addWaitingProcessor(processor);    
				}
            } else if (state == SocketState.SUSPENDED) {
            } else {
            }

            if (processor != null) {
                wrapper.setCurrentProcessor(processor);
            }
            return state;
        }

        release(processor);
        return SocketState.CLOSED;
    }
}

        2.1、removeWaitingProcessor、addWaitingProcessor

        removeWaitingProcessor、addWaitingProcessor都是操作WaitingProcessor的方法

public abstract class AbstractProtocol<S> implements ProtocolHandler, MBeanRegistration { 
   public void removeWaitingProcessor(Processor processor) {
        waitingProcessors.remove(processor);
    }

    public void addWaitingProcessor(Processor processor) {
        waitingProcessors.add(processor);
    }
}

        2.2、longPoll

        longPoll 判断是否异步,这里默认为false因此会调用 socket.registerReadInterest() 方法,该方法在上篇文章里讲过了,这里就不多赘述。 

protected void longPoll(SocketWrapperBase<?> socket, Processor processor) {
    if (!processor.isAsync()) {
        socket.registerReadInterest();
    }
}

        2.3、release

        从方法名可以推测出该方法用于释放process资源,如果不是升级协议则放入recycledProcessors缓存中供后续请求使用。

        private void release(Processor processor) {
            if (processor != null) {
                processor.recycle();
                if (processor.isUpgrade()) {
                    getProtocol().removeWaitingProcessor(processor);
                } else {
                    recycledProcessors.push(processor);
                    if (getLog().isDebugEnabled()) {
                        getLog().debug("Pushed Processor [" + processor + "]");
                    }
                }
            }
        }

        ConnectionHandler也没有做什么操作,也是简单的讲请求递交给processor 组件来处理。

三、Http11Processor

        1、Http11Processor的创建

        1.1、createProcessor

        上文中ConnectionHandler#process会调用createProcessor创建processor 对象,因为我们走的标准HTTP1.1所以这里的实现类为Http11Processor。在使用构造方法创建了Http11Processor对象后会为其设置相关属性,包括之前创建的Adapter对象等。

public abstract class AbstractHttp11Protocol<S> extends AbstractProtocol<S> {
    protected Processor createProcessor() {
        Http11Processor processor = new Http11Processor(this, getEndpoint());
        processor.setAdapter(getAdapter());
        processor.setMaxKeepAliveRequests(getMaxKeepAliveRequests());
        processor.setConnectionUploadTimeout(getConnectionUploadTimeout());
        processor.setDisableUploadTimeout(getDisableUploadTimeout());
        processor.setRestrictedUserAgents(getRestrictedUserAgents());
        processor.setMaxSavePostSize(getMaxSavePostSize());
        return processor;
    }
}

        1.2、构造方法

        Http11Processor构造方法第一步使用super调用了父类AbstractProcessor的构造方法,可以看到里面使用new创建了request(org.apache.coyote.Request)与response(org.apache.coyote.Response)对象,而子类中使用这两个对象创建httpParser(HttpParser)、inputBuffer(Http11InputBuffer)、outputBuffer(Http11OutputBuffer)等组件。

    public Http11Processor(AbstractHttp11Protocol<?> protocol, AbstractEndpoint<?, ?> endpoint) {
        super(endpoint);
        this.protocol = protocol;

        httpParser = new HttpParser(protocol.getRelaxedPathChars(), protocol.getRelaxedQueryChars());
        inputBuffer = new Http11InputBuffer(request, protocol.getMaxHttpRequestHeaderSize(),
                protocol.getRejectIllegalHeader(), httpParser);
        request.setInputBuffer(inputBuffer);
        outputBuffer = new Http11OutputBuffer(response, protocol.getMaxHttpResponseHeaderSize(),
                protocol.getSendReasonPhrase());
        response.setOutputBuffer(outputBuffer);
        inputBuffer.addFilter(new IdentityInputFilter(protocol.getMaxSwallowSize()));
        outputBuffer.addFilter(new IdentityOutputFilter());
        inputBuffer.addFilter(
                new ChunkedInputFilter(protocol.getMaxTrailerSize(), protocol.getAllowedTrailerHeadersInternal(),
                        protocol.getMaxExtensionSize(), protocol.getMaxSwallowSize()));
        outputBuffer.addFilter(new ChunkedOutputFilter());
        inputBuffer.addFilter(new VoidInputFilter());
        outputBuffer.addFilter(new VoidOutputFilter());
        inputBuffer.addFilter(new BufferedInputFilter(protocol.getMaxSwallowSize()));
        outputBuffer.addFilter(new GzipOutputFilter());
        pluggableFilterIndex = inputBuffer.getFilters().length;
    }
public abstract class AbstractProcessor extends AbstractProcessorLight implements ActionHook {
    protected final AbstractEndpoint<?, ?> endpoint;
    protected final Request request;
    protected final Response response;

    public AbstractProcessor(AbstractEndpoint<?, ?> endpoint) {
        this(endpoint, new Request(), new Response());
    }

    protected AbstractProcessor(AbstractEndpoint<?, ?> endpoint, Request coyoteRequest, Response coyoteResponse) {
        this.endpoint = endpoint;
        asyncStateMachine = new AsyncStateMachine(this);
        request = coyoteRequest;
        response = coyoteResponse;
        response.setHook(this);
        request.setResponse(response);
        request.setHook(this);
        userDataHelper = new UserDataHelper(getLog());
    }

}

        2、process

        process方法在一个 do-while 循环里,根据不同的条件,分别处理,其中重要的处理是调用 dispatch 方法或者 service 方法。

public abstract class AbstractProcessorLight implements Processor {
    public SocketState process(SocketWrapperBase<?> socketWrapper, SocketEvent status) throws IOException {

        SocketState state = SocketState.CLOSED;
        Iterator<DispatchType> dispatches = null;
        do {
            if (dispatches != null) {
                DispatchType nextDispatch = dispatches.next();
                state = dispatch(nextDispatch.getSocketStatus());
                if (!dispatches.hasNext()) {
                    state = checkForPipelinedData(state, socketWrapper);
                }
            } else if (status == SocketEvent.DISCONNECT) {
            } else if (isAsync() || isUpgrade() || state == SocketState.ASYNC_END) {
                state = dispatch(status);
                state = checkForPipelinedData(state, socketWrapper);
            } else if (status == SocketEvent.OPEN_WRITE) {
                state = SocketState.LONG;
            } else if (status == SocketEvent.OPEN_READ) {
                state = service(socketWrapper);
            } else if (status == SocketEvent.CONNECT_FAIL) {
                logAccess(socketWrapper);
            } else {
                state = SocketState.CLOSED;
            }

            if (isAsync()) {
                state = asyncPostProcess();
            }

            if (dispatches == null || !dispatches.hasNext()) {
                dispatches = getIteratorAndClearDispatches();
            }
        } while (state == SocketState.ASYNC_END || dispatches != null && state != SocketState.CLOSED);

        return state;
    }
}

         从注释里可以看出,dispatch 方法是处理非标准 HTTP 模式下的正在处理中的请求,这是在 Servlet 3.0 Async 和 HTTP 升级连接里用到的。

    /**
     * Process an in-progress request that is not longer in standard HTTP mode. Uses currently include Servlet 3.0 Async
     * and HTTP upgrade connections. Further uses may be added in the future. These will typically start as HTTP
     * requests.
     *
     * @param status The event to process
     *
     * @return The state the caller should put the socket in when this method returns
     *
     * @throws IOException If an I/O error occurs during the processing of the request
     */
    protected abstract SocketState dispatch(SocketEvent status) throws IOException;

        与 dispatch 方法相对立,service 方法是用来处理标准的 HTTP 请求的。service 方法的实现 Http11Processor 里。 

    /**
     * Service a 'standard' HTTP request. This method is called for both new requests and for requests that have
     * partially read the HTTP request line or HTTP headers. Once the headers have been fully read this method is not
     * called again until there is a new HTTP request to process. Note that the request type may change during
     * processing which may result in one or more calls to {@link #dispatch(SocketEvent)}. Requests may be pipe-lined.
     *
     * @param socketWrapper The connection to process
     *
     * @return The state the caller should put the socket in when this method returns
     *
     * @throws IOException If an I/O error occurs during the processing of the request
     */
    protected abstract SocketState service(SocketWrapperBase<?> socketWrapper) throws IOException;

        3、service

        service先执行一下初步工作setSocketWrapper,内部调用了inputBuffer.init(socketWrapper) 、 outputBuffer.init(socketWrapper) 等,然后就进入一个 while 循环。在 while 循环里,是在 try-catch 语句块里执行inputBuffer.parseRequestLine(用来处理请求行)以及一些状态和属性的设置。

        然后就执行调用 prepareRequestProtocol() 方法来对请求就行初步处理,也就是针对请求头里的一些属性加入一些 InputFilter 到 Http11InputBuffer 里。比如解析请求头里的 host,transfer-encoding,content-length 等。

        最后就调用 Adapter 的方法进行处理了,也就是getAdapter().service(request, response);

public class Http11Processor extends AbstractProcessor {
    public SocketState service(SocketWrapperBase<?> socketWrapper) throws IOException {
        RequestInfo rp = request.getRequestProcessor();
        rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);

        // Setting up the I/O
        setSocketWrapper(socketWrapper);

        // Flags
        keepAlive = true;
        openSocket = false;
        readComplete = true;
        boolean keptAlive = false;
        SendfileState sendfileState = SendfileState.DONE;

        while (!getErrorState().isError() && keepAlive && !isAsync() && upgradeToken == null &&
                sendfileState == SendfileState.DONE && !endpoint.isPaused()) {
            // Parsing the request header
            try {
                if (!inputBuffer.parseRequestLine(keptAlive)) {
                    if (inputBuffer.getParsingRequestLinePhase() == -1) {
                        return SocketState.UPGRADING;
                    } else if (handleIncompleteRequestLineRead()) {
                        break;
                    }
                }
                // Process the Protocol component of the request line
                // Need to know if this is an HTTP 0.9 request before trying to
                // parse headers.
                prepareRequestProtocol();
            } 
            // Has an upgrade been requested?
			// ...

            // Process the request in the adapter
            if (getErrorState().isIoAllowed()) {
                try {
                    rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
                    getAdapter().service(request, response);

                    if (keepAlive && !getErrorState().isError() && !isAsync() &&
                            statusDropsConnection(response.getStatus())) {
                        setErrorState(ErrorState.CLOSE_CLEAN, null);
                    }
                } 
            }
        }

    }
}

        可以看出Processor 接收来自 EndPoint 的 Socket,读取字节流解析成 Tomcat Request 和 Response 对象,最后将请求传递给了Adapter做进一步的处理。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/601290.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++线程 并发编程:std::thread、std::sync与std::packaged_task深度解析

C线程 并发编程&#xff1a;std::thread、std::sync与std::packaged_task深度解析 1. C并发编程概述&#xff08;C Concurrency Overview&#xff09;1.1 并发与并行的区别&#xff08;Difference between Concurrency and Parallelism&#xff09;1.2 C中的线程模型&#xff0…

(二)微服务中间键工作原理——nacos服务端服务注册心跳包(/nacos/v1/ns/instance/beat)源码解读

前言 上节内容我们通过分析nacos客户端源码&#xff0c;了解了nacos客户端是如何向服务端注册服务和发送心跳包的&#xff0c;本节内容话接上一节内容&#xff0c;我们通过分析nacos服务的源码&#xff0c;查看服务端是如何处理客户端注册时候的心跳包的。关于nacos服务端的源…

工程制图篇:公差原则与要求

目录 1、尺寸与几何公差2、公差原则3、公差要求4、关键名词解释5、常用通用量具 1、尺寸与几何公差 1&#xff09;尺寸公差1&#xff1a;上极限尺寸减去下极限尺寸之差&#xff0c;或上极限偏差减去下极限偏差只差。它是允许尺寸的变动量。尺寸公差是一个没有符号的绝对值&…

Cisco.Packet.Tracer思科模拟器练习题

一、网络设备配置 1、将 RA 路由器重命名为 RA&#xff0c;将 RB 路由器重命名为 RB 2、按下方表格中的要求完成设备主机名&#xff08;hostname&#xff09;及接口配置&#xff1b; 3、将 RA 的 DCE 端时间频率设置 64000&#xff0c;并封装 PPP 协议后发送用户名密码 usera…

Spring 源码分析衍生篇十三 :事务扩展机制 TransactionSynchronization

文章目录 一、前言二、TransactionSynchronization 1. TransactionSynchronization 1.1 TransactionSynchronization 的定义1.2 TransactionSynchronizationManager2. TransactionSynchronization 原理简述 2.1 TransactionSynchronization#beforeCommit2.2 TransactionSynchro…

基于状态的系统和有限状态机FSM

基于状态的系统和有限状态机FSM 介绍 基于模型进行测试。模型可能是规格或描述感兴趣的属性。 该模型通常是一个抽象概念&#xff0c;应该相对容易理解。 测试补充了白盒方法。 测试通常是黑盒&#xff1a;不考虑实际系统的结构。 如果模型具有形式语义&#xff0c;则可实现的…

leetcode--二叉搜索树中第K小的元素(java)

二叉搜索树中第K小的元素 leetcode 230 题 二叉搜索树第K 小的元素解题思路代码演示二叉树专题 leetcode 230 题 二叉搜索树第K 小的元素 原题链接&#xff1a; https://leetcode.cn/problems/kth-smallest-element-in-a-bst/ 题目描述 给定一个二叉搜索树的根节点 root &#…

智慧办公室虚拟现实 VR 可视化

“虚拟现实”是来自英文“Virtual Reality”&#xff0c;简称 VR 技术&#xff0c;其是通过利用计算机仿真系统模拟外界环境&#xff0c;主要模拟对象有环境、技能、传感设备和感知等&#xff0c;为用户提供多信息、三维动态、交互式的仿真体验。 图扑软件基于自研可视化引擎 H…

亚马逊云科技位列IDC MarketScape决策支持分析数据平台领导者

随着科学技术的不断发展&#xff0c;人们的生活方式也在不断改变。现在&#xff0c;人们可以通过互联网获得更多的信息&#xff0c;也可以通过智能手机随时随地与他人进行交流。此外&#xff0c;人工智能技术的进步也使得机器能够完成一些复杂的任务&#xff0c;从而提高了人们…

【Azure】微软 Azure 基础解析(六)计算服务中的虚拟机 VM、虚拟机规模集、Azure Functions 与 Azure 容器(ACI)

本系列博文还在更新中&#xff0c;收录在专栏&#xff1a;「Azure探秘&#xff1a;构建云计算世界」 专栏中。 本系列文章列表如下&#xff1a; 【Azure】微软 Azure 基础解析&#xff08;三&#xff09;描述云计算运营中的 CapEx 与 OpEx&#xff0c;如何区分 CapEx 与 OpEx…

写了 7 年代码,第一次见这么狗血的小 Bug!

刚刚修我们鱼聪明 AI 助手平台的一个 Bug&#xff0c;结局很狗血&#xff01;赶紧给大家分享一下&#xff0c;顺便也分享下标准的排查 Bug 思路。 事情是这样的&#xff0c;有小伙伴在鱼聪明平台&#xff08;https://www.yucongming.com&#xff09;创建了一个 AI 助手&#x…

ModStartCMS v6.5.0 菜单多级支持,框架结构优化

ModStart 是一个基于 Laravel 模块化极速开发框架。模块市场拥有丰富的功能应用&#xff0c;支持后台一键快速安装&#xff0c;让开发者能快的实现业务功能开发。 系统完全开源&#xff0c;基于 Apache 2.0 开源协议&#xff0c;免费且不限制商业使用。 功能特性 丰富的模块市…

InnoDB Cluster集群Mysql Router代理层最佳实践

InnoDB Cluster 集群 & Mysql-Router 代理层 前言 Mysql是现今最常用的关系型数据库之一&#xff0c;高可用一直是我们对软件服务的要求。常见的Mysql高可用是主从配置&#xff0c;在主节点挂掉后需要依赖监控脚本进行主从切换将从节点升级&#xff0c;后台服务代码层面也…

正在破坏您的协程(Coroutines)的无声杀手(Silent Killer)

正在破坏您的协程的无声杀手 处理 Kotlin 中的取消异常的唯一安全方法是不重新抛出它们。 今天生产服务器再次停止响应流量。 上个星期&#xff0c;你刚重新启动它们并将其视为故障。但是你总觉得有些奇怪&#xff0c;因为日志中没有任何错误的痕迹&#xff0c;甚至没有警告。…

vue cli配置代理解决跨域问题

跨域问题 是由于违背了同源策略&#xff0c;同源策略规定了协议名、主机名、端口号必须一致 我们目前所处的位置是http localhost 8080&#xff0c;我们想向一台服务器发送请求&#xff0c;它的位置是http localhost 5000&#xff0c;我们的ajax请求从浏览器发送到服务器&#…

界面组件DevExpress WPF中文指南 - 如何应用系统强调色及主题切换

在最新版本的Microsoft Office产品中&#xff0c;用户可以根据系统设置选择主题&#xff0c;当使用这个主题时&#xff0c;Office将采用Windows强调色和应用模式(亮/暗)设置&#xff0c;并将它们应用到Office本身。如果用户在操作系统中更改了强调色或应用模式&#xff0c;Offi…

【spring源码系列-02】通过refresh方法剖析IOC的整体流程

Spring源码系列整体栏目 内容链接地址【一】spring源码整体概述https://blog.csdn.net/zhenghuishengq/article/details/130940885【一】通过refresh方法剖析IOC的整体流程https://blog.csdn.net/zhenghuishengq/article/details/131003428 spring底层源码整体概述 一&#xff…

2个实际工作中的小技巧,先收再看(网工版)

大家好&#xff0c;我是老杨。 本来想发点关于快乐的文章&#xff0c;但我思来想去&#xff0c;对成年人最大的快乐&#xff0c;莫过于高效完成工作&#xff0c;早点下班回家。 关于提升工作效率这方面啊&#xff0c;我的文章其实零碎、分散的写过了很多次了。 你要知道&…

ChatGPT国内免费使用地址和开源公众号集成项目分享

ChatGPT国内免费使用地址和开源公众号集成项目分享 ChatGPT国内免费使用地址ChatGPT开源公众号集成项目ChatGPT国内免费调用API的地址总结免费体验地址 人工智能技术的快速发展&#xff0c;ChatGPT聊天机器人备受瞩目。然而&#xff0c;如何在国内免费使用ChatGPT却是许多人关注…

手绘echarts散点图

面对各种定制&#xff0c;echarts图表有时候不好处理&#xff0c;无论是数据处理还是样式处理&#xff0c;都会被echarts限制。 举例&#xff1a;echarts散点图如果数据较少&#xff0c;echarts会均匀分布&#xff0c;如图1 对于产品或者老板对页面的要求&#xff0c;认为中间…