国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

Android網(wǎng)絡(luò)編程8之源碼解析OkHttp中篇[復(fù)用連接池]

fasss / 683人閱讀

摘要:構(gòu)造函數(shù)默認(rèn)空閑的最大連接數(shù)為個(gè),的時(shí)間為秒通過構(gòu)造函數(shù)可以看出默認(rèn)的空閑的最大連接數(shù)為個(gè),的時(shí)間為秒。實(shí)例化實(shí)例化是在實(shí)例化時(shí)進(jìn)行的在的構(gòu)造函數(shù)中調(diào)用了省略省略緩存操作提供對(duì)進(jìn)行操作的方法分別為和幾個(gè)操作。

1.引子

在了解OkHttp的復(fù)用連接池之前,我們首先要了解幾個(gè)概念。

TCP三次握手

通常我們進(jìn)行HTTP連接網(wǎng)絡(luò)的時(shí)候我們會(huì)進(jìn)行TCP的三次握手,然后傳輸數(shù)據(jù),然后再釋放連接。

TCP三次握手的過程為:

第一次握手:建立連接。客戶端發(fā)送連接請(qǐng)求報(bào)文段,將SYN位置為1,Sequence Number為x;然后,客戶端進(jìn)入SYN_SEND狀態(tài),等待服務(wù)器的確認(rèn);

第二次握手:服務(wù)器收到客戶端的SYN報(bào)文段,需要對(duì)這個(gè)SYN報(bào)文段進(jìn)行確認(rèn),設(shè)置Acknowledgment Number為x+1(Sequence Number+1);同時(shí),自己自己還要發(fā)送SYN請(qǐng)求信息,將SYN位置為1,Sequence Number為y;服務(wù)器端將上述所有信息放到一個(gè)報(bào)文段(即SYN+ACK報(bào)文段)中,一并發(fā)送給客戶端,此時(shí)服務(wù)器進(jìn)入SYN_RECV狀態(tài);

第三次握手:客戶端收到服務(wù)器的SYN+ACK報(bào)文段。然后將Acknowledgment Number設(shè)置為y+1,向服務(wù)器發(fā)送ACK報(bào)文段,這個(gè)報(bào)文段發(fā)送完畢以后,客戶端和服務(wù)器端都進(jìn)入ESTABLISHED狀態(tài),完成TCP三次握手。

TCP四次分手

當(dāng)客戶端和服務(wù)器通過三次握手建立了TCP連接以后,當(dāng)數(shù)據(jù)傳送完畢,斷開連接就需要進(jìn)行TCP四次分手:

第一次分手:主機(jī)1(可以使客戶端,也可以是服務(wù)器端),設(shè)置Sequence Number和Acknowledgment

Number,向主機(jī)2發(fā)送一個(gè)FIN報(bào)文段;此時(shí),主機(jī)1進(jìn)入FIN_WAIT_1狀態(tài);這表示主機(jī)1沒有數(shù)據(jù)要發(fā)送給主機(jī)2了;

第二次分手:主機(jī)2收到了主機(jī)1發(fā)送的FIN報(bào)文段,向主機(jī)1回一個(gè)ACK報(bào)文段,Acknowledgment Number為Sequence

第三次分手:主機(jī)2向主機(jī)1發(fā)送FIN報(bào)文段,請(qǐng)求關(guān)閉連接,同時(shí)主機(jī)2進(jìn)入LAST_ACK狀態(tài);

第四次分手:主機(jī)1收到主機(jī)2發(fā)送的FIN報(bào)文段,向主機(jī)2發(fā)送ACK報(bào)文段,然后主機(jī)1進(jìn)入TIME_WAIT狀態(tài);主機(jī)2收到主機(jī)1的ACK報(bào)文段以后,就關(guān)閉連接;此時(shí),主機(jī)1等待2MSL后依然沒有收到回復(fù),則證明Server端已正常關(guān)閉,那好,主機(jī)1也可以關(guān)閉連接了。

來看下面的圖加強(qiáng)下理解:

keepalive connections

當(dāng)然大量的連接每次連接關(guān)閉都要三次握手四次分手的很顯然會(huì)造成性能低下,因此http有一種叫做keepalive connections的機(jī)制,它可以在傳輸數(shù)據(jù)后仍然保持連接,當(dāng)客戶端需要再次獲取數(shù)據(jù)時(shí),直接使用剛剛空閑下來的連接而不需要再次握手。

Okhttp支持5個(gè)并發(fā)KeepAlive,默認(rèn)鏈路生命為5分鐘(鏈路空閑后,保持存活的時(shí)間)。

2.連接池(ConnectionPool)分析 引用計(jì)數(shù)

在okhttp中,在高層代碼的調(diào)用中,使用了類似于引用計(jì)數(shù)的方式跟蹤Socket流的調(diào)用,這里的計(jì)數(shù)對(duì)象是StreamAllocation,它被反復(fù)執(zhí)行aquire與release操作,這兩個(gè)函數(shù)其實(shí)是在改變RealConnection中的List> 的大小。(StreamAllocation.java)

  public void acquire(RealConnection connection) {
    connection.allocations.add(new WeakReference<>(this));
  }
  private void release(RealConnection connection) {
    for (int i = 0, size = connection.allocations.size(); i < size; i++) {
      Reference reference = connection.allocations.get(i);
      if (reference.get() == this) {
        connection.allocations.remove(i);
        return;
      }
    }
    throw new IllegalStateException();
  }

RealConnection是socket物理連接的包裝,它里面維護(hù)了List>的引用。List中StreamAllocation的數(shù)量也就是socket被引用的計(jì)數(shù),如果計(jì)數(shù)為0的話,說明此連接沒有被使用就是空閑的,需要通過下文的算法實(shí)現(xiàn)回收;如果計(jì)數(shù)不為0,則表示上層代碼仍然引用,就不需要關(guān)閉連接。

主要變量

連接池的類位于okhttp3.ConnectionPool:

 private static final Executor executor = new ThreadPoolExecutor(0 /* corePoolSize */,
      Integer.MAX_VALUE /* maximumPoolSize */, 60L /* keepAliveTime */, TimeUnit.SECONDS,
      new SynchronousQueue(), Util.threadFactory("OkHttp ConnectionPool", true));

  /** The maximum number of idle connections for each address. */
  //空閑的socket最大連接數(shù)
  private final int maxIdleConnections;
  //socket的keepAlive時(shí)間
  private final long keepAliveDurationNs;
  // 雙向隊(duì)列
  private final Deque connections = new ArrayDeque<>();
  final RouteDatabase routeDatabase = new RouteDatabase();
  boolean cleanupRunning;

主要的變量有必要說明一下:

executor線程池,類似于CachedThreadPool,需要注意的是這種線程池的工作隊(duì)列采用了沒有容量的SynchronousQueue,不了解它的請(qǐng)查看Java并發(fā)編程(六)阻塞隊(duì)列這篇文章。

Deque,雙向隊(duì)列,雙端隊(duì)列同時(shí)具有隊(duì)列和棧性質(zhì),經(jīng)常在緩存中被使用,里面維護(hù)了RealConnection也就是socket物理連接的包裝。

RouteDatabase,它用來記錄連接失敗的Route的黑名單,當(dāng)連接失敗的時(shí)候就會(huì)把失敗的線路加進(jìn)去。

構(gòu)造函數(shù)
public ConnectionPool() {
//默認(rèn)空閑的socket最大連接數(shù)為5個(gè),socket的keepAlive時(shí)間為5秒
 this(5, 5, TimeUnit.MINUTES);
}
public ConnectionPool(int maxIdleConnections, long keepAliveDuration, TimeUnit timeUnit) {
 this.maxIdleConnections = maxIdleConnections;
 this.keepAliveDurationNs = timeUnit.toNanos(keepAliveDuration);

 // Put a floor on the keep alive duration, otherwise cleanup will spin loop.
 if (keepAliveDuration <= 0) {
   throw new IllegalArgumentException("keepAliveDuration <= 0: " + keepAliveDuration);
 }
}

通過構(gòu)造函數(shù)可以看出ConnectionPool默認(rèn)的空閑的socket最大連接數(shù)為5個(gè),socket的keepAlive時(shí)間為5秒。
實(shí)例化

ConnectionPool實(shí)例化是在OkHttpClient實(shí)例化時(shí)進(jìn)行的:

  public OkHttpClient() {
    this(new Builder());
  }

在OkHttpClient的構(gòu)造函數(shù)中調(diào)用了new Builder():

  public Builder() {
      dispatcher = new Dispatcher();
     ...省略
      connectionPool = new ConnectionPool();
     ...省略
    }
緩存操作

ConnectionPool提供對(duì)Deque進(jìn)行操作的方法分別為put、get、connectionBecameIdle和evictAll幾個(gè)操作。分別對(duì)應(yīng)放入連接、獲取連接、移除連接和移除所有連接操作,這里我們舉例put和get操作。

put操作

  void put(RealConnection connection) {
    assert (Thread.holdsLock(this));
    if (!cleanupRunning) {
      cleanupRunning = true;
      executor.execute(cleanupRunnable);
    }
    connections.add(connection);
  }

在添加到Deque之前首先要清理空閑的線程,這個(gè)后面會(huì)講到。

get操作

  RealConnection get(Address address, StreamAllocation streamAllocation) {
    assert (Thread.holdsLock(this));
    for (RealConnection connection : connections) {
      if (connection.allocations.size() < connection.allocationLimit
          && address.equals(connection.route().address)
          && !connection.noNewStreams) {
        streamAllocation.acquire(connection);
        return connection;
      }
    }
    return null;
  }

遍歷connections緩存列表,當(dāng)某個(gè)連接計(jì)數(shù)的次數(shù)小于限制的大小并且request的地址和緩存列表中此連接的地址完全匹配。則直接復(fù)用緩存列表中的connection作為request的連接。

自動(dòng)回收連接

okhttp是根據(jù)StreamAllocation引用計(jì)數(shù)是否為0來實(shí)現(xiàn)自動(dòng)回收連接的。我們?cè)趐ut操作前首先要調(diào)用executor.execute(cleanupRunnable)來清理閑置的線程。我們來看看cleanupRunnable到底做了什么:

  private final Runnable cleanupRunnable = new Runnable() {
    @Override public void run() {
      while (true) {
        long waitNanos = cleanup(System.nanoTime());
        if (waitNanos == -1) return;
        if (waitNanos > 0) {
          long waitMillis = waitNanos / 1000000L;
          waitNanos -= (waitMillis * 1000000L);
          synchronized (ConnectionPool.this) {
            try {
              ConnectionPool.this.wait(waitMillis, (int) waitNanos);
            } catch (InterruptedException ignored) {
            }
          }
        }
      }
    }
  };

線程不斷的調(diào)用cleanup來進(jìn)行清理,并返回下次需要清理的間隔時(shí)間,然后調(diào)用wait進(jìn)行等待以釋放鎖與時(shí)間片,當(dāng)?shù)却龝r(shí)間到了后,再次進(jìn)行清理,并返回下次要清理的間隔時(shí)間,如此循環(huán)下去,接下來看看cleanup方法:

  long cleanup(long now) {
    int inUseConnectionCount = 0;
    int idleConnectionCount = 0;
    RealConnection longestIdleConnection = null;
    long longestIdleDurationNs = Long.MIN_VALUE;

    // Find either a connection to evict, or the time that the next eviction is due.
    synchronized (this) {
    //遍歷連接
      for (Iterator i = connections.iterator(); i.hasNext(); ) {
        RealConnection connection = i.next();
        //查詢此連接的StreamAllocation的引用數(shù)量,如果大于0則inUseConnectionCount數(shù)量加1,否則idleConnectionCount加1
        if (pruneAndGetAllocationCount(connection, now) > 0) {
          inUseConnectionCount++;
          continue;
        }
        idleConnectionCount++;
        long idleDurationNs = now - connection.idleAtNanos;
        if (idleDurationNs > longestIdleDurationNs) {
          longestIdleDurationNs = idleDurationNs;
          longestIdleConnection = connection;
        }
      }
      //如果空閑連接keepAlive時(shí)間超過5分鐘,或者空閑連接數(shù)超過5個(gè),則從Deque中移除此連接
      if (longestIdleDurationNs >= this.keepAliveDurationNs
          || idleConnectionCount > this.maxIdleConnections) {
        // We"ve found a connection to evict. Remove it from the list, then close it below (outside
        // of the synchronized block).
        connections.remove(longestIdleConnection);
       //如果空閑連接大于0,則返回此連接即將到期的時(shí)間
      } else if (idleConnectionCount > 0) {
        // A connection will be ready to evict soon.
        return keepAliveDurationNs - longestIdleDurationNs;
        //如果沒有空閑連接,并且活躍連接大于0則返回5分鐘
      } else if (inUseConnectionCount > 0) {
        // All connections are in use. It"ll be at least the keep alive duration "til we run again.
        return keepAliveDurationNs;
      } else {
      //如果沒有任何連接則跳出循環(huán)
        cleanupRunning = false;
        return -1;
      }
    }

    closeQuietly(longestIdleConnection.socket());

    // Cleanup again immediately.
    return 0;
  }

cleanup所做的簡(jiǎn)單總結(jié)就是根據(jù)連接中的引用計(jì)數(shù)來計(jì)算空閑連接數(shù)和活躍連接數(shù),然后標(biāo)記出空閑的連接,如果空閑連接keepAlive時(shí)間超過5分鐘,或者空閑連接數(shù)超過5個(gè),則從Deque中移除此連接。接下來根據(jù)空閑連接或者活躍連接來返回下次需要清理的時(shí)間數(shù):如果空閑連接大于0則返回此連接即將到期的時(shí)間,如果都是活躍連接并且大于0則返回默認(rèn)的keepAlive時(shí)間5分鐘,如果沒有任何連接則跳出循環(huán)并返回-1。在上述代碼中的第13行,通過pruneAndGetAllocationCount方法來判斷連接是否閑置的,如果pruneAndGetAllocationCount方法返回值大于0則是空閑連接,否則就是活躍連接,讓我們來看看pruneAndGetAllocationCount方法:

  private int pruneAndGetAllocationCount(RealConnection connection, long now) {
    List> references = connection.allocations;
    //遍歷弱引用列表
    for (int i = 0; i < references.size(); ) {
      Reference reference = references.get(i);
      //若StreamAllocation被使用則接著循環(huán)
      if (reference.get() != null) {
        i++;
        continue;
      }

      // We"ve discovered a leaked allocation. This is an application bug.
      Internal.logger.warning("A connection to " + connection.route().address().url()
          + " was leaked. Did you forget to close a response body?");
      //若StreamAllocation未被使用則移除引用
      references.remove(i);
      connection.noNewStreams = true;

      // If this was the last allocation, the connection is eligible for immediate eviction.
      //如果列表為空則說明此連接沒有被引用了,則返回0,表示此連接是空閑連接
      if (references.isEmpty()) {
        connection.idleAtNanos = now - keepAliveDurationNs;
        return 0;
      }
    }
    //否則返回非0的數(shù),表示此連接是活躍連接
    return references.size();
  }

pruneAndGetAllocationCount方法首先遍歷傳進(jìn)來的RealConnection的StreamAllocation列表,如果StreamAllocation被使用則接著遍歷下一個(gè)StreamAllocation,如果StreamAllocation未被使用則從列表中移除。如果列表為空則說明此連接沒有引用了,則返回0,表示此連接是空閑連接,否則就返回非0的數(shù)表示此連接是活躍連接。

總結(jié)

可以看出連接池復(fù)用的核心就是用Deque來存儲(chǔ)連接,通過put、get、connectionBecameIdle和evictAll幾個(gè)操作來對(duì)Deque進(jìn)行操作,另外通過判斷連接中的計(jì)數(shù)對(duì)象StreamAllocation來進(jìn)行自動(dòng)回收連接。

參考資料
okhttp3源碼
簡(jiǎn)析TCP的三次握手與四次分手
TCP三次握手過程
短連接、長連接與keep-alive
[OkHttp3源碼分析[復(fù)用連接池]](http://www.jianshu.com/p/92a6...
okhttp連接池復(fù)用機(jī)制

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/70586.html

相關(guān)文章

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

    摘要:異步請(qǐng)求當(dāng)正在運(yùn)行的異步請(qǐng)求隊(duì)列中的數(shù)量小于并且正在運(yùn)行的請(qǐng)求主機(jī)數(shù)小于時(shí)則把請(qǐng)求加載到中并在線程池中執(zhí)行,否則就再入到中進(jìn)行緩存等待。通常情況下攔截器用來添加,移除或者轉(zhuǎn)換請(qǐng)求或者響應(yīng)的頭部信息。 前言 學(xué)會(huì)了OkHttp3的用法后,我們當(dāng)然有必要來了解下OkHttp3的源碼,當(dāng)然現(xiàn)在網(wǎng)上的文章很多,我仍舊希望我這一系列文章篇是最簡(jiǎn)潔易懂的。 1.從請(qǐng)求處理開始分析 首先OKHttp...

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

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

0條評(píng)論

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