Xamarin.Formsで.NETのHttpClientの挙動を調査


.Net の HttpClient を使ったHTTP通信がどんな動きをするのか調べてみます。

## まずは単純な通信から

ローカルマシン上で稼働するHTTPサーバーに向かって1本だけリクエストを投げてみます。

### 実験用コード
```xml:XAML
<?xml version="1.0" encoding="utf-8"?>
<ContentPage
	xmlns="http://xamarin.com/schemas/2014/forms"
	xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
	xmlns:local="clr-namespace:MyApp"
	x:Class="MyApp.MyAppPage">
	
	<Button x:Name="ExecuteButton" Text="Execute" VerticalOptions="Center" HorizontalOptions="Center" />
	
</ContentPage>
```

```csharp:コードビハインド
using Xamarin.Forms;

namespace MyApp
{
    public partial class MyAppPage : ContentPage
    {
        public MyAppPage()
        {
            InitializeComponent();

            var httpClient = new System.Net.Http.HttpClient();

            this.ExecuteButton.Clicked += (sender, e) => {
                httpClient.GetAsync("http://127.0.0.1/");
            };
        }
    }
}
```

### 結果

なんてことはない、普通の通信ですね。

![result_1.png](https://qiita-image-store.s3.amazonaws.com/0/169798/04f90dfa-f0d4-7c33-9e5e-15a985b44df6.png "result_1.png")


## 単一インスタンスでたくさん非同期リクエストしてみる

同一URLにたくさん投げてみましょうかね。
たくさんと言っても2個ですが。

### コード
```csharp:コードビハインド
using Xamarin.Forms;

namespace MyApp
{
    public partial class MyAppPage : ContentPage
    {
        public MyAppPage()
        {
            InitializeComponent();

            var httpClient = new System.Net.Http.HttpClient();

            this.ExecuteButton.Clicked += (sender, e) => {
                httpClient.GetAsync("http://127.0.0.1/");
                httpClient.GetAsync("http://127.0.0.1/");
            };
        }
    }
}
```

### 結果

2回続けて計4本のリクエストを送信しました。
ポートが同じだったり違ったり...（1回目は 64244、2回目は 64245 と 64246）
単一インスタンスだからといっても、非同期通信では必ず同一のポートを使うとも限らないようです。

![result_2.png](https://qiita-image-store.s3.amazonaws.com/0/169798/e5dd373b-203c-b824-6608-40bc5106d248.png "result_2.png")


## 単一インスタンスでたくさん同期リクエストしてみる

Wait() を付けて同期的にリクエストしてみます。

### コード
```csharp:コードビハインド
using Xamarin.Forms;

namespace MyApp
{
    public partial class MyAppPage : ContentPage
    {
        public MyAppPage()
        {
            InitializeComponent();

            var httpClient = new System.Net.Http.HttpClient();

            this.ExecuteButton.Clicked += (sender, e) => {
                httpClient.GetAsync("http://127.0.0.1/").Wait();
                httpClient.GetAsync("http://127.0.0.1/").Wait();
            };
        }
    }
}
```
### 結果

FIN ACK が送られないくらいのタイミングで2回続けて計4本リクエストしました。
同じポートが使われている模様です。

![result_4.png](https://qiita-image-store.s3.amazonaws.com/0/169798/d499d59b-257b-0733-eb15-8806e36c864a.png "result_4.png")


## 複数インスタンスでたくさんリクエストしてみる

### コード
```csharp:コードビハインド
using Xamarin.Forms;

namespace MyApp
{
    public partial class MyAppPage : ContentPage
    {
        public MyAppPage()
        {
            InitializeComponent();

            var httpClient_1 = new System.Net.Http.HttpClient();
            var httpClient_2 = new System.Net.Http.HttpClient();

            this.ExecuteButton.Clicked += (sender, e) => {
                httpClient_1.GetAsync("http://127.0.0.1/");
                httpClient_2.GetAsync("http://127.0.0.1/");
            };
        }
    }
}
```

### 結果

FIN ACK が送られないくらいのタイミングで2回続けて計4本リクエストしました。
各回の1本目と2本目で違うポートを使っているようです。

非同期であっても、インスタンスが異なれば違うソケットを使っているのが分かります。
また、インスタンスごとにコネクションが生きていれば同じソケットを使いまわしていることも分かります。

![result_5.png](https://qiita-image-store.s3.amazonaws.com/0/169798/67b383bb-a24f-361d-4670-ef10407bedb1.png "result_5.png")


## まとめ

.NET の HttpClient クラスは、

- インスタンスごとにソケットを生成
- コネクション生存時はソケットを再利用


単一インスタンスであれば、1個以上のソケットが生成されるのに対して、
複数インスタンスの場合は、インスタンスと同数のソケットが生成される。

という挙動でした。

コネクションがたくさん張られるような状況がマズい場合は、巷間の情報どおり、単一インスタンスで通信するのが無難ですね。

とりあえずは以上です。


## こちらを参考にしました
.NETのHttpClientの取り扱いには要注意という話
http://qiita.com/nskhara/items/b7c31d60531ffbe29537