成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專(zhuān)欄INFORMATION COLUMN

Android網(wǎng)絡(luò)編程7之源碼解析OkHttp前篇[請(qǐng)求網(wǎng)絡(luò)]

blastz / 617人閱讀

摘要:異步請(qǐng)求當(dāng)正在運(yùn)行的異步請(qǐng)求隊(duì)列中的數(shù)量小于并且正在運(yùn)行的請(qǐng)求主機(jī)數(shù)小于時(shí)則把請(qǐng)求加載到中并在線(xiàn)程池中執(zhí)行,否則就再入到中進(jìn)行緩存等待。通常情況下攔截器用來(lái)添加,移除或者轉(zhuǎn)換請(qǐng)求或者響應(yīng)的頭部信息。

前言

學(xué)會(huì)了OkHttp3的用法后,我們當(dāng)然有必要來(lái)了解下OkHttp3的源碼,當(dāng)然現(xiàn)在網(wǎng)上的文章很多,我仍舊希望我這一系列文章篇是最簡(jiǎn)潔易懂的。

1.從請(qǐng)求處理開(kāi)始分析

首先OKHttp3如何使用這里就不在贅述了,不明白的同學(xué)可以查看Android網(wǎng)絡(luò)編程(五)OkHttp2.x用法全解析、
Android網(wǎng)絡(luò)編程(六)OkHttp3用法全解析這兩篇文章。當(dāng)我們要請(qǐng)求網(wǎng)絡(luò)的時(shí)候我們需要用OkHttpClient.newCall(request)進(jìn)行execute或者enqueue操作,當(dāng)我們調(diào)用newCall時(shí):

  @Override public Call newCall(Request request) {
    return new RealCall(this, request);
  }

實(shí)際返回的是一個(gè)RealCall類(lèi),我們調(diào)用enqueue異步請(qǐng)求網(wǎng)絡(luò)實(shí)際上是調(diào)用了RealCall的enqueue方法:

  void enqueue(Callback responseCallback, boolean forWebSocket) {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    client.dispatcher().enqueue(new AsyncCall(responseCallback, forWebSocket));
  }

可以看到最終的請(qǐng)求是dispatcher來(lái)完成的。

2.Dispatcher任務(wù)調(diào)度 主要的變量

Dispatcher主要用于控制并發(fā)的請(qǐng)求,它主要維護(hù)了以下變量:

  /** 最大并發(fā)請(qǐng)求數(shù)*/
  private int maxRequests = 64;
  /** 每個(gè)主機(jī)最大請(qǐng)求數(shù)*/
  private int maxRequestsPerHost = 5;
  /** 消費(fèi)者線(xiàn)程池 */
  private ExecutorService executorService;
  /** 將要運(yùn)行的異步請(qǐng)求隊(duì)列 */
  private final Deque readyAsyncCalls = new ArrayDeque<>();
  /**正在運(yùn)行的異步請(qǐng)求隊(duì)列 */
  private final Deque runningAsyncCalls = new ArrayDeque<>();
  /** 正在運(yùn)行的同步請(qǐng)求隊(duì)列 */
  private final Deque runningSyncCalls = new ArrayDeque<>();
構(gòu)造函數(shù)
 public Dispatcher(ExecutorService executorService) {
    this.executorService = executorService;
  }

  public Dispatcher() {
  }

  public synchronized ExecutorService executorService() {
    if (executorService == null) {
      executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
          new SynchronousQueue(), Util.threadFactory("OkHttp Dispatcher", false));
    }
    return executorService;
  }

Dispatcher有兩個(gè)構(gòu)造函數(shù),可以使用自己設(shè)定線(xiàn)程池,如果沒(méi)有設(shè)定線(xiàn)程池則會(huì)在請(qǐng)求網(wǎng)絡(luò)前自己創(chuàng)建線(xiàn)程池,這個(gè)線(xiàn)程池類(lèi)似于CachedThreadPool比較適合執(zhí)行大量的耗時(shí)比較少的任務(wù)。不了解線(xiàn)程池的同學(xué)可以查看Android多線(xiàn)程(一)線(xiàn)程池這篇文章。其中用到了SynchronousQueue,不了解它的同學(xué)可以查看Java并發(fā)編程(六)阻塞隊(duì)列這篇文章。

異步請(qǐng)求
  synchronized void enqueue(AsyncCall call) {
    if (runningAsyncCalls.size() < maxRequests && runningCallsForHost(call) < maxRequestsPerHost) {
      runningAsyncCalls.add(call);
      executorService().execute(call);
    } else {
      readyAsyncCalls.add(call);
    }
  }

當(dāng)正在運(yùn)行的異步請(qǐng)求隊(duì)列中的數(shù)量小于64并且正在運(yùn)行的請(qǐng)求主機(jī)數(shù)小于5時(shí)則把請(qǐng)求加載到runningAsyncCalls中并在線(xiàn)程池中執(zhí)行,否則就再入到readyAsyncCalls中進(jìn)行緩存等待。

AsyncCall

線(xiàn)程池中傳進(jìn)來(lái)的參數(shù)就是AsyncCall它是RealCall的內(nèi)部類(lèi),內(nèi)部也實(shí)現(xiàn)了execute方法:

    @Override protected void execute() {
      boolean signalledCallback = false;
      try {
        Response response = getResponseWithInterceptorChain(forWebSocket);
        if (canceled) {
          signalledCallback = true;
          responseCallback.onFailure(RealCall.this, new IOException("Canceled"));
        } else {
          signalledCallback = true;
          responseCallback.onResponse(RealCall.this, response);
        }
      } catch (IOException e) {
        if (signalledCallback) {
          // Do not signal the callback twice!
          logger.log(Level.INFO, "Callback failure for " + toLoggableString(), e);
        } else {
          responseCallback.onFailure(RealCall.this, e);
        }
      } finally {
        client.dispatcher().finished(this);
      }
    }

首先我們來(lái)看看最后一行, 無(wú)論這個(gè)請(qǐng)求的結(jié)果如何都會(huì)執(zhí)行client.dispatcher().finished(this);

 synchronized void finished(AsyncCall call) {
    if (!runningAsyncCalls.remove(call)) throw new AssertionError("AsyncCall wasn"t running!");
    promoteCalls();
  }

finished方法將此次請(qǐng)求從runningAsyncCalls移除后還執(zhí)行了promoteCalls方法:

  private void promoteCalls() {
    if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity.
    if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.

    for (Iterator i = readyAsyncCalls.iterator(); i.hasNext(); ) {
      AsyncCall call = i.next();

      if (runningCallsForHost(call) < maxRequestsPerHost) {
        i.remove();
        runningAsyncCalls.add(call);
        executorService().execute(call);
      }

      if (runningAsyncCalls.size() >= maxRequests) return; // Reached max capacity.
    }
  }

可以看到最關(guān)鍵的點(diǎn)就是會(huì)從readyAsyncCalls取出下一個(gè)請(qǐng)求,并加入runningAsyncCalls中并交由線(xiàn)程池處理。好了讓我們?cè)倩氐缴厦娴腁syncCall的execute方法,我們會(huì)發(fā)getResponseWithInterceptorChain方法返回了Response,很明顯這是在請(qǐng)求網(wǎng)絡(luò)。

3.Interceptor攔截器
  private Response getResponseWithInterceptorChain(boolean forWebSocket) throws IOException {
    Interceptor.Chain chain = new ApplicationInterceptorChain(0, originalRequest, forWebSocket);
    return chain.proceed(originalRequest);
  }

getResponseWithInterceptorChain方法,創(chuàng)建了ApplicationInterceptorChain,它是一個(gè)攔截器鏈,這個(gè)類(lèi)也是RealCall的內(nèi)部類(lèi),接下來(lái)執(zhí)行了它的proceed方法:

    @Override public Response proceed(Request request) throws IOException {
      // If there"s another interceptor in the chain, call that.
      if (index < client.interceptors().size()) {
        Interceptor.Chain chain = new ApplicationInterceptorChain(index + 1, request, forWebSocket);
        //從攔截器列表取出攔截器
        Interceptor interceptor = client.interceptors().get(index);
        Response interceptedResponse = interceptor.intercept(chain);

        if (interceptedResponse == null) {
          throw new NullPointerException("application interceptor " + interceptor
              + " returned null");
        }

        return interceptedResponse;
      }

      // No more interceptors. Do HTTP.
      return getResponse(request, forWebSocket);
    }

proceed方法每次從攔截器列表中取出攔截器,當(dāng)存在多個(gè)攔截器時(shí)都會(huì)在第七行阻塞,并等待下一個(gè)攔截器的調(diào)用返回。下面分別以 攔截器鏈中有1個(gè)、2個(gè)攔截器的場(chǎng)景加以模擬:

攔截器主要用來(lái)觀(guān)察,修改以及可能短路的請(qǐng)求輸出和響應(yīng)的回來(lái)。通常情況下攔截器用來(lái)添加,移除或者轉(zhuǎn)換請(qǐng)求或者響應(yīng)的頭部信息。比如將域名替換為ip地址,將請(qǐng)求頭中添加host屬性,也可以添加我們應(yīng)用中的一些公共參數(shù),比如設(shè)備id、版本號(hào)等等。 不了解攔截器的可以查看Okhttp-wiki 之 Interceptors 攔截器這篇文章。
回到代碼上來(lái),我們看最后一行 return getResponse(request, forWebSocket),如果沒(méi)有更多的攔截器的話(huà),就會(huì)執(zhí)行網(wǎng)絡(luò)請(qǐng)求,來(lái)看看getResponse方法做了些什么(RealCall.java):

Response getResponse(Request request, boolean forWebSocket) throws IOException {
 ...省略
    // Create the initial HTTP engine. Retries and redirects need new engine for each attempt.
    engine = new HttpEngine(client, request, false, false, forWebSocket, null, null, null);

    int followUpCount = 0;
    while (true) {
      if (canceled) {
        engine.releaseStreamAllocation();
        throw new IOException("Canceled");
      }

      boolean releaseConnection = true;
      try {
        engine.sendRequest();
        engine.readResponse();
        releaseConnection = false;
      } catch (RequestException e) {
        // The attempt to interpret the request failed. Give up.
        throw e.getCause();
      } catch (RouteException e) {
        // The attempt to connect via a route failed. The request will not have been sent.
  ...省略     
    }
  }

getResponse方法比較長(zhǎng)我省略了一些代碼,可以看到創(chuàng)建了HttpEngine類(lèi)并且調(diào)用HttpEngine的sendRequest方法和readResponse方法。

4.緩存策略

我們先來(lái)看看sendRequest方法:

 public void sendRequest() throws RequestException, RouteException, IOException {
    if (cacheStrategy != null) return; // Already sent.
    if (httpStream != null) throw new IllegalStateException();
    //請(qǐng)求頭部添加
    Request request = networkRequest(userRequest);
    //獲取client中的Cache,同時(shí)Cache在初始化的時(shí)候會(huì)去讀取緩存目錄中關(guān)于曾經(jīng)請(qǐng)求過(guò)的所有信息。
    InternalCache responseCache = Internal.instance.internalCache(client);
    //cacheCandidate為上次與服務(wù)器交互緩存的Response
    Response cacheCandidate = responseCache != null
        ? responseCache.get(request)
        : null;

    long now = System.currentTimeMillis();

    //創(chuàng)建CacheStrategy.Factory對(duì)象,進(jìn)行緩存配置
    cacheStrategy = new CacheStrategy.Factory(now, request, cacheCandidate).get();
    //網(wǎng)絡(luò)請(qǐng)求
    networkRequest = cacheStrategy.networkRequest;
    //緩存的響應(yīng)
    cacheResponse = cacheStrategy.cacheResponse;

    if (responseCache != null) {
     //記錄當(dāng)前請(qǐng)求是網(wǎng)絡(luò)發(fā)起還是緩存發(fā)起
      responseCache.trackResponse(cacheStrategy);
    }

    if (cacheCandidate != null && cacheResponse == null) {
      closeQuietly(cacheCandidate.body()); // The cache candidate wasn"t applicable. Close it.
    }

    //不進(jìn)行網(wǎng)絡(luò)請(qǐng)求并且緩存不存在或者過(guò)期則返回504錯(cuò)誤
    if (networkRequest == null && cacheResponse == null) {
      userResponse = new Response.Builder()
          .request(userRequest)
          .priorResponse(stripBody(priorResponse))
          .protocol(Protocol.HTTP_1_1)
          .code(504)
          .message("Unsatisfiable Request (only-if-cached)")
          .body(EMPTY_BODY)
          .build();
      return;
    }

    // 不進(jìn)行網(wǎng)絡(luò)請(qǐng)求,而且緩存可以使用,直接返回緩存
    if (networkRequest == null) {
      userResponse = cacheResponse.newBuilder()
          .request(userRequest)
          .priorResponse(stripBody(priorResponse))
          .cacheResponse(stripBody(cacheResponse))
          .build();
      userResponse = unzip(userResponse);
      return;
    }

    //需要訪(fǎng)問(wèn)網(wǎng)絡(luò)時(shí)
    boolean success = false;
    try {
      httpStream = connect();
      httpStream.setHttpEngine(this);

      if (writeRequestHeadersEagerly()) {
        long contentLength = OkHeaders.contentLength(request);
        if (bufferRequestBody) {
          if (contentLength > Integer.MAX_VALUE) {
            throw new IllegalStateException("Use setFixedLengthStreamingMode() or "
                + "setChunkedStreamingMode() for requests larger than 2 GiB.");
          }

          if (contentLength != -1) {
            // Buffer a request body of a known length.
            httpStream.writeRequestHeaders(networkRequest);
            requestBodyOut = new RetryableSink((int) contentLength);
          } else {
            // Buffer a request body of an unknown length. Don"t write request headers until the
            // entire body is ready; otherwise we can"t set the Content-Length header correctly.
            requestBodyOut = new RetryableSink();
          }
        } else {
          httpStream.writeRequestHeaders(networkRequest);
          requestBodyOut = httpStream.createRequestBody(networkRequest, contentLength);
        }
      }
      success = true;
    } finally {
      // If we"re crashing on I/O or otherwise, don"t leak the cache body.
      if (!success && cacheCandidate != null) {
        closeQuietly(cacheCandidate.body());
      }
    }
  }

上面的代碼顯然是在發(fā)送請(qǐng)求,但是最主要的是做了緩存的策略。cacheCandidate是上次與服務(wù)器交互緩存的Response,這里的緩存都是基于Map,key是請(qǐng)求中url的md5,value是在文件中查詢(xún)到的緩存,頁(yè)面置換基于LRU算法,我們現(xiàn)在只需要知道它是一個(gè)可以讀取緩存Header的Response即可。根據(jù)cacheStrategy的處理得到了networkRequest和cacheResponse這兩個(gè)值,根據(jù)這兩個(gè)值的數(shù)據(jù)是否為null來(lái)進(jìn)行進(jìn)一步的處理,當(dāng)networkRequest和cacheResponse都為null的情況也就是不進(jìn)行網(wǎng)絡(luò)請(qǐng)求并且緩存不存在或者過(guò)期,這時(shí)候則返回504錯(cuò)誤;當(dāng)networkRequest 為null時(shí)也就是不進(jìn)行網(wǎng)絡(luò)請(qǐng)求,而且緩存可以使用時(shí)則直接返回緩存;其他的情況則請(qǐng)求網(wǎng)絡(luò)。
接下來(lái)我們查看readResponse方法:

  public void readResponse() throws IOException {
    ...省略
    else{
      //讀取網(wǎng)絡(luò)響應(yīng)
      networkResponse = readNetworkResponse();
    }
    //將響應(yīng)頭部存入Cookie中
    receiveHeaders(networkResponse.headers());

    // If we have a cache response too, then we"re doing a conditional get.
    if (cacheResponse != null) {
    //檢查緩存是否可用,如果可用。那么就用當(dāng)前緩存的Response,關(guān)閉網(wǎng)絡(luò)連接,釋放連接。
      if (validate(cacheResponse, networkResponse)) {
        userResponse = cacheResponse.newBuilder()
            .request(userRequest)
            .priorResponse(stripBody(priorResponse))
            .headers(combine(cacheResponse.headers(), networkResponse.headers()))
            .cacheResponse(stripBody(cacheResponse))
            .networkResponse(stripBody(networkResponse))
            .build();
        networkResponse.body().close();
        releaseStreamAllocation();

        // Update the cache after combining headers but before stripping the
        // Content-Encoding header (as performed by initContentStream()).
        InternalCache responseCache = Internal.instance.internalCache(client);
        responseCache.trackConditionalCacheHit();
        // 更新緩存
        responseCache.update(cacheResponse, stripBody(userResponse));
        userResponse = unzip(userResponse);
        return;
      } else {
        closeQuietly(cacheResponse.body());
      }
    }

    userResponse = networkResponse.newBuilder()
        .request(userRequest)
        .priorResponse(stripBody(priorResponse))
        .cacheResponse(stripBody(cacheResponse))
        .networkResponse(stripBody(networkResponse))
        .build();

    if (hasBody(userResponse)) {
      maybeCache();
      userResponse = unzip(cacheWritingResponse(storeRequest, userResponse));
    }
  }

這個(gè)方法發(fā)起刷新請(qǐng)求頭部和請(qǐng)求體,解析HTTP響應(yīng)頭部。如果有緩存并且可用則用緩存的數(shù)據(jù)并更新緩存,否則就用網(wǎng)絡(luò)請(qǐng)求返回的數(shù)據(jù)。
我們?cè)賮?lái)看看validate(cacheResponse, networkResponse)方法是如何判斷緩存是否可用的:

  private static boolean validate(Response cached, Response network) {
  //如果服務(wù)器返回304則緩存有效
    if (network.code() == HTTP_NOT_MODIFIED) {
      return true;
    }
   //通過(guò)緩存和網(wǎng)絡(luò)請(qǐng)求響應(yīng)中的Last-Modified來(lái)計(jì)算是否是最新數(shù)據(jù),如果是則緩存有效
    Date lastModified = cached.headers().getDate("Last-Modified");
    if (lastModified != null) {
      Date networkLastModified = network.headers().getDate("Last-Modified");
      if (networkLastModified != null
          && networkLastModified.getTime() < lastModified.getTime()) {
        return true;
      }
    }
    return false;
  }

如緩存果過(guò)期或者強(qiáng)制放棄緩存,在此情況下,緩存策略全部交給服務(wù)器判斷,客戶(hù)端只用發(fā)送條件get請(qǐng)求即可,如果緩存是有效的,則返回304 Not Modifiled,否則直接返回body。條件get請(qǐng)求有兩種方式一種是Last-Modified-Date,一種是 ETag。這里采用了Last-Modified-Date,通過(guò)緩存和網(wǎng)絡(luò)請(qǐng)求響應(yīng)中的Last-Modified來(lái)計(jì)算是否是最新數(shù)據(jù),如果是則緩存有效。

5.失敗重連

最后我們?cè)倩氐絉ealCall的getResponse方法:

  Response getResponse(Request request, boolean forWebSocket) throws IOException {
  ...省略
      boolean releaseConnection = true;
      try {
        engine.sendRequest();
        engine.readResponse();
        releaseConnection = false;
      } catch (RequestException e) {
        // The attempt to interpret the request failed. Give up.
        throw e.getCause();
      } catch (RouteException e) {
        // The attempt to connect via a route failed. The request will not have been sent.
        HttpEngine retryEngine = engine.recover(e.getLastConnectException(), null);
        if (retryEngine != null) {
          releaseConnection = false;
          engine = retryEngine;
          continue;
        }
        // Give up; recovery is not possible.
        throw e.getLastConnectException();
      } catch (IOException e) {
        // An attempt to communicate with a server failed. The request may have been sent.
        HttpEngine retryEngine = engine.recover(e, null);
        if (retryEngine != null) {
          releaseConnection = false;
          engine = retryEngine;
          continue;
        }

        // Give up; recovery is not possible.
        throw e;
      } finally {
        // We"re throwing an unchecked exception. Release any resources.
        if (releaseConnection) {
          StreamAllocation streamAllocation = engine.close();
          streamAllocation.release();
        }
      }
     ...省略
      engine = new HttpEngine(client, request, false, false, forWebSocket, streamAllocation, null,
          response);
    }
  }

查看代碼第11行和21行當(dāng)發(fā)生IOException或者RouteException時(shí)會(huì)執(zhí)行HttpEngine的recover方法:

  public HttpEngine recover(IOException e, Sink requestBodyOut) {
    if (!streamAllocation.recover(e, requestBodyOut)) {
      return null;
    }

    if (!client.retryOnConnectionFailure()) {
      return null;
    }

    StreamAllocation streamAllocation = close();

    // For failure recovery, use the same route selector with a new connection.
    return new HttpEngine(client, userRequest, bufferRequestBody, callerWritesRequestBody,
        forWebSocket, streamAllocation, (RetryableSink) requestBodyOut, priorResponse);
  }

最后一行可以看到就是重新創(chuàng)建了HttpEngine并返回,用來(lái)完成重連。
到這里OkHttp請(qǐng)求網(wǎng)絡(luò)的流程基本上講完了,下面是關(guān)于OKHttp的請(qǐng)求流程圖:

參考資料:
http://www.jianshu.com/p/aad5aacd79bf
http://www.jianshu.com/p/64e256c1dbbf
http://www.cnblogs.com/LuLei1990/p/5534791.html
http://frodoking.github.io/2015/03/12/android-okhttp/

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/70587.html

相關(guān)文章

  • Android網(wǎng)絡(luò)編程9Retrofit2前篇[基本使用]

    摘要:創(chuàng)建增加返回值為的支持這里的加上之前定義的參數(shù)形成完整的請(qǐng)求地址用于指定返回的參數(shù)數(shù)據(jù)類(lèi)型,這里我們支持和類(lèi)型。完整的代碼如下增加返回值為的支持請(qǐng)求參數(shù)上文講了訪(fǎng)問(wèn)網(wǎng)絡(luò)的基本方法,接下來(lái)我們來(lái)了解下常用的請(qǐng)求參數(shù)。 前言 Retrofit是Square公司開(kāi)發(fā)的一款針對(duì)Android網(wǎng)絡(luò)請(qǐng)求的框架,Retrofit2底層基于OkHttp實(shí)現(xiàn)的,而OkHttp現(xiàn)在已經(jīng)得到Google官方...

    Nosee 評(píng)論0 收藏0
  • Android網(wǎng)絡(luò)編程6OkHttp3用法全解析

    摘要:使用前準(zhǔn)備配置添加網(wǎng)絡(luò)權(quán)限異步請(qǐng)求慣例,請(qǐng)求百度可以省略,默認(rèn)是請(qǐng)求請(qǐng)求成功與版本并沒(méi)有什么不同,比較郁悶的是回調(diào)仍然不在線(xiàn)程。 前言 上一篇介紹了OkHttp2.x的用法,這一篇文章我們來(lái)對(duì)照OkHttp2.x版本來(lái)看看,OkHttp3使用起來(lái)有那些變化。當(dāng)然,看這篇文章前建議看一下前一篇文章Android網(wǎng)絡(luò)編程(五)OkHttp2.x用法全解析。 1.使用前準(zhǔn)備 Android ...

    Amos 評(píng)論0 收藏0
  • Android網(wǎng)絡(luò)編程5OkHttp2.x用法全解析

    摘要:需要注意的是回調(diào)并不是在線(xiàn)程。也可以通過(guò)來(lái)同時(shí)取消多個(gè)請(qǐng)求。在開(kāi)始創(chuàng)建的時(shí)候配置好,在請(qǐng)求網(wǎng)絡(luò)的時(shí)候用將請(qǐng)求的結(jié)果回調(diào)給線(xiàn)程。最后調(diào)用這個(gè)的方法請(qǐng)求成功使用起來(lái)簡(jiǎn)單多了,而且請(qǐng)求結(jié)果回調(diào)是在線(xiàn)程的。 前言 講完了Volley,我們接下來(lái)看看目前比較火的網(wǎng)絡(luò)框架OkHttp, 它處理了很多網(wǎng)絡(luò)疑難雜癥:會(huì)從很多常用的連接問(wèn)題中自動(dòng)恢復(fù)。如果您的服務(wù)器配置了多個(gè)IP地址,當(dāng)?shù)谝粋€(gè)IP連接失...

    wemall 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<