查漏补缺:连接器在Tomcat中是如何设计的
创始人
2025-03-18 12:31:23
0次

从连接器(Connector)源码说起

[[277690]]

既然是来解析连接器(Connector),那么我们直接从源码入手,后面所有源码我会剔除不重要部分,所以会忽略大部分源码细节,只关注流程。源码如下(高能预警,大量代码):

  1. public class Connector extends LifecycleMBeanBase { 
  2.  public Connector() { 
  3.  this("org.apache.coyote.http11.Http11NioProtocol"); 
  4.  } 
  5.  public Connector(String protocol) { 
  6.  boolean aprConnector = AprLifecycleListener.isAprAvailable() && 
  7.  AprLifecycleListener.getUseAprConnector(); 
  8.  if ("HTTP/1.1".equals(protocol) || protocol == null) { 
  9.  if (aprConnector) { 
  10.  protocolHandlerClassName = "org.apache.coyote.http11.Http11AprProtocol"; 
  11.  } else { 
  12.  protocolHandlerClassName = "org.apache.coyote.http11.Http11NioProtocol"; 
  13.  } 
  14.  } else if ("AJP/1.3".equals(protocol)) { 
  15.  if (aprConnector) { 
  16.  protocolHandlerClassName = "org.apache.coyote.ajp.AjpAprProtocol"; 
  17.  } else { 
  18.  protocolHandlerClassName = "org.apache.coyote.ajp.AjpNioProtocol"; 
  19.  } 
  20.  } else { 
  21.  protocolHandlerClassName = protocol; 
  22.  } 
  23.  // Instantiate protocol handler 
  24.  ProtocolHandler p = null; 
  25.  try { 
  26.  Class clazz = Class.forName(protocolHandlerClassName); 
  27.  p = (ProtocolHandler) clazz.getConstructor().newInstance(); 
  28.  } catch (Exception e) { 
  29.  log.error(sm.getString( 
  30.  "coyoteConnector.protocolHandlerInstantiationFailed"), e); 
  31.  } finally { 
  32.  this.protocolHandler = p; 
  33.  } 
  34.  // Default for Connector depends on this system property 
  35.  setThrowOnFailure(Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")); 
  36.  } 

我们来看看Connector的构造方法,其实只做了一件事情,就是根据协议设置对应的ProtocolHandler,根据名称我们知道,这是协议处理类,所以连接器内部的一个重要子模块就是ProtocolHandler。

关于生命周期

我们看到Connector继承了LifecycleMBeanBase,我们来看看Connector的最终继承关系:

 

金九银十,查漏补缺:连接器在Tomcat中是如何设计的

 

我们看到最终实现的是Lifecycle接口,我们看看这个接口是何方神圣。我把其接口的注释拿下来解释下

  1. /** 
  2.  * Common interface for component life cycle methods. Catalina components 
  3.  * may implement this interface (as well as the appropriate interface(s) for 
  4.  * the functionality they support) in order to provide a consistent mechanism 
  5.  * to start and stop the component. 
  6.  * start() 
  7.  * ----------------------------- 
  8.  * | | 
  9.  * | init() | 
  10.  * NEW -»-- INITIALIZING | 
  11.  * | | | | ------------------«----------------------- 
  12.  * | | |auto | | | 
  13.  * | | \|/ start() \|/ \|/ auto auto stop() | 
  14.  * | | INITIALIZED --»-- STARTING_PREP --»- STARTING --»- STARTED --»--- | 
  15.  * | | | | | 
  16.  * | |destroy()| | | 
  17.  * | --»-----«-- ------------------------«-------------------------------- ^ 
  18.  * | | | | 
  19.  * | | \|/ auto auto start() | 
  20.  * | | STOPPING_PREP ----»---- STOPPING ------»----- STOPPED -----»----- 
  21.  * | \|/ ^ | ^ 
  22.  * | | stop() | | | 
  23.  * | | -------------------------- | | 
  24.  * | | | | | 
  25.  * | | | destroy() destroy() | | 
  26.  * | | FAILED ----»------ DESTROYING ---«----------------- | 
  27.  * | | ^ | | 
  28.  * | | destroy() | |auto | 
  29.  * | --------»----------------- \|/ | 
  30.  * | DESTROYED | 
  31.  * | | 
  32.  * | stop() | 
  33.  * ----»-----------------------------»------------------------------ 
  34.  * 
  35.  * Any state can transition to FAILED. 
  36.  * 
  37.  * Calling start() while a component is in states STARTING_PREP, STARTING or 
  38.  * STARTED has no effect. 
  39.  * 
  40.  * Calling start() while a component is in state NEW will cause init() to be 
  41.  * called immediately after the start() method is entered. 
  42.  * 
  43.  * Calling stop() while a component is in states STOPPING_PREP, STOPPING or 
  44.  * STOPPED has no effect. 
  45.  * 
  46.  * Calling stop() while a component is in state NEW transitions the component 
  47.  * to STOPPED. This is typically encountered when a component fails to start and 
  48.  * does not start all its sub-components. When the component is stopped, it will 
  49.  * try to stop all sub-components - even those it didn't start. 
  50.  * 
  51.  * Attempting any other transition will throw {@link LifecycleException}. 
  52.  * 
  53.  * 
 
  •  * The {@link LifecycleEvent}s fired during state changes are defined in the 
  •  * methods that trigger the changed. No {@link LifecycleEvent}s are fired if the 
  •  * attempted transition is not valid. 
  • 这段注释翻译就是,这个接口是提供给组件声明周期管理的,并且提供了声明周期流转图。这里我们只需要知道正常流程即可:

    1. New--->Init()---->Start()---->Stop()--->Destory() 

    从生命周期探索连接器

    根据上面的生命周期说明,我们可以知道连接器(Connector)就是按照如此的声明周期管理的,所以我们找到了线索,所以连接器肯定会先初始化然后再启动。我们查看其initInternal()方法可以知道连接器初始化做了什么事情,源码如下:

    1. @Override 
    2.  protected void initInternal() throws LifecycleException { 
    3.  super.initInternal(); 
    4.  if (protocolHandler == null) { 
    5.  throw new LifecycleException( 
    6.  sm.getString("coyoteConnector.protocolHandlerInstantiationFailed")); 
    7.  } 
    8.  // Initialize adapter 
    9.  adapter = new CoyoteAdapter(this); 
    10.  protocolHandler.setAdapter(adapter); 
    11.  if (service != null) { 
    12.  protocolHandler.setUtilityExecutor(service.getServer().getUtilityExecutor()); 
    13.  } 
    14.  // Make sure parseBodyMethodsSet has a default 
    15.  if (null == parseBodyMethodsSet) { 
    16.  setParseBodyMethods(getParseBodyMethods()); 
    17.  } 
    18.  if (protocolHandler.isAprRequired() && !AprLifecycleListener.isInstanceCreated()) { 
    19.  throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoAprListener", 
    20.  getProtocolHandlerClassName())); 
    21.  } 
    22.  if (protocolHandler.isAprRequired() && !AprLifecycleListener.isAprAvailable()) { 
    23.  throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoAprLibrary", 
    24.  getProtocolHandlerClassName())); 
    25.  } 
    26.  if (AprLifecycleListener.isAprAvailable() && AprLifecycleListener.getUseOpenSSL() && 
    27.  protocolHandler instanceof AbstractHttp11JsseProtocol) { 
    28.  AbstractHttp11JsseProtocol jsseProtocolHandler = 
    29.  (AbstractHttp11JsseProtocol) protocolHandler; 
    30.  if (jsseProtocolHandler.isSSLEnabled() && 
    31.  jsseProtocolHandler.getSslImplementationName() == null) { 
    32.  // OpenSSL is compatible with the JSSE configuration, so use it if APR is available 
    33.  jsseProtocolHandler.setSslImplementationName(OpenSSLImplementation.class.getName()); 
    34.  } 
    35.  } 
    36.  try { 
    37.  protocolHandler.init(); 
    38.  } catch (Exception e) { 
    39.  throw new LifecycleException( 
    40.  sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e); 
    41.  } 
    42.  } 
    43. } 

    根据上面源码,我们发现主要是处理protocolHandler并初始化它,同时我们注意到了protocolHandler 设置了一个适配器,我们看看这个适配器是做啥的,跟踪源码如下:

    1. /** 
    2.  * The adapter, used to call the connector. 
    3.  * 
    4.  * @param adapter The adapter to associate 
    5.  */ 
    6.  public void setAdapter(Adapter adapter); 

    这个注释已经说的很直白了,这个适配器就是用来调用连接器的。我们再继续看看protocolHandler的初始化方法

    1.  /** 
    2.  * Endpoint that provides low-level network I/O - must be matched to the 
    3.  * ProtocolHandler implementation (ProtocolHandler using NIO, requires NIO 
    4.  * Endpoint etc.). 
    5.  */ 
    6. private final AbstractEndpoint endpoint; 
    7. public void init() throws Exception { 
    8.  if (getLog().isInfoEnabled()) { 
    9.  getLog().info(sm.getString("abstractProtocolHandler.init", getName())); 
    10.  logPortOffset(); 
    11.  } 
    12.  if (oname == null) { 
    13.  // Component not pre-registered so register it 
    14.  oname = createObjectName(); 
    15.  if (oname != null) { 
    16.  Registry.getRegistry(null, null).registerComponent(this, oname, null); 
    17.  } 
    18.  } 
    19.  if (this.domain != null) { 
    20.  rgOname = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getName()); 
    21.  Registry.getRegistry(null, null).registerComponent( 
    22.  getHandler().getGlobal(), rgOname, null); 
    23.  } 
    24.  String endpointName = getName(); 
    25.  endpoint.setName(endpointName.substring(1, endpointName.length()-1)); 
    26.  endpoint.setDomain(domain); 
    27.  endpoint.init(); 
    28.  } 

    这里出现了一个新的对象,endpoint,根据注释我们可以知道endpoint是用来处理网络IO的,而且必须匹配到指定的子类(比如Nio,就是NioEndPoint处理)。endpoint.init()实际上就是做一些网络的配置,然后就是初始化完毕了。根据我们上面的周期管理,我们知道init()后就是start(),所以我们查看Connector的start()源码:

    1. protected void startInternal() throws LifecycleException { 
    2. // Validate settings before starting 
    3. if (getPortWithOffset() < 0) { 
    4. throw new LifecycleException(sm.getString( 
    5. "coyoteConnector.invalidPort", Integer.valueOf(getPortWithOffset()))); 
    6. } 
    7. setState(LifecycleState.STARTING); 
    8. try { 
    9. protocolHandler.start(); 
    10. } catch (Exception e) { 
    11. throw new LifecycleException( 
    12. sm.getString("coyoteConnector.protocolHandlerStartFailed"), e); 
    13. } 
    14. } 

    其实就是主要调用 protocolHandler.start()方法,继续跟踪,为了方便表述,我会把接下来的代码统一放在一起说明,代码如下:

    1. //1.类:AbstractProtocol implements ProtocolHandler, 
    2.  MBeanRegistration 
    3.  public void start() throws Exception { 
    4.  // 省略部分代码 
    5.  endpoint.start(); 
    6.  } 
    7. //2. 类:AbstractEndPoint  
    8. public final void start() throws Exception { 
    9.  // 省略部分代码 
    10.  startInternal(); 
    11.  } 
    12.  /**3.类:NioEndPoint extends AbstractJsseEndpoint 
    13.  * Start the NIO endpoint, creating acceptor, poller threads. 
    14.  */ 
    15.  @Override 
    16.  public void startInternal() throws Exception { 
    17.  //省略部分代码 
    18.   
    19.  // Start poller thread 
    20.  poller = new Poller(); 
    21.  Thread pollerThread = new Thread(poller, getName() + "-ClientPoller"); 
    22.  pollerThread.setPriority(threadPriority); 
    23.  pollerThread.setDaemon(true); 
    24.  pollerThread.start(); 
    25.  startAcceptorThread(); 
    26.  } 
    27.  } 

    到这里,其实整个启动代码就完成了,我们看到最后是在NioEndPoint创建了一个Poller,并且启动它,这里需要补充说明下,这里只是以NioEndPoint为示列,其实Tomcat 主要提供了三种实现,分别是AprEndPoint,NioEndPoint,Nio2EndPoint,这里表示了tomcat支持的I/O模型:

    • APR:采用 Apache 可移植运行库实现,它根据不同操作系统,分别用c重写了大部分IO和系统线程操作模块,据说性能要比其他模式要好(未实测)。
    • NIO:非阻塞 I/O
    • NIO.2:异步 I/O

    上述代码主要是开启两个线程,一个是Poller,一个是开启Acceptor,既然是线程,核心的代码肯定是run方法,我们来查看源码,代码如下:

    1. //4.类:Acceptor implements Runnable 
    2.  public void run() { 
    3.  //省略了部分代码 
    4.  U socket = null; 
    5.  socket = endpoint.serverSocketAccept(); 
    6.  // Configure the socket 
    7.  if (endpoint.isRunning() && !endpoint.isPaused()) { 
    8.  // setSocketOptions() will hand the socket off to 
    9.  // an appropriate processor if successful 
    10.  //核心逻辑 
    11.  if (!endpoint.setSocketOptions(socket)) { 
    12.  endpoint.closeSocket(socket); 
    13.  } 
    14.  } else { 
    15.  endpoint.destroySocket(socket); 
    16.  } 
    17.   
    18.  state = AcceptorState.ENDED; 
    19. } 
    20. //5.类:NioEndpoint 
    21. protected boolean setSocketOptions(SocketChannel socket) { 
    22.  // Process the connection 
    23.  //省略部分代码 
    24.  try { 
    25.  // Disable blocking, polling will be used 
    26.  socket.configureBlocking(false); 
    27.  Socket sock = socket.socket(); 
    28.  socketProperties.setProperties(sock); 
    29.  NioSocketWrapper socketWrapper = new NioSocketWrapper(channel, this); 
    30.  channel.setSocketWrapper(socketWrapper); 
    31.  socketWrapper.setReadTimeout(getConnectionTimeout()); 
    32.  socketWrapper.setWriteTimeout(getConnectionTimeout()); 
    33.  socketWrapper.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests()); 
    34.  socketWrapper.setSecure(isSSLEnabled()); 
    35.  //核心逻辑 
    36.  poller.register(channel, socketWrapper); 
    37.  return true; 
    38.   
    39.  } 

    这里可以发现Acceptor主要就是接受socket,然后把它注册到poller中,我们继续看看是如何注册的。

    1. /**6.类NioEndpoint 
    2.  * Registers a newly created socket with the poller. 
    3.  * 
    4.  * @param socket The newly created socket 
    5.  * @param socketWrapper The socket wrapper 
    6.  */ 
    7.  public void register(final NioChannel socket, final NioSocketWrapper socketWrapper) { 
    8.  socketWrapper.interestOps(SelectionKey.OP_READ);//this is what OP_REGISTER turns into. 
    9.  PollerEvent r = null; 
    10.  if (eventCache != null) { 
    11.  r = eventCache.pop(); 
    12.  } 
    13.  if (r == null) { 
    14.  r = new PollerEvent(socket, OP_REGISTER); 
    15.  } else { 
    16.  r.reset(socket, OP_REGISTER); 
    17.  } 
    18.  addEvent(r); 
    19.  } 
    20. /** 7.类:PollerEvent implements Runnable 
    21.  public void run() { 
    22.  //省略部分代码 
    23.  socket.getIOChannel().register(socket.getSocketWrapper().getPoller().getSelector(), SelectionKey.OP_READ, socket.getSocketWrapper()); 
    24.  } 

    这里发现最终就是采用NIO模型把其注册到通道中。(这里涉及NIO网络编程知识,不了解的同学可以传送这里)。那么注册完毕后,我们看看Poller做了什么事情。

    1. */  
    2.  /**8.类:NioEndPoint内部类 Poller implements Runnable 
    3.  **/  
    4.  @Override 
    5.  public void run() { 
    6.  // Loop until destroy() is called 
    7.  while (true) { 
    8.  //省略部分代码 
    9.  Iterator iterator = 
    10.  keyCount > 0 ? selector.selectedKeys().iterator() : null; 
    11.  // Walk through the collection of ready keys and dispatch 
    12.  // any active event. 
    13.  while (iterator != null && iterator.hasNext()) { 
    14.  SelectionKey sk = iterator.next(); 
    15.  NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment(); 
    16.  // Attachment may be null if another thread has called 
    17.  // cancelledKey() 
    18.  if (socketWrapper == null) { 
    19.  iterator.remove(); 
    20.  } else { 
    21.  iterator.remove(); 
    22.  //sock处理 
    23.  processKey(sk, socketWrapper); 
    24.  } 
    25.  } 
    26.  //省略部分代码 
    27.  }  

    这个就是通过selector把之前注册的事件取出来,从而完成了调用。

    1. //9.类: NioEndPoint内部类 Poller implements Runnable  
    2. protected void processKey(SelectionKey sk, NioSocketWrapper socketWrapper) { 
    3.  //省略大部分代码 
    4.  processSocket(socketWrapper, SocketEvent.OPEN_WRITE, true) 
    5.   
    6. } 
    7.   
    8. //10.类:AbstractEndPoint  
    9. public boolean processSocket(SocketWrapperBase socketWrapper, 
    10.  SocketEvent event, boolean dispatch) { 
    11.  //省略部分代码 
    12.  Executor executor = getExecutor(); 
    13.  if (dispatch && executor != null) { 
    14.  executor.execute(sc); 
    15.  } else { 
    16.  sc.run(); 
    17.  } 
    18.   
    19.  return true; 
    20.  }  
    21. //11.类:SocketProcessorBase implements Runnable  
    22. public final void run() { 
    23.  synchronized (socketWrapper) { 
    24.  // It is possible that processing may be triggered for read and 
    25.  // write at the same time. The sync above makes sure that processing 
    26.  // does not occur in parallel. The test below ensures that if the 
    27.  // first event to be processed results in the socket being closed, 
    28.  // the subsequent events are not processed. 
    29.  if (socketWrapper.isClosed()) { 
    30.  return; 
    31.  } 
    32.  doRun(); 
    33.  } 
    34.  } 
    35.   
    36. //类:12.NioEndPoint extends AbstractJsseEndpoint  
    37. protected void doRun() { 
    38.  //省略部分代码 
    39.  if (handshake == 0) { 
    40.  SocketState state = SocketState.OPEN; 
    41.  // Process the request from this socket 
    42.  if (event == null) { 
    43.  state = getHandler().process(socketWrapper, SocketEvent.OPEN_READ); 
    44.  } else { 
    45.  state = getHandler().process(socketWrapper, event); 
    46.  } 
    47.  if (state == SocketState.CLOSED) { 
    48.  poller.cancelledKey(key, socketWrapper); 
    49.  } 
    50.  } 
    51.  }  
    52.   

    Poller调用的run方法或者用Executor线程池去执行run(),最终调用都是各个子EndPoint中的doRun()方法,最终会取一个Handler去处理socketWrapper。继续看源码:

    1. //类:13.AbstractProtocol内部类ConnectionHandler implements AbstractEndpoint.Handler 
    2.  public SocketState process(SocketWrapperBase wrapper, SocketEvent status) { 
    3.  //省略部分代码 
    4.   
    5.  state = processor.process(wrapper, status); 
    6.   
    7.  return SocketState.CLOSED; 
    8.  } 
    9.   
    10. //类:14.AbstractProcessorLight implements Processor  
    11. public SocketState process(SocketWrapperBase socketWrapper, SocketEvent status) 
    12.  throws IOException { 
    13.  //省略部分代码 
    14.   
    15.  state = service(socketWrapper); 
    16.   
    17.  return state; 
    18.  } 

    这部分源码表明最终调用的process是通过一个Processor接口的实现类来完成的,这里最终也是会调用到各个子类中,那么这里的处理器其实就是处理应用协议,我们可以查看AbstractProcessorLight的实现类,分别有AjpProcessor、Http11Processor、StreamProcessor,分别代表tomcat支持三种应用层协议,分别是:

    • AJP协议
    • HTTP.1协议
    • HTTP2.0协议

    这里我们以常用的HTTP1.1为例,继续看源码:

    1. //类:15. Http11Processor extends AbstractProcessor 
    2. public SocketState service(SocketWrapperBase socketWrapper) 
    3.  throws IOException { 
    4.  //省略大部分代码 
    5.  getAdapter().service(request, response); 
    6.  //省略大部分代码  
    7.  }  
    8. //类:16 CoyoteAdapter implements Adapter 
    9. public void service(org.apache.coyote.Request req, org.apache.coyote.Response res) 
    10.  throws Exception { 
    11.  Request request = (Request) req.getNote(ADAPTER_NOTES); 
    12.  Response response = (Response) res.getNote(ADAPTER_NOTES); 
    13.  postParseSuccess = postParseRequest(req, request, res, response); 
    14.  if (postParseSuccess) { 
    15.  //check valves if we support async 
    16.  request.setAsyncSupported( 
    17.  connector.getService().getContainer().getPipeline().isAsyncSupported()); 
    18.  // Calling the container 
    19.  connector.getService().getContainer().getPipeline().getFirst().invoke( 
    20.  request, response); 
    21.  } 
    22.   
    23.  } 

    这里我们发现协议处理器最终会调用适配器(CoyoteAdapter),而适配器最终的工作是转换Request和Response对象为HttpServletRequest和HttpServletResponse,从而可以去调用容器,到这里整个连接器的流程和作用我们就已经分析完了。

    小结

    那么我们来回忆下整个流程,我画了一张时序图来说明:

     

    金九银十,查漏补缺:连接器在Tomcat中是如何设计的

     

    这张图包含了两个流程,一个是组件的初始化,一个是调用的流程。连接器(Connector)主要初始化了两个组件,ProtcoHandler和EndPoint,但是我们从代码结构发现,他们两个是父子关系,也就是说ProtcoHandler包含了EndPoint。后面的流程就是各个子组件的调用链关系,总结来说就是Acceptor负责接收请求,然后注册到Poller,Poller负责处理请求,然后调用processor处理器来处理,最后把请求转成符合Servlet规范的request和response去调用容器(Container)。点击免费“领取Java架构资料”

    我们流程梳理清楚了,接下来我们来结构化的梳理下:

    回到连接器(Connector)是源码,我们发现,上述说的模块只有ProtocolHandler和Adapter两个属于连接器中,也就是说,连接器只包含了这两大子模块,那么后续的EndPoint、Acceptor、Poller、Processor都是ProtocolHandler的子模块。 而Acceptor和Poller两个模块的核心功能都是在EndPoint 中完成的,所以是其子模块,而Processor比较独立,所以它和EndPoint是一个级别的子模块。

    我们用图来说明下上述的关系:

     

    金九银十,查漏补缺:连接器在Tomcat中是如何设计的

     

    根据上图我们可以知道,连接器主要负责处理连接请求,然后通过适配器调用容器。那么具体流程细化可以如下:

    • Acceptor监听网络请求,获取请求。
    • Poller获取到监听的请求提交线程池进行处理。
    • Processor根据具体的应用协议(HTTP/AJP)来生成Tomcat Request对象。
    • Adapter把Request对象转换成Servlet标准的Request对象,调用容器。

    总结

    我们从连接器的源码,一步一步解析,分析了连接器主要包含了两大模块,ProtocolHandler和Adapter。ProtocolHandler主要包含了Endpoint模块和Processor模块。Endpoint模块主要的作用是连接的处理,它委托了Acceptor子模块进行连接的监听和注册,委托子模块Poller进行连接的处理;而Processor模块主要是应用协议的处理,最后提交给Adapter进行对象的转换,以便可以调用容器(Container)。另外我们也在分析源码的过程中补充了一些额外知识点:

    • 当前Tomcat版本支持的IO模型为:APR模型、NIO模型、NIO.2模型
    • Tomcat支持的协议是AJP和HTTP,其中HTTP又分为HTTP1.1和HTTP2.0

     

    相关内容

    热门资讯

    PHP新手之PHP入门 PHP是一种易于学习和使用的服务器端脚本语言。只需要很少的编程知识你就能使用PHP建立一个真正交互的...
    网络中立的未来 网络中立性是什... 《牛津词典》中对“网络中立”的解释是“电信运营商应秉持的一种原则,即不考虑来源地提供所有内容和应用的...
    各种千兆交换机的数据接口类型详... 千兆交换机有很多值得学习的地方,这里我们主要介绍各种千兆交换机的数据接口类型,作为局域网的主要连接设...
    粉嫩如何诠释霸道 东芝M805... “霸道粉”是个什么玩意东芝M805拿过来的时候,笔者扑哧笑了,不是笑这款笔记本,而是笑这款产品的颜色...
    如何利用交换机和端口设置来管理... 在网络管理中,总是有些人让管理员头疼。下面我们就将介绍一下一个网管员利用交换机以及端口设置等来进行D...
    什么是大数据安全 什么是大数据... 在《为什么需要大数据安全分析》一文中,我们已经阐述了一个重要观点,即:安全要素信息呈现出大数据的特征...
    只需三步,让你掌握阿尔法路由器... 请参照以下阿尔法路由器设置方法,通过浏览器进入及设置宽带路由器。经过这三个步骤的设置,基本上能够熟悉...
    全面诠释网络负载均衡 负载均衡的出现大大缓解了服务器的压力,更是有效的利用了资源,提高了效率。那么我们现在来说一下网络负载...
    如何允许远程连接到MySQL数... [[277004]]【51CTO.com快译】默认情况下,MySQL服务器仅侦听来自localhos...
    Windows恶意软件20年“... 在Windows的早期年代,病毒游走于系统之间,偶尔删除文件(但被删除的文件几乎都是可恢复的),并弹...