C#15分でマルチスレッドサーバー

C#はかなりシンプルで柔軟な言語です。 .NETに加えて、既製のクラスがかなりあり、それによりさらに簡単になります。 そのため、単純なマルチスレッドHTTPサーバーを記述して、わずか15分で静的コンテンツをレンダリングすることが可能です。 既製のHttpListenerクラスを使用して、さらに高速に管理することもできますが、この記事の目標は、C#でこのようなことを行う方法を示すことです。



最初に、新しいコンソールプロジェクトを作成します。

Copy Source | Copy HTML using System; using System.Collections.Generic; using System.Text; namespace HTTPServer { class Server { static void Main( string [] args) { } } }



  1. Copy Source | Copy HTML using System; using System.Collections.Generic; using System.Text; namespace HTTPServer { class Server { static void Main( string [] args) { } } }



  2. Copy Source | Copy HTML using System; using System.Collections.Generic; using System.Text; namespace HTTPServer { class Server { static void Main( string [] args) { } } }



  3. Copy Source | Copy HTML using System; using System.Collections.Generic; using System.Text; namespace HTTPServer { class Server { static void Main( string [] args) { } } }



  4. Copy Source | Copy HTML using System; using System.Collections.Generic; using System.Text; namespace HTTPServer { class Server { static void Main( string [] args) { } } }



  5. Copy Source | Copy HTML using System; using System.Collections.Generic; using System.Text; namespace HTTPServer { class Server { static void Main( string [] args) { } } }



  6. Copy Source | Copy HTML using System; using System.Collections.Generic; using System.Text; namespace HTTPServer { class Server { static void Main( string [] args) { } } }



  7. Copy Source | Copy HTML using System; using System.Collections.Generic; using System.Text; namespace HTTPServer { class Server { static void Main( string [] args) { } } }



  8. Copy Source | Copy HTML using System; using System.Collections.Generic; using System.Text; namespace HTTPServer { class Server { static void Main( string [] args) { } } }



  9. Copy Source | Copy HTML using System; using System.Collections.Generic; using System.Text; namespace HTTPServer { class Server { static void Main( string [] args) { } } }



  10. Copy Source | Copy HTML using System; using System.Collections.Generic; using System.Text; namespace HTTPServer { class Server { static void Main( string [] args) { } } }



  11. Copy Source | Copy HTML using System; using System.Collections.Generic; using System.Text; namespace HTTPServer { class Server { static void Main( string [] args) { } } }



  12. Copy Source | Copy HTML using System; using System.Collections.Generic; using System.Text; namespace HTTPServer { class Server { static void Main( string [] args) { } } }





.NETでは、TcpListenerクラスを使用してTCPサーバーを非常に簡単に作成できます。これを使用します。

Copy Source | Copy HTML



  1. クラス サーバー
  2. {
  3. TcpListenerリスナー。 // TCPクライアントを受け入れるオブジェクト
  4. //サーバーの起動
  5. パブリック サーバーintポート)
  6. {
  7. //指定されたポートの「リスナー」を作成します
  8. リスナー= 新しい TcpListener (IPAddress.Any、ポート);
  9. Listener.Start(); //実行する
  10. //無限ループ内
  11. whiletrue
  12. {
  13. //新しい顧客を受け入れる
  14. Listener.AcceptTcpClient();
  15. }
  16. }
  17. //サーバーを停止します
  18. サーバー ()
  19. {
  20. //「リスナー」が作成された場合
  21. if (リスナー!= null
  22. {
  23. //やめて
  24. Listener.Stop();
  25. }
  26. }
  27. static void Main( string [] args)
  28. {
  29. //ポート80に新しいサーバーを作成します
  30. 新しい サーバー80 );
  31. }
  32. }


ここでアプリケーションを実行すると、すでにポート80に接続できます...それだけです。 接続は、ハンドラが見つからず、サーバー側で閉じないため、何もせずにアイドル状態になります。

最も簡単なハンドラーを作成しましょう。

Copy Source | Copy HTML



  1. //クライアントハンドラクラス
  2. クラスの クライアント
  3. {
  4. //クラスのコンストラクター。 彼はTcpListenerから受信したクライアントを転送する必要があります
  5. パブリック クライアント (TcpClient クライアント
  6. {
  7. //シンプルなHTMLページのコード
  8. string Html = "<html> <body> <h1>動作します!</ h1> </ body> </ html>" ;
  9. //必須のヘッダー:サーバーの応答、コンテンツのタイプと長さ。 2行の空行の後-コンテンツ自体
  10. string Str = "HTTP / 1.1 200 OK \ nContent-type:text / html \ nContent-Length:" + Html.Length.ToString()+ "\ n \ n" + Html;
  11. //文字列をバイト配列にキャストします
  12. byte [] Buffer = Encoding.ASCII.GetBytes(Str);
  13. //クライアントに送信します
  14. Client .GetStream()。Write( Buffer0Buffer .Length);
  15. //接続を閉じます
  16. クライアント .Close();
  17. }
  18. }


クライアントを渡すには、Serverクラスの1行を変更する必要があります。

Copy Source | Copy HTML



  1. //新しいクライアントを受け入れ、処理のためにそれらをClientクラスの新しいインスタンスに転送します
  2. 新しい クライアント (Listener.AcceptTcpClient());


これで、プログラムを実行し、ブラウザでアドレス127.0.0.1を開き、大文字で「It works!」と表示できます。

HTTPリクエストパーサーの作成を開始する前に、サーバーをマルチスレッド化します。 これを行うには、クライアントごとに新しいスレッドを手動で作成する方法と、 スレッドプールを使用する方法の2つがあります 。 どちらの方法にも長所と短所があります。 クライアントごとにストリームを作成すると、サーバーは高負荷に耐えられない可能性がありますが、ほぼ無制限の数のクライアントで同時に作業できます。 スレッドプールを使用する場合、同時に実行されるスレッドの数は制限されますが、古いスレッドが完了するまで新しいスレッドを作成することはできません。 どちらの方法がより適しているかはわかりませんので、両方の例を示します。

Clientクラスの新しいインスタンスのみを作成する単純なスレッドプロシージャを作成します。

Copy Source | Copy HTML



  1. static void ClientThread( Object StateInfo)
  2. {
  3. 新しい クライアント ((TcpClient)StateInfo);
  4. }


最初の方法を使用するには、無限の顧客受付サイクルの内容を置き換えるだけです。

Copy Source | Copy HTML



  1. //新しいクライアントを受け入れます
  2. TcpClient Client = Listener.AcceptTcpClient();
  3. //ストリームを作成します
  4. スレッドスレッド = 新しい スレッド新しい ParameterizedThreadStart (ClientThread));
  5. //そして、このスレッドを実行し、受信したクライアントに渡します
  6. Thread .Start(クライアント);


2番目の方法では、同じことを行う必要があります。

Copy Source | Copy HTML



  1. //新しい顧客を受け入れます。 クライアントが受け入れられた後、新しいスレッド(ClientThread)に転送されます
  2. //スレッドプールを使用します。
  3. ThreadPool.QueueUserWorkItem( new WaitCallback (ClientThread)、Listener.AcceptTcpClient());


さらに、同時に実行されるスレッドの最大数と最小数を設定する必要があります。 これはメイン手順で行います:

Copy Source | Copy HTML



  1. //必要な最大スレッド数を定義します
  2. //プロセッサごとに4つあります
  3. int MaxThreadsCount = Environment .ProcessorCount * 4 ;
  4. //ワーカースレッドの最大数を設定します
  5. ThreadPool.SetMaxThreads(MaxThreadsCount、MaxThreadsCount);
  6. //ワーカースレッドの最小数を設定します
  7. ThreadPool.SetMinThreads( 2、2 );


この数にはメインスレッドが含まれるため、スレッドの最大数は少なくとも2でなければなりません。 ユニットを設定すると、メインスレッドが作業を中断した場合にのみクライアント処理が可能になります(たとえば、新しいクライアントを待機したり、Sleepプロシージャが呼び出されたりします)。

したがって、完全にClientクラスに切り替えて、HTTP要求の処理を開始します。 クライアントからリクエストテキストを取得します。

Copy Source | Copy HTML



  1. //クライアントリクエストを保存する行を宣言します
  2. 文字列 Request = "" ;
  3. //クライアントから受信したデータを保存するバッファ
  4. byte [] Buffer = 新しいバイト [ 1024 ];
  5. //クライアントから受信したバイト数を格納する変数
  6. intカウント;
  7. //クライアントストリームからデータを受信するまで読み取ります
  8. while ((Count = Client.GetStream()。Read( Buffer0Buffer .Length))> 0
  9. {
  10. //このデータを文字列に変換し、リクエスト変数に追加します
  11. リクエスト+ = Encoding.ASCII.GetString( Buffer0 、Count);
  12. //リクエストは、シーケンス\ r \ n \ r \ nで中断する必要があります
  13. //リクエスト行の長さが4キロバイトを超える場合、データ受信を自分で中断します
  14. // POSTリクエスト(など)からデータを受信する必要はありませんが、定期的なリクエスト
  15. //理論的には4キロバイトを超えてはいけません
  16. if (Request.IndexOf( "\ r \ n \ r \ n" )> = 0 || Request.Length> 4096
  17. {
  18. 休憩 ;
  19. }
  20. }


次に、受信したデータを解析します。
Copy Source | Copy HTML



  1. //正規表現を使用してクエリ文字列を解析します
  2. //同時に、GETリクエストのすべての変数をカットします
  3. ReqMatch = Regexに 一致します。 Match (リクエスト、 @ "^ \ w + \ s +([^ \ s \?] +)[^ \ S] * \ s + HTTP /.* |" );
  4. //リクエストが失敗した場合
  5. if (ReqMatch == Match .Empty)
  6. {
  7. //クライアントにエラー400を渡す-無効なリクエスト
  8. SendError(クライアント、 400 );
  9. 帰る
  10. }
  11. //クエリ文字列を取得します
  12. string RequestUri = ReqMatch.Groups [ 1 ] .Value;
  13. //エスケープされた文字を変換して、元の形式に縮小します
  14. //たとえば、「%20」->「」
  15. RequestUri = Uri .UnescapeDataString(RequestUri);
  16. //行にコロンが含まれる場合、エラー400を送信します
  17. //これは、http://example.com/../../file.txtなどのURLから保護するために必要です。
  18. if (RequestUri.IndexOf( ".." )> = 0
  19. {
  20. SendError(クライアント、 400 );
  21. 帰る
  22. }
  23. //クエリ行が「/」で終わる場合、index.htmlを追加します
  24. if (RequestUri.EndsWith( "/" ))
  25. {
  26. RequestUri + = "index.html" ;
  27. }


最後に、ファイルを操作します。ファイルがあるかどうかを確認し、そのコンテンツタイプを判別して、クライアントに渡します。

Copy Source | Copy HTML



  1. 文字列 FilePath = "www /" + RequestUri;
  2. //このファイルがwwwフォルダーに存在しない場合、404エラーを送信します
  3. if (! File .Exists(FilePath))
  4. {
  5. SendError(クライアント、 404 );
  6. 帰る
  7. }
  8. //クエリ文字列からファイル拡張子を取得します
  9. string Extension = RequestUri.Substring(RequestUri.LastIndexOf( '。' ));
  10. //コンテンツタイプ
  11. string ContentType = "" ;
  12. //ファイル拡張子によってコンテンツのタイプを判別しようとしています
  13. スイッチ (拡張機能)
  14. {
  15. ケース ".htm"
  16. ケース ".html"
  17. ContentType = "text / html" ;
  18. 休憩 ;
  19. ケース 「.css」
  20. ContentType = "text / stylesheet" ;
  21. 休憩 ;
  22. ケース 「.js」
  23. ContentType = "text / javascript" ;
  24. 休憩 ;
  25. ケース ".jpg"
  26. ContentType = "image / jpeg" ;
  27. 休憩 ;
  28. ケース 「.jpeg」
  29. ケース ".png"
  30. ケース ".gif"
  31. ContentType = "image /" + Extension.Substring( 1 );
  32. 休憩 ;
  33. デフォルト
  34. if (Extension.Length> 1
  35. {
  36. ContentType = "application /" + Extension.Substring( 1 );
  37. }
  38. 他に
  39. {
  40. ContentType = "application / unknown" ;
  41. }
  42. 休憩 ;
  43. }
  44. //エラーを防ぐためにファイルを開きます
  45. FileStream FS;
  46. 試してみる
  47. {
  48. FS = new FileStream (FilePath、FileMode.Open、FileAccess.Read、FileShare.Read);
  49. }
  50. catch例外
  51. {
  52. //エラーが発生した場合、クライアントに500エラーを送信します
  53. SendError(クライアント、 500 );
  54. 帰る
  55. }
  56. //ヘッダーを送信します
  57. string Headers = "HTTP / 1.1 200 OK \ nContent-Type:" + ContentType + "\ nContent-Length:" + FS.Length + "\ n \ n" ;
  58. byte [] HeadersBuffer = Encoding.ASCII.GetBytes(ヘッダー);
  59. Client.GetStream()。Write(HeadersBuffer、 0 、HeadersBuffer.Length);
  60. //ファイルの終わりに達するまで
  61. while (FS.Position <FS.Length)
  62. {
  63. //ファイルからデータを読み取ります
  64. Count = FS.Read( Buffer0Buffer .Length);
  65. //そしてそれらをクライアントに渡します
  66. Client.GetStream()。書き込み( Buffer0 、Count);
  67. }
  68. //ファイルと接続を閉じます
  69. FS.Close();
  70. Client.Close();


コードには、まだ説明されていないSendErrorプロシージャも記載されています。 それも書きましょう:

Copy Source | Copy HTML



  1. //エラーページを送信する
  2. private void SendError(TcpClientクライアント、 intコード)
  3. {
  4. //「200 OK」形式の文字列を取得します
  5. // HttpStatusCodeはすべてのHTTP / 1.1ステータスコードを保存します
  6. string CodeStr = Code.ToString()+ "" +((HttpStatusCode)Code).ToString();
  7. //シンプルなHTMLページのコード
  8. string Html = "<html> <body> <h1>" + CodeStr + "</ h1> </ body> </ html>" ;
  9. //必須のヘッダー:サーバーの応答、コンテンツのタイプと長さ。 2行の空行の後-コンテンツ自体
  10. string Str = "HTTP / 1.1" + CodeStr + "\ nContent-type:text / html \ nContent-Length:" + Html.Length.ToString()+ "\ n \ n" + Html;
  11. //文字列をバイト配列にキャストします
  12. byte [] Buffer = Encoding.ASCII.GetBytes(Str);
  13. //クライアントに送信します
  14. Client.GetStream()。Write( Buffer0Buffer .Length);
  15. //接続を閉じます
  16. Client.Close();
  17. }


これで、簡単なHTTPサーバーの作成が完了しました。 いくつかのスレッドで機能し、静的なものを提供し、不正な要求に対する単純な保護を備えており、欠落ファイルを誓います。 これらはすべて、追加のガジェットでハングアップできます。ドメインの構成、処理、mod_rewriteなどのアドレスの変更、CGIのサポートなどです。 しかし、これはまったく別の話になります:-)



ソース(ThreadPool経由)

ソース(スレッド経由)

ソース付きアーカイブ(ThreadPool経由、スレッド経由のオプションはコメント化されています)

コンパイル済みバージョンのアーカイブ(ThreadPool経由)



All Articles