CURLOPT_MAXCONNECTS / CURLMOPT_MAXCONNECTS / CURLMOPT_MAX_TOTAL_CONNECTIONS の違い

# 概要

## 早見表

|定数|意味|
|:---|:---|
|CURLOPT_MAXCONNECTS<br>(PHP5.5以降)|[`curl_exec`]でキャッシュされる<br>非アクティブなTCPコネクション最大数|
|CURLMOPT_MAXCONNECTS<br>(PHP5.5以降)|[`curl_multi_exec`]でキャッシュされる<br>非アクティブなTCPコネクション最大数|
|CURLMOPT_MAX_TOTAL_CONNECTIONS<br>(PHP7.0.7以降)|[`curl_multi_exec`]で使用される**<br>アクティブな**TCPコネクション最大数|

- 非アクティブ<br>→ これをいじったところで何も嬉しくない場合がほとんど
- アクティブ<br>→ これの数を制御したい！！！

## 従来は…

TCPコネクションを増やし過ぎないようにPHP側でプールサイズを制御する必要があった

- 実装が面倒
- この手法は`CURLMOPT_PIPELINING` によるHTTP/1.1のパイプライン化・HTTP/2のマルチプレクサの設定と相性が最悪
  - **実際にこれらのグループ化が行われたかどうかをPHP側から知るすべが無い**


#### HTTP/1.1のパイプライン化を使う場合の問題

- 確立直後のTCPコネクションでは**パイプライン化を行ってはならない**
  - TCPコネクションがKeep-Aliveであることを確認してから初めて許可される

これはつらい．直接パイプライン化適用の有無を知る手段が無いため，いま当該サーバとの間のTCPコネクションが生きているかどうかを何とかして確認する必要があるが…実装は複雑になりそう．

#### HTTP/2のマルチプレクサを使う場合の問題

- 相手がHTTP/2に対応しているかを事前に知っておかなければならない
  - HTTP/1.1にフォールバックされる可能性がある

こっちは相手サーバが限定される場合はまだ何とかなりそう…？

## 今後は…

PHP7.0.7以降はプールサイズの管理をlibcurlに丸投げできて最高

# コードで理解する `CURLMOPT_MAX_TOTAL_CONNECTIONS`

コンストラクタでプールサイズを指定して，こんなコードを動かせるようにPoolクラスを作りたい．

```php
$pool = new Pool(5);
for ($i = 0; $i < 10; ++$i) {
    $ch = curl_init('example.com');
    $pool->add($ch);
}
$pool->exec();
```

```php:PHP7.0.6以前
class Pool
{
    protected $mh;
    protected $size;
    protected $added = [];
    protected $queue = [];

    public function __construct(int $size)
    {
        $this->mh = curl_multi_init();
        $this->size = $size;
    }

    public function add($ch)
    {
        if (count($this->added) < $this->size) {
            curl_multi_add_handle($this->mh, $ch);
            $this->added[(int)$ch] = $ch;
        } else {
            $this->queue[] = $ch;
        }
    }

    public function exec()
    {
        curl_multi_exec($this->mh, $running);
        do {
            if (curl_multi_select($this->mh) === -1) {
                usleep(2500);
                continue;
            }
            curl_multi_exec($this->mh, $running);
            $this->read();
        } while ($this->shift() || $running);
    }

    protected function read()
    {
        while ($info = curl_multi_info_read($this->mh)) {
            unset($this->added[(int)$info['handle']]);
        }
    }

    protected function shift()
    {
        while (count($this->added) < $size) {
            $ch = array_shift($this->queue);
            curl_multi_add_handle($this->mh, $ch);
            $this->added[(int)$ch] = $ch;
        }
        return isset($ch);
    }
}
```

オエエエエエエエエエ

```php:PHP7.0.7以降
class Pool
{
    protected $mh;

    public function __construct(int $size)
    {
        $this->mh = curl_multi_init();
        curl_multi_setopt($this->mh, CURLMOPT_MAX_TOTAL_CONNECTIONS, $size);
        curl_multi_setopt($this->mh, CURLMOPT_PIPELINING, 3);
    }

    public function add($ch)
    {
        curl_multi_add_handle($this->mh, $ch);
    }

    public function exec()
    {
        curl_multi_exec($this->mh, $running);
        do {
            if (curl_multi_select($this->mh) === -1) {
                usleep(2500);
                continue;
            }
            curl_multi_exec($this->mh, $running);
        } while ($running);
    }
}
```

イケメン！！！！しかもパイプラインやマルチプレクサも軽々扱えるなんて完璧すぎる！！！

# 補足

[`curl_multi_init`] で作成したリソースを破棄すると**「パイプラインが可能である」と許可しているメモが吹っ飛ぶ．**要注意．

- [PHP - HTTP/1.1 Pipelining の現在(44750)｜teratail](https://teratail.com/questions/44750)

[`curl_exec`]: http://www.php.net/manual/ja/function.curl-exec.php
[`curl_multi_exec`]: http://www.php.net/manual/ja/function.curl-multi-exec.php
[`curl_multi_init`]: http://www.php.net/manual/ja/function.curl-multi-init.php