2017-03-31 3 views
1

ベンダーソフトウェアクライアントが接続するWindowsサービスとして動作するTcpListenerサービスを作成しています。私はクライアントの実装を変更することはできず、その仕様に従わなければなりません。TcpListenerがハートビートを5秒ごとに送信し、クライアントasyncからメッセージを読み取る

メッセージを受信して​​応答を返す部分があります。私が持っている問題は、クライアントが同じメッセージで応答するハートビートメッセージ(S0000E)が5秒ごとに送信されることを期待することです。クライアントから受信した実際のメッセージを処理するために、非同期/待機中の機能の中にその機能を追加する方法がわかりません。

のOnStart

_serverListenerTask = Task.Run(() => AcceptClientsAsync(_listener, _cancellationToken.Token)); 

AcceptClientsAsync

static async Task AcceptClientsAsync(TcpListener listener, CancellationToken ct) 
{ 
    var clientCounter = 0; 
    while (!ct.IsCancellationRequested) 
    { 
     TcpClient client = await listener.AcceptTcpClientAsync() 
             .ConfigureAwait(false); 
     clientCounter++; 
     await ReceiveMessageAsync(client, clientCounter, ct); 
    } 
} 

私はTask.WhenAnyにハートビートタスクを追加することができると思ったが、それは、ハートビートに常に火災やIを引き起こし

static async Task ReceiveMessageAsync(TcpClient client, int clientIndex, CancellationToken ct) 
{ 
    Log.Info("New client ({0}) connected", clientIndex); 
    using (client) 
    { 
     var buffer = new byte[4096]; 
     var stream = client.GetStream(); 
     while (!ct.IsCancellationRequested) 
     { 
      var timeoutTask = Task.Delay(TimeSpan.FromSeconds(15)); 
      var amountReadTask = stream.ReadAsync(buffer, 0, buffer.Length, ct); 

      var completedTask = await Task.WhenAny(timeoutTask, amountReadTask) 
              .ConfigureAwait(false); 

      if (completedTask == timeoutTask) 
      { 
       var msg = Encoding.ASCII.GetBytes("Client timed out"); 
       await stream.WriteAsync(msg, 0, msg.Length); 
       break; 
      } 

      var bytesRead = amountReadTask.Result; 
      if (bytesRead == 0) 
      { 
       // Nothing was read 
       break; 
      } 

      // Snip... Handle message from buffer here 

      await stream.WriteAsync(responseBuffer, 0, responseBuffer.Length, ct) 
         .ConfigureAwait(false); 
     } 
    } 
    Log.Info("Client ({0}) disconnected", clientIndex); 
} 

ReceiveMessageAsyncその応答を決して読むことはできませんでした。私はまた、タイムアウトの前にハートビートを送信しようとしましたが、それは送信のために働くタスクを読んでいましたが、次にハートビートの応答を読んでいました。本質的に、ハートビート交換が成功した場合、その15秒後にクライアントを切断しないでください。

+0

あなたはどのようにして、リソースでより効率的にそれを改善する場合は、実装の仕方以下、現実的な解決策になることができますあるメッセージと別のメッセージを分離しますか? – john

+0

私は通常、このプロトコルはクライアント要求 - >サーバー応答ですが、この特定のインスタンスではサーバーハートビート - >クライアントハートビートであることを読んでいますか?プロトコルに特定のルールがありますか?応答が未処理の間にハートビートメッセージを送信することは許可されていませんか?ここに含まれるメッセージ(種類)とそれを取り巻く規則(すなわち「プロトコル」)の明確な定義が役立ちます。 –

+0

クライアントが要求を送信するのとまったく同じ瞬間にサーバーがハートビートを送信することを選択した場合、それは本質的に難しいようです。 –

答えて

1

TCPサーバークライアントの実装は単純な作業ではありません。

サーバー:

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

namespace Server 
{ 
    public class Program 
    { 
     static List<SocketBuffer> clients = new List<SocketBuffer>(); 
     public static void Main(string[] args) 
     { 
      //Receive from any IP, listen on port 65000 in this machine 
      var listener = new TcpListener(IPAddress.Any, 65000); 
      var t = Task.Run(() => 
      { 
       while (true) 
       { 
        listener.Start(); 
        var task = listener.AcceptTcpClientAsync(); 
        task.Wait(); 
        clients.Add(new SocketBuffer(task.Result, new byte[4096])); 
       } 
      }); 
      t.Wait(); //It will remain here, do in a better way if you like ! 
     } 
    } 

    /// <summary> 
    /// We need this class because each TcpClient will have its own buffer 
    /// </summary> 
    class SocketBuffer 
    { 
     public SocketBuffer(TcpClient client, byte[] buffer) 
     { 
      this.client = client; 
      stream = client.GetStream(); 
      this.buffer = buffer; 

      receiveData(null); 
     } 

     private TcpClient client; 
     private NetworkStream stream; 
     private byte[] buffer; 

     private object _lock = new object(); 
     private async void receiveData(Task<int> result) 
     { 
      if (result != null) 
      { 
       lock (_lock) 
       { 
        int numberOfBytesRead = result.Result; 
        //If no data read, it means we are here to be notified that the tcp client has been disconnected 
        if (numberOfBytesRead == 0) 
        { 
         onDisconnected(); 
         return; 
        } 
        //We need a part of this array, you can do it in more efficient way if you like 
        var segmentedArr = new ArraySegment<byte>(buffer, 0, numberOfBytesRead).ToArray(); 
        OnDataReceived(segmentedArr); 
       } 

      } 
      var task = stream.ReadAsync(buffer, 0, buffer.Length); 
      //This is not recursion in any sense because the current 
      //thread will be free and the call to receiveData will be from a new thread 
      await task.ContinueWith(receiveData);  
     } 

     private void onDisconnected() 
     { 
      //Add your code here if you want this event 
     } 

     private void OnDataReceived(byte[] dat) 
     { 
      //Do anything with the data, you can reply here. I will just pring the received data from the demo client 
      string receivedTxt = Encoding.ASCII.GetString(dat); 
      Console.WriteLine(receivedTxt); 
     } 
    } 
} 

デモクライアント:

using System; 
using System.Net.Sockets; 
using System.Text; 
using System.Threading; 

namespace Client 
{ 
    public class Program 
    { 
     public static void Main(string[] args) 
     { 
      TcpClient client = new TcpClient(); 
      var task = client.ConnectAsync("localhost", 65000); 
      task.Wait(); 
      if(client.Connected) 
      { 
       Console.WriteLine("Client connected"); 
       var stream = client.GetStream(); 
       var data = Encoding.ASCII.GetBytes("test"); 
       stream.Write(data, 0, data.Length); 
      } 
      else 
      { 
       Console.WriteLine("Client NOT connected"); 
      } 
      Thread.Sleep(60000); 
     } 
    } 
} 
関連する問題