WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

localhost への同時接続数

by WebSurfer 2021年6月18日 15:03

.NET Framework を使って Web にアクセスする WinForms などのアプリ開発時に、検証用に自分の開発マシンに Web アプリを作ってアクセスすることがあると思います。その際、localhost への要求の場合は同時接続数の制約が外れることに注意が必要という話を書きます。(検証したのは HttpWebRequest のみで HttpClient は未検証。Core はすべて未検証です)

localhost の場合

(元の話は teratail のスレッド「C#にて並列かつ非同期でWebRequestを使用する方法について」です)

HTTP 1.1 仕様では同時接続は 2 つまでとなっています。HttpWebRequest を使った場合にもその制約が適用されるようですが、(1) localhost への要求の場合で、かつ (2) ユーザーが同時接続数の設定 (ServicePointManager.DefaultConnectionLimit) を変えない場合、Int32.MaxValue になります。

上の画像がその例です。要求先はローカルの IIS Express で動く検証用の ASP.NET Web アプリで、要求を受けて 10 秒後に OK という文字列を返します。その URL を、この記事の下の方に記載したサンプルコードにあるように、マルチスレッドで HttpWebRequest を使って同時に 5 回要求した結果です。end の時間が 5 つとも同じになっているのに注目してください。

同時接続が 2 つまでに制限されていれば、一度にサーバーに要求が出るのは 2 つまでで、3 つ目以降の要求は 10 秒経って先の応答が戻ってこないと出ないので、2 つおきに end の時間が 10 秒ずつ遅れるという結果になるはずです。そうなっていないのは、localhost への要求なので同時接続数の制限が外れたからです (ServicePoint.ConnectionLimit が Int32.MaxValue になっています)。

自分が使っているレンタルサーバー (当然 localhost ではない) で動く ASP.NET Web Forms アプリに、要求を受けて 10 秒後に Hello World という文字列を返す HTTP ジェネリックハンドラを作って、それを下のサンプルコードで同時に 5 回要求した場合は下のようになります。

surferonwww.info の場合

デフォルトの同時接続数 2 は有効になっていて (ServicePoint.ConnectionLimit が 2 になっています)、2 を超えた分は先の応答が返ってきてからでないと要求が出ず、上の画像の end を見ると分かりますが 2 要求毎に 10 秒ずつ待たされるという結果になりました。

ただし、Fiddler を使うと話が違ってきます。Fiddler は IP アドレス 127.0.0.1 のプロキシなのですが、どこかで IP アドレスから localhost と判定されているようで、同時接続数は制限されないという結果になりました。下の画像がその結果です。

Fiddler 経由で surferonwww.info を要求

ServicePoint.ConnectionLimit を取得するとデフォルトの同時接続数 2 となっていますが、実際の動きを見るとそれは無視されていて (Fiddler の画面を見ていると一度に 5 つ要求が出るのが分かります)、応答が返ってきた時間(end の時間)が 5 つとも同じになっています。

開発時に Fiddler で要求・応答をキャプチャして見るということはよく行うと思いますが、Fiddler の有無で同時接続数が異なるというのも要注意と思いました。

もう一つ、開発マシンのローカル IIS で動く ASP.NET Web Forms アプリに、hosts ファイルで 127.0.0.1 に websiteproject.com というホスト名を付けて、そのホスト名で呼び出せるようにし、それを下のサンプルコードで同時に 5 回要求した場合はどうなるを試してみました。結果は以下の画像の通りです。

websiteproject.com の場合

画像の end を見るとデフォルトの同時接続数 2 は有効になっているようで、2 要求毎に 10 秒ずつ待たされるという結果になっています。要求先の IP アドレスは 127.0.0.1 なのですが、.NET Framework ライブラリは IP アドレスでではなくて localhost という名前で判定しているのでしょうか。

ただし、ServicePoint.ConnectionLimit が Int32.MaxValue になっているのが解せません。

さらに、Fiddler を使うとどうなるかと言うと、上のケースと同様に同時接続数は制限されないという結果になりました。下の画像がその結果です。

Fiddler 経由で websiteproject.com を要求

この時は、ServicePoint.ConnectionLimit が何故か 2 になるのですが、これも解せません。

ソースコードを Microsoft のサイト ServicePoint.csServicePointManager.cs で見ることができるのですが、localhost か否かの判定はどのタイミングでどのようにしているのか、何がどのタイミングで ConnectionLimit プロパティの getter を呼び出して同時接続数を Int32.MaxValue に設定しているのか等々は読み切れませんでした。

ServicePointManager.DefaultConnectionLimit や ServicePoint.ConnectionLimit の値など、いろいろ不可解な動きに見えるのですが、ServicePoint.cs のソースコードのコメントに、

3. If ServicePoint.DefaultConnectionLimit is set, then take that value

4. If ServicePoint is localhost, then set to infinite (TO Should we change this value?)

・・・と書いてある通りで、とにかく localhost はデフォルトでは同時接続数は Int32.MaxValue になるということは間違いなさそうです。

最後に、検証に使った HttpWebRequest のコンソールアプリのコードを載せておきます。Visual Studio 2019 のテンプレートで .NET Framework 4.8 で作ったものです。

using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Net;
using System.Linq;
using System.Text;
using System.Threading;

namespace ConsoleAppAsync4
{
    class Program
    {
        static async Task Main(string[] args)
        {
            // localhost
            // ローカルの IIS Express で動くASP.NET MVC5 アプリの
            // アクションメソッド。
            // 要求を受けて 10 秒後に OK という文字列を返す
            var uri = "https://localhost:44365/Home/sample";

            // surferonwww.info
            // レンタルサーバーの IIS で動く ASP.NET Web Forms ア
            // プリの HTTP ジェネリックハンドラ。
            // 要求を受けて 10 秒後に Hello World という文字列を返す
            //var uri = "http://surferonwww.info/Test/Sample.ashx";

            // websiteproject.com
            // ローカル IIS で動く ASP.NET Web Forms アプリの HTTP
            // ジェネリックハンドラ。
            // hosts ファイルで 127.0.0.1 に websiteproject.com と
            // いうホスト名を付けたのでそのホスト名で呼び出せる。
            // 要求を受けて 10 秒後に Hello World という文字列を返す
            //var uri = "http://websiteproject.com/Sample.ashx";

            Console.WriteLine($"ServicePointManager.DefaultConnectionLimit = " +
                              $"{ServicePointManager.DefaultConnectionLimit}");
            
            var sp = ServicePointManager.FindServicePoint(new Uri(uri));
            Console.WriteLine($"ServicePoint.ConnectionLimit = " +
                              $"{sp.ConnectionLimit}");

            var encoding = new UTF8Encoding(false);
            var tasks = new List<Task>();

            // 同じ URL を 5 回同時に要求する。実際に Web サーバーに
            // いくつ同時に要求が出るかは ConnectionLimit による (はず)
            foreach (var i in Enumerable.Range(0, 5))
            {
                var task = Task.Run(async () =>
                {
                    // ThreadId と開始時刻
                    int id = Thread.CurrentThread.ManagedThreadId;
                    string start = $" / ThreadID = {id}, " +
                                   $"start: {DateTime.Now:ss.fff}, ";
                    
                    var request = (HttpWebRequest)WebRequest.Create(uri);
                    using (var response = await request.GetResponseAsync())
                    using (var stream = response.GetResponseStream())
                    using (var memory = new System.IO.MemoryStream())
                    {
                        await stream.CopyToAsync(memory);
                        var text = encoding.GetString(memory.ToArray());

                        // 終了時刻と ServicePoint.ConnectionLimit
                        string end = $"end: {DateTime.Now:ss.fff}";
                        string limit = $", limit: {sp.ConnectionLimit}";
                        text += start + end + limit;

                        Console.WriteLine(text);
                    }
                });

                tasks.Add(task);
            }

            await Task.WhenAll(tasks);

            Console.WriteLine("Finish");
            Console.ReadLine();
        }
    }
}

Tags: , ,

.NET Framework

Visual Studio で Edge 利用

by WebSurfer 2017年2月18日 14:26

先日買った Windows 10 Pro 64-bit のノート PC に旧マシンから開発環境を移していますが、Visual Studio で開発中の Web アプリを Edge で開けないという問題に遭遇しました。一応解決したので、自分が行った解決方法を備忘録として書いておきます。

Windows 10 には Microsoft Edge という新しいブラウザが標準として搭載されています。

その Edge を使うと、Visual Studio から開発中の Web アプリを開けないだけでなく、Edge を単独で立ち上げてアドレスバーにローカル IIS で動くように設定した Web アプリの URL を入力しても表示されません。

ググって調べてみると、Edge はセキュリティが厳しくなって、ローカルホスト(ホスト名に関係なく IP アドレス 127.0.0.1 がアクセス先のホスト)へのアクセスが制限されているそうです。

解決策として、Edge のアドレスバーに about:flags と入力して表示される「開発者向け設定」で[ローカルホスト ループバックを許可する]にチェックを入れて Edge を再起動するという記事がいくつか見つかりました。

ローカルホストループバックの許可

で、早速自分もやってみました・・・が、ダメでした。(涙) 依然として Edge はローカルホストにはアクセスできません。当然 Visual Studio からローカルホストの Web アプリを開くこともできません。

さらにググって調べてみると、コントロールパネルから開く「インターネットオプション」で、ローカルイントラネットゾーンに含めるサイトの定義を変更するという記事を見つけました。

イントラネット Web サイトの設定

具体的には上の画像のように、[セキュリティ]タブ ⇒[ローカルイントラネット]⇒[サイト(S)]で表示される「ローカルイントラネット」ダイアログで[ほかのゾーンに指定されてないローカル (イントラネット) のサイトをすべて含める(Z)]のチェックを外します。

結果、Visual Studio 2010 / 2015 ともローカルホストの Web アプリを Edge で開くことができるようになりました。もちろん Edge を単独で立ち上げてアドレスバーにローカルホストの URL を入力しても表示できます。

Edge でのローカルホストサイト表示

上の画像は、Visual Studio 2015 のテンプレートを使って自動生成させたインターネット用 Web サイトアプリケーションです。

ローカル IIS 上でホスト名 websiteproject.com で動くように設定し、hosts ファイルでそのホスト名の IP アドレスを 127.0.0.1 に設定し、Visual Studio 2015 でサイトを開いて実行させています。

(ググって調べた記事の中に CheckNetIsolation.exe コマンドを使って設定するというものがいくつかありました。Edge のアドレスバーに about:flags として設定できなかった古いバージョンではそれを使っていたようです。なお、CheckNetIsolation.exe コマンドが「インターネットオプション」の変更まで行ってくれるかどうかは分かりません)

Tags: , ,

DevelopmentTools

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年4月  >>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar