連接器在Tomcat中是怎么設(shè)計的

這篇文章將為大家詳細講解有關(guān)連接器在Tomcat中是怎么設(shè)計的,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關(guān)知識有一定的了解。

創(chuàng)新互聯(lián)建站網(wǎng)站建設(shè)公司是一家服務(wù)多年做網(wǎng)站建設(shè)策劃設(shè)計制作的公司,為廣大用戶提供了成都網(wǎng)站建設(shè)、網(wǎng)站建設(shè),成都網(wǎng)站設(shè)計,廣告投放平臺,成都做網(wǎng)站選創(chuàng)新互聯(lián)建站,貼合企業(yè)需求,高性價比,滿足客戶不同層次的需求一站式服務(wù)歡迎致電。

成都創(chuàng)新互聯(lián)是專業(yè)的民勤網(wǎng)站建設(shè)公司,民勤接單;提供成都做網(wǎng)站、網(wǎng)站設(shè)計,網(wǎng)頁設(shè)計,網(wǎng)站設(shè)計,建網(wǎng)站,PHP網(wǎng)站建設(shè)等專業(yè)做網(wǎng)站服務(wù);采用PHP框架,可快速的進行民勤網(wǎng)站開發(fā)網(wǎng)頁制作和功能擴展;專業(yè)做搜索引擎喜愛的網(wǎng)站,專業(yè)的做網(wǎng)站團隊,希望更多企業(yè)前來合作!

專業(yè)領(lǐng)域包括成都做網(wǎng)站、網(wǎng)站建設(shè)、外貿(mào)營銷網(wǎng)站建設(shè)成都商城網(wǎng)站開發(fā)、微信營銷、系統(tǒng)平臺開發(fā), 與其他網(wǎng)站設(shè)計及系統(tǒng)開發(fā)公司不同,創(chuàng)新互聯(lián)的整合解決方案結(jié)合了幫做網(wǎng)絡(luò)品牌建設(shè)經(jīng)驗和互聯(lián)網(wǎng)整合營銷的理念,并將策略和執(zhí)行緊密結(jié)合,為客戶提供全網(wǎng)互聯(lián)網(wǎng)整合方案。

創(chuàng)新互聯(lián)專注于企業(yè)營銷型網(wǎng)站建設(shè)、網(wǎng)站重做改版、坪山網(wǎng)站定制設(shè)計、自適應品牌網(wǎng)站建設(shè)、HTML5、成都商城網(wǎng)站開發(fā)、集團公司官網(wǎng)建設(shè)、成都外貿(mào)網(wǎng)站制作、高端網(wǎng)站制作、響應式網(wǎng)頁設(shè)計等建站業(yè)務(wù),價格優(yōu)惠性價比高,為坪山等各大城市提供網(wǎng)站開發(fā)制作服務(wù)。

創(chuàng)新互聯(lián)網(wǎng)站建設(shè)提供從項目策劃、軟件開發(fā),軟件安全維護、網(wǎng)站優(yōu)化(SEO)、網(wǎng)站分析、效果評估等整套的建站服務(wù),主營業(yè)務(wù)為網(wǎng)站設(shè)計、成都網(wǎng)站設(shè)計,重慶App定制開發(fā)以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠的服務(wù)。創(chuàng)新互聯(lián)深信只要達到每一位用戶的要求,就會得到認可,從而選擇與我們長期合作。這樣,我們也可以走得更遠!

創(chuàng)新互聯(lián)-專業(yè)網(wǎng)站定制、快速模板網(wǎng)站建設(shè)、高性價比楚雄州網(wǎng)站開發(fā)、企業(yè)建站全套包干低至880元,成熟完善的模板庫,直接使用。一站式楚雄州網(wǎng)站制作公司更省心,省錢,快速模板網(wǎng)站建設(shè)找我們,業(yè)務(wù)覆蓋楚雄州地區(qū)。費用合理售后完善,十余年實體公司更值得信賴。

從連接器(Connector)源碼說起

既然是來解析連接器(Connector),那么我們直接從源碼入手,后面所有源碼我會剔除不重要部分,所以會忽略大部分源碼細節(jié),只關(guān)注流程。源碼如下(高能預警,大量代碼):

public class Connector extends LifecycleMBeanBase {  public Connector() {  this("org.apache.coyote.http11.Http11NioProtocol");  }  public Connector(String protocol) {  boolean aprConnector = AprLifecycleListener.isAprAvailable() &&  AprLifecycleListener.getUseAprConnector();  if ("HTTP/1.1".equals(protocol) || protocol == null) {  if (aprConnector) {  protocolHandlerClassName = "org.apache.coyote.http11.Http11AprProtocol";  } else {  protocolHandlerClassName = "org.apache.coyote.http11.Http11NioProtocol";  }  } else if ("AJP/1.3".equals(protocol)) {  if (aprConnector) {  protocolHandlerClassName = "org.apache.coyote.ajp.AjpAprProtocol";  } else {  protocolHandlerClassName = "org.apache.coyote.ajp.AjpNioProtocol";  }  } else {  protocolHandlerClassName = protocol;  }  // Instantiate protocol handler  ProtocolHandler p = null;  try {  Class<?> clazz = Class.forName(protocolHandlerClassName);  p = (ProtocolHandler) clazz.getConstructor().newInstance();  } catch (Exception e) {  log.error(sm.getString(  "coyoteConnector.protocolHandlerInstantiationFailed"), e);  } finally {  this.protocolHandler = p;  }  // Default for Connector depends on this system property  setThrowOnFailure(Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE"));  }

我們來看看Connector的構(gòu)造方法,其實只做了一件事情,就是根據(jù)協(xié)議設(shè)置對應的ProtocolHandler,根據(jù)名稱我們知道,這是協(xié)議處理類,所以連接器內(nèi)部的一個重要子模塊就是ProtocolHandler。

關(guān)于生命周期

我們看到Connector繼承了LifecycleMBeanBase,我們來看看Connector的最終繼承關(guān)系:

連接器在Tomcat中是怎么設(shè)計的

我們看到最終實現(xiàn)的是Lifecycle接口,我們看看這個接口是何方神圣。我把其接口的注釋拿下來解釋下

/**  * Common interface for component life cycle methods. Catalina components  * may implement this interface (as well as the appropriate interface(s) for  * the functionality they support) in order to provide a consistent mechanism  * to start and stop the component.  * start()  * -----------------------------  * | |  * | init() |  * NEW -&raquo;-- INITIALIZING |  * | | | | ------------------&laquo;-----------------------  * | | |auto | | |  * | | \|/ start() \|/ \|/ auto auto stop() |  * | | INITIALIZED --&raquo;-- STARTING_PREP --&raquo;- STARTING --&raquo;- STARTED --&raquo;--- |  * | | | | |  * | |destroy()| | |  * | --&raquo;-----&laquo;-- ------------------------&laquo;-------------------------------- ^  * | | | |  * | | \|/ auto auto start() |  * | | STOPPING_PREP ----&raquo;---- STOPPING ------&raquo;----- STOPPED -----&raquo;-----  * | \|/ ^ | ^  * | | stop() | | |  * | | -------------------------- | |  * | | | | |  * | | | destroy() destroy() | |  * | | FAILED ----&raquo;------ DESTROYING ---&laquo;----------------- |  * | | ^ | |  * | | destroy() | |auto |  * | --------&raquo;----------------- \|/ |  * | DESTROYED |  * | |  * | stop() |  * ----&raquo;-----------------------------&raquo;------------------------------  *  * Any state can transition to FAILED.  *  * Calling start() while a component is in states STARTING_PREP, STARTING or  * STARTED has no effect.  *  * Calling start() while a component is in state NEW will cause init() to be  * called immediately after the start() method is entered.  *  * Calling stop() while a component is in states STOPPING_PREP, STOPPING or  * STOPPED has no effect.  *  * Calling stop() while a component is in state NEW transitions the component  * to STOPPED. This is typically encountered when a component fails to start and  * does not start all its sub-components. When the component is stopped, it will  * try to stop all sub-components - even those it didn't start.  *  * Attempting any other transition will throw {@link LifecycleException}.  *  * </pre>  * 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.

這段注釋翻譯就是,這個接口是提供給組件聲明周期管理的,并且提供了聲明周期流轉(zhuǎn)圖。這里我們只需要知道正常流程即可:

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

從生命周期探索連接器

根據(jù)上面的生命周期說明,我們可以知道連接器(Connector)就是按照如此的聲明周期管理的,所以我們找到了線索,所以連接器肯定會先初始化然后再啟動。我們查看其initInternal()方法可以知道連接器初始化做了什么事情,源碼如下:

@Override  protected void initInternal() throws LifecycleException {  super.initInternal();  if (protocolHandler == null) {  throw new LifecycleException(  sm.getString("coyoteConnector.protocolHandlerInstantiationFailed"));  }  // Initialize adapter  adapter = new CoyoteAdapter(this);  protocolHandler.setAdapter(adapter);  if (service != null) {  protocolHandler.setUtilityExecutor(service.getServer().getUtilityExecutor());  }  // Make sure parseBodyMethodsSet has a default  if (null == parseBodyMethodsSet) {  setParseBodyMethods(getParseBodyMethods());  }  if (protocolHandler.isAprRequired() && !AprLifecycleListener.isInstanceCreated()) {  throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoAprListener",  getProtocolHandlerClassName()));  }  if (protocolHandler.isAprRequired() && !AprLifecycleListener.isAprAvailable()) {  throw new LifecycleException(sm.getString("coyoteConnector.protocolHandlerNoAprLibrary",  getProtocolHandlerClassName()));  }  if (AprLifecycleListener.isAprAvailable() && AprLifecycleListener.getUseOpenSSL() &&  protocolHandler instanceof AbstractHttp11JsseProtocol) {  AbstractHttp11JsseProtocol<?> jsseProtocolHandler =  (AbstractHttp11JsseProtocol<?>) protocolHandler;  if (jsseProtocolHandler.isSSLEnabled() &&  jsseProtocolHandler.getSslImplementationName() == null) {  // OpenSSL is compatible with the JSSE configuration, so use it if APR is available  jsseProtocolHandler.setSslImplementationName(OpenSSLImplementation.class.getName());  }  }  try {  protocolHandler.init();  } catch (Exception e) {  throw new LifecycleException(  sm.getString("coyoteConnector.protocolHandlerInitializationFailed"), e);  }  } }

根據(jù)上面源碼,我們發(fā)現(xiàn)主要是處理protocolHandler并初始化它,同時我們注意到了protocolHandler  設(shè)置了一個適配器,我們看看這個適配器是做啥的,跟蹤源碼如下:

/**  * The adapter, used to call the connector.  *  * @param adapter The adapter to associate  */  public void setAdapter(Adapter adapter);

這個注釋已經(jīng)說的很直白了,這個適配器就是用來調(diào)用連接器的。我們再繼續(xù)看看protocolHandler的初始化方法

 /**  * Endpoint that provides low-level network I/O - must be matched to the  * ProtocolHandler implementation (ProtocolHandler using NIO, requires NIO  * Endpoint etc.).  */ private final AbstractEndpoint<S,?> endpoint; public void init() throws Exception {  if (getLog().isInfoEnabled()) {  getLog().info(sm.getString("abstractProtocolHandler.init", getName()));  logPortOffset();  }  if (oname == null) {  // Component not pre-registered so register it  oname = createObjectName();  if (oname != null) {  Registry.getRegistry(null, null).registerComponent(this, oname, null);  }  }  if (this.domain != null) {  rgOname = new ObjectName(domain + ":type=GlobalRequestProcessor,name=" + getName());  Registry.getRegistry(null, null).registerComponent(  getHandler().getGlobal(), rgOname, null);  }  String endpointName = getName();  endpoint.setName(endpointName.substring(1, endpointName.length()-1));  endpoint.setDomain(domain);  endpoint.init();  }

這里出現(xiàn)了一個新的對象,endpoint,根據(jù)注釋我們可以知道endpoint是用來處理網(wǎng)絡(luò)IO的,而且必須匹配到指定的子類(比如Nio,就是NioEndPoint處理)。endpoint.init()實際上就是做一些網(wǎng)絡(luò)的配置,然后就是初始化完畢了。根據(jù)我們上面的周期管理,我們知道init()后就是start(),所以我們查看Connector的start()源碼:

protected void startInternal() throws LifecycleException { // Validate settings before starting if (getPortWithOffset() < 0) { throw new LifecycleException(sm.getString( "coyoteConnector.invalidPort", Integer.valueOf(getPortWithOffset()))); } setState(LifecycleState.STARTING); try { protocolHandler.start(); } catch (Exception e) { throw new LifecycleException( sm.getString("coyoteConnector.protocolHandlerStartFailed"), e); } }

其實就是主要調(diào)用 protocolHandler.start()方法,繼續(xù)跟蹤,為了方便表述,我會把接下來的代碼統(tǒng)一放在一起說明,代碼如下:

//1.類:AbstractProtocol implements ProtocolHandler,  MBeanRegistration  public void start() throws Exception {  // 省略部分代碼  endpoint.start();  } //2. 類:AbstractEndPoint  public final void start() throws Exception {  // 省略部分代碼  startInternal();  }  /**3.類:NioEndPoint extends AbstractJsseEndpoint<NioChannel,SocketChannel>  * Start the NIO endpoint, creating acceptor, poller threads.  */  @Override  public void startInternal() throws Exception {  //省略部分代碼    // Start poller thread  poller = new Poller();  Thread pollerThread = new Thread(poller, getName() + "-ClientPoller");  pollerThread.setPriority(threadPriority);  pollerThread.setDaemon(true);  pollerThread.start();  startAcceptorThread();  }  }

到這里,其實整個啟動代碼就完成了,我們看到最后是在NioEndPoint創(chuàng)建了一個Poller,并且啟動它,這里需要補充說明下,這里只是以NioEndPoint為示列,其實Tomcat  主要提供了三種實現(xiàn),分別是AprEndPoint,NioEndPoint,Nio2EndPoint,這里表示了tomcat支持的I/O模型:

  • APR:采用 Apache 可移植運行庫實現(xiàn),它根據(jù)不同操作系統(tǒng),分別用c重寫了大部分IO和系統(tǒng)線程操作模塊,據(jù)說性能要比其他模式要好(未實測)。

  • NIO:非阻塞 I/O

  • NIO.2:異步 I/O

上述代碼主要是開啟兩個線程,一個是Poller,一個是開啟Acceptor,既然是線程,核心的代碼肯定是run方法,我們來查看源碼,代碼如下:

//4.類:Acceptor<U> implements Runnable  public void run() {  //省略了部分代碼  U socket = null;  socket = endpoint.serverSocketAccept();  // Configure the socket  if (endpoint.isRunning() && !endpoint.isPaused()) {  // setSocketOptions() will hand the socket off to  // an appropriate processor if successful  //核心邏輯  if (!endpoint.setSocketOptions(socket)) {  endpoint.closeSocket(socket);  }  } else {  endpoint.destroySocket(socket);  }    state = AcceptorState.ENDED; } //5.類:NioEndpoint protected boolean setSocketOptions(SocketChannel socket) {  // Process the connection  //省略部分代碼  try {  // Disable blocking, polling will be used  socket.configureBlocking(false);  Socket sock = socket.socket();  socketProperties.setProperties(sock);  NioSocketWrapper socketWrapper = new NioSocketWrapper(channel, this);  channel.setSocketWrapper(socketWrapper);  socketWrapper.setReadTimeout(getConnectionTimeout());  socketWrapper.setWriteTimeout(getConnectionTimeout());  socketWrapper.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests());  socketWrapper.setSecure(isSSLEnabled());  //核心邏輯  poller.register(channel, socketWrapper);  return true;    }

這里可以發(fā)現(xiàn)Acceptor主要就是接受socket,然后把它注冊到poller中,我們繼續(xù)看看是如何注冊的。

/**6.類NioEndpoint  * Registers a newly created socket with the poller.  *  * @param socket The newly created socket  * @param socketWrapper The socket wrapper  */  public void register(final NioChannel socket, final NioSocketWrapper socketWrapper) {  socketWrapper.interestOps(SelectionKey.OP_READ);//this is what OP_REGISTER turns into.  PollerEvent r = null;  if (eventCache != null) {  r = eventCache.pop();  }  if (r == null) {  r = new PollerEvent(socket, OP_REGISTER);  } else {  r.reset(socket, OP_REGISTER);  }  addEvent(r);  } /** 7.類:PollerEvent implements Runnable  public void run() {  //省略部分代碼  socket.getIOChannel().register(socket.getSocketWrapper().getPoller().getSelector(), SelectionKey.OP_READ, socket.getSocketWrapper());  }

這里發(fā)現(xiàn)最終就是采用NIO模型把其注冊到通道中。(這里涉及NIO網(wǎng)絡(luò)編程知識,不了解的同學可以傳送這里)。那么注冊完畢后,我們看看Poller做了什么事情。

*/   /**8.類:NioEndPoint內(nèi)部類 Poller implements Runnable  **/   @Override  public void run() {  // Loop until destroy() is called  while (true) {  //省略部分代碼  Iterator<SelectionKey> iterator =  keyCount > 0 ? selector.selectedKeys().iterator() : null;  // Walk through the collection of ready keys and dispatch  // any active event.  while (iterator != null && iterator.hasNext()) {  SelectionKey sk = iterator.next();  NioSocketWrapper socketWrapper = (NioSocketWrapper) sk.attachment();  // Attachment may be null if another thread has called  // cancelledKey()  if (socketWrapper == null) {  iterator.remove();  } else {  iterator.remove();  //sock處理  processKey(sk, socketWrapper);  }  }  //省略部分代碼  }

這個就是通過selector把之前注冊的事件取出來,從而完成了調(diào)用。

//9.類: NioEndPoint內(nèi)部類 Poller implements Runnable  protected void processKey(SelectionKey sk, NioSocketWrapper socketWrapper) {  //省略大部分代碼  processSocket(socketWrapper, SocketEvent.OPEN_WRITE, true)   }   //10.類:AbstractEndPoint  public boolean processSocket(SocketWrapperBase<S> socketWrapper,  SocketEvent event, boolean dispatch) {  //省略部分代碼  Executor executor = getExecutor();  if (dispatch && executor != null) {  executor.execute(sc);  } else {  sc.run();  }    return true;  }  //11.類:SocketProcessorBase implements Runnable  public final void run() {  synchronized (socketWrapper) {  // It is possible that processing may be triggered for read and  // write at the same time. The sync above makes sure that processing  // does not occur in parallel. The test below ensures that if the  // first event to be processed results in the socket being closed,  // the subsequent events are not processed.  if (socketWrapper.isClosed()) {  return;  }  doRun();  }  }   //類:12.NioEndPoint extends AbstractJsseEndpoint<NioChannel,SocketChannel>  protected void doRun() {  //省略部分代碼  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(key, socketWrapper);  }  }  }

Poller調(diào)用的run方法或者用Executor線程池去執(zhí)行run(),最終調(diào)用都是各個子EndPoint中的doRun()方法,最終會取一個Handler去處理socketWrapper。繼續(xù)看源碼:

//類:13.AbstractProtocol內(nèi)部類ConnectionHandler implements AbstractEndpoint.Handler<S>  public SocketState process(SocketWrapperBase<S> wrapper, SocketEvent status) {  //省略部分代碼    state = processor.process(wrapper, status);    return SocketState.CLOSED;  }   //類:14.AbstractProcessorLight implements Processor  public SocketState process(SocketWrapperBase<?> socketWrapper, SocketEvent status)  throws IOException {  //省略部分代碼    state = service(socketWrapper);    return state;  }

這部分源碼表明最終調(diào)用的process是通過一個Processor接口的實現(xiàn)類來完成的,這里最終也是會調(diào)用到各個子類中,那么這里的處理器其實就是處理應用協(xié)議,我們可以查看AbstractProcessorLight的實現(xiàn)類,分別有AjpProcessor、Http11Processor、StreamProcessor,分別代表tomcat支持三種應用層協(xié)議,分別是:

  • AJP協(xié)議

  • HTTP.1協(xié)議

  • HTTP2.0協(xié)議

這里我們以常用的HTTP1.1為例,繼續(xù)看源碼:

//類:15. Http11Processor extends AbstractProcessor public SocketState service(SocketWrapperBase<?> socketWrapper)  throws IOException {  //省略大部分代碼  getAdapter().service(request, response);  //省略大部分代碼   }  //類:16 CoyoteAdapter implements Adapter public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)  throws Exception {  Request request = (Request) req.getNote(ADAPTER_NOTES);  Response response = (Response) res.getNote(ADAPTER_NOTES);  postParseSuccess = postParseRequest(req, request, res, response);  if (postParseSuccess) {  //check valves if we support async  request.setAsyncSupported(  connector.getService().getContainer().getPipeline().isAsyncSupported());  // Calling the container  connector.getService().getContainer().getPipeline().getFirst().invoke(  request, response);  }    }

這里我們發(fā)現(xiàn)協(xié)議處理器最終會調(diào)用適配器(CoyoteAdapter),而適配器最終的工作是轉(zhuǎn)換Request和Response對象為HttpServletRequest和HttpServletResponse,從而可以去調(diào)用容器,到這里整個連接器的流程和作用我們就已經(jīng)分析完了。

小結(jié)

那么我們來回憶下整個流程,我畫了一張時序圖來說明:

連接器在Tomcat中是怎么設(shè)計的

這張圖包含了兩個流程,一個是組件的初始化,一個是調(diào)用的流程。連接器(Connector)主要初始化了兩個組件,ProtcoHandler和EndPoint,但是我們從代碼結(jié)構(gòu)發(fā)現(xiàn),他們兩個是父子關(guān)系,也就是說ProtcoHandler包含了EndPoint。后面的流程就是各個子組件的調(diào)用鏈關(guān)系,總結(jié)來說就是Acceptor負責接收請求,然后注冊到Poller,Poller負責處理請求,然后調(diào)用processor處理器來處理,最后把請求轉(zhuǎn)成符合Servlet規(guī)范的request和response去調(diào)用容器(Container)。點擊免費“領(lǐng)取Java架構(gòu)資料”

我們流程梳理清楚了,接下來我們來結(jié)構(gòu)化的梳理下:

回到連接器(Connector)是源碼,我們發(fā)現(xiàn),上述說的模塊只有ProtocolHandler和Adapter兩個屬于連接器中,也就是說,連接器只包含了這兩大子模塊,那么后續(xù)的EndPoint、Acceptor、Poller、Processor都是ProtocolHandler的子模塊。  而Acceptor和Poller兩個模塊的核心功能都是在EndPoint  中完成的,所以是其子模塊,而Processor比較獨立,所以它和EndPoint是一個級別的子模塊。

我們用圖來說明下上述的關(guān)系:

連接器在Tomcat中是怎么設(shè)計的

根據(jù)上圖我們可以知道,連接器主要負責處理連接請求,然后通過適配器調(diào)用容器。那么具體流程細化可以如下:

  • Acceptor監(jiān)聽網(wǎng)絡(luò)請求,獲取請求。

  • Poller獲取到監(jiān)聽的請求提交線程池進行處理。

  • Processor根據(jù)具體的應用協(xié)議(HTTP/AJP)來生成Tomcat Request對象。

  • Adapter把Request對象轉(zhuǎn)換成Servlet標準的Request對象,調(diào)用容器。

關(guān)于連接器在Tomcat中是怎么設(shè)計的就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

當前標題:連接器在Tomcat中是怎么設(shè)計的
文章URL:http://bm7419.com/article28/igchcp.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站設(shè)計公司、網(wǎng)站排名服務(wù)器托管、App開發(fā)網(wǎng)站改版、網(wǎng)站內(nèi)鏈

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

成都seo排名網(wǎng)站優(yōu)化