背景
現在TCPクライアントを作成しています。
以下のプログラム(MyTcpClient.cs)は問題なく動作するのですが、全体的に冗長な気がしています。
例えば、イベントハンドラの登録解除をメソッド内で行っていたり、Task.Delay(); で待機している部分等が気になります。
質問
UserLoginRequest
メソッドの処理を最適化するアイデアやTCPネットワークプログラム用のデザインパターン等ありますでしょうか?
eventを使っている理由について
リクエストとレスポンスが1:1の関係でない為です。
HTTPであれば以下のように書くことが出来ます。
リクエストとレスポンスが1:1の関係です。
C#
1var client = new HttpClient(); 2var responseString = await client.GetStringAsync("http://example.com"); 3// TCPでもこんな感じに書きたい
しかし、System.Net.Sockets
の場合、自分で実装しなければならず、リクエストとレスポンスが1:1で取れない場合を考慮する必要がありました。任意のリクエストをしてから期待するレスポンスを受け取るまで、サーバーから異なるデータを送信し、そのデータを受け取る可能性があるからです。
なので、レスポンスデータの1バイト目で何のデータかを区別して個別のeventを発火させることにしました。
試したこと
- corefx のソースコードを読んだ
corefxのソースコードでHttpClientはTCPをどのように扱い、上位層のHTTPとして実装してるかが参考になるかと思いHttpClientのソースコードを読みましたが、WindowsであればWinHttp, unixであればcurlを呼び出しているだけで、詳細は分かりませんでした。
(WinHTTPはOSSでない為ソースコードが見れません。)
(curlのソースコードはC言語で書かれていました。Cに対する理解が深くないので読むのが辛くて読んでません。)
プログラム
- Program.cs
c#
1 static async Task Main(string[] args) 2{ 3 var client = new MyTcpClient(); 4 var requestBytes = new byte[] { }; 5 var responseBytes = await client.UserLoginRequest(requestBytes); 6}
- MyTcpClient.cs
c#
1using System; 2using System.Linq; 3using System.Net.Sockets; 4using System.Threading.Tasks; 5 6namespace TcpTest 7{ 8 class MyTcpClient 9 { 10 private Socket MyTcpClientSocket { get; set; } 11 12 private const int BufferSize = 1024; 13 public byte[] Buffer { get; } = new byte[BufferSize]; 14 15 private delegate void LoginDataReceivedEventHandler(object sender, byte[] bytes); 16 private event LoginDataReceivedEventHandler LoginDataReceived; 17 18 public MyTcpClient() 19 { 20 MyTcpClientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 21 MyTcpClientSocket.Connect("127.0.0.1", 1000); 22 23 // 非同期で受信を待機 24 MyTcpClientSocket.BeginReceive(Buffer, 0, BufferSize, SocketFlags.None, new AsyncCallback(ReceiveCallback), MyTcpClientSocket); 25 } 26 27 private void ReceiveCallback(IAsyncResult asyncResult) 28 { 29 var socket = asyncResult.AsyncState as Socket; 30 31 var receiveSize = 0; 32 try 33 { 34 // 受信を待機 35 receiveSize = socket.EndReceive(asyncResult); 36 } 37 catch (Exception ex) 38 { 39 Console.WriteLine(ex.Message); 40 return; 41 } 42 43 if (receiveSize > 0) 44 { 45 var bytes = Buffer.Take(receiveSize).ToArray(); 46 47 if (bytes[0] == 1) 48 { 49 // 1バイト目が1の場合UserLoginRequetに対するレスポンスデータ 50 LoginDataReceived?.Invoke(this, bytes); 51 } 52 53 socket.BeginReceive(Buffer, 0, Buffer.Length, SocketFlags.None, ReceiveCallback, socket); 54 } 55 } 56 57 public async Task<byte[]> UserLoginRequest(byte[] requestBytes) 58 { 59 byte[] responseBytes = null; 60 61 var eventHandler = new LoginDataReceivedEventHandler(delegate (object _sender, byte[] _bytes) 62 { 63 responseBytes = _bytes; 64 }); 65 66 // イベントハンドラを登録 67 LoginDataReceived += eventHandler; 68 69 // サーバにリクエストを送信 70 MyTcpClientSocket.Send(requestBytes); 71 72 // レスポンスを待つ 73 while (responseBytes == null) 74 { 75 await Task.Delay(100); 76 } 77 78 // イベントハンドラを解除 79 LoginDataReceived -= eventHandler; 80 81 return responseBytes; 82 } 83 } 84}
環境
- Windows 10
- Visual Studio 2017
- C# 7.2
- .NET Standard 2.0
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。