2017-01-18 39 views
2

TcpClientクラスを使用してソケットクライアントを実装しました。だから私はデータを送受信することができ、すべてがうまくいく。しかし、私はいくつかの教祖をそこに尋ねます:)私の実装に何か問題はありますか?おそらく、より良いやり方があります。特に、私はどのように切断するのですか?ソケットが接続されていないことを示すインジケータがありますか?TcpClientクラスを使用した非同期ソケットクライアントC#

私はSocketクラスの機能を待っていますが、 "SocketAsyncEventArgs"の周りに私の頭を包むことはできません。どうしてそちらが最初ですか? なぜ私はちょうど:待っているClient.SendAsync( "データ");

public class Client 
{ 
    private TcpClient tcpClient; 

    public void Initialize(string ip, int port) 
    { 
     try 
     { 
      tcpClient = new TcpClient(ip, port); 

      if (tcpClient.Connected) 
       Console.WriteLine("Connected to: {0}:{1}", ip, port); 
     } 
     catch (Exception ex) 
     { 
      Console.WriteLine(ex.Message); 
      Initialize(ip, port); 
     } 
    } 

    public void BeginRead() 
    { 
     var buffer = new byte[4096]; 
     var ns = tcpClient.GetStream(); 
     ns.BeginRead(buffer, 0, buffer.Length, EndRead, buffer); 
    } 

    public void EndRead(IAsyncResult result) 
    { 
     var buffer = (byte[])result.AsyncState; 
     var ns = tcpClient.GetStream(); 
     var bytesAvailable = ns.EndRead(result); 

     Console.WriteLine(Encoding.ASCII.GetString(buffer, 0, bytesAvailable)); 
     BeginRead(); 
    } 

    public void BeginSend(string xml) 
    { 
     var bytes = Encoding.ASCII.GetBytes(xml); 
     var ns = tcpClient.GetStream(); 
     ns.BeginWrite(bytes, 0, bytes.Length, EndSend, bytes); 
    } 

    public void EndSend(IAsyncResult result) 
    { 
     var bytes = (byte[])result.AsyncState; 
     Console.WriteLine("Sent {0} bytes to server.", bytes.Length); 
     Console.WriteLine("Sent: {0}", Encoding.ASCII.GetString(bytes)); 
    } 
} 

と使用方法:

static void Main(string[] args) 
{ 
    var client = new Client(); 
    client.Initialize("127.0.0.1", 8778); 

    client.BeginRead(); 
    client.BeginSend("<Names><Name>John</Name></Names>"); 

    Console.ReadLine(); 
} 

答えて

4

オーケー、それはあなたがやった可能性が最大の問題を見つけるために私の10秒を要した:

public void BeginRead() 
{ 
    var buffer = new byte[4096]; 
    var ns = tcpClient.GetStream(); 
    ns.BeginRead(buffer, 0, buffer.Length, EndRead, buffer); 
} 

をしかし、我々はしている理由です心配しないでくださいSO。


まずそれは、このような大きな問題だ、なぜ私が説明しましょう。

4097バイトの長さのメッセージをとします。あなたのバッファは4096バイトしか受け取ることができません。つまり、このバッファにメッセージ全体をパックすることはできません。または、12バイトの長さのパッケージを送信していると仮定しましょう... 4096バイトをメモリに保存するだけで12バイトを保存しています。

これに対処するには?

あなたは、あなたが全体のパッケージを識別するのに役立ちますプロトコル(一部の人はメッセージフレーミングそれを呼び出すが、それはただのプロトコルです...)のいくつかの種類を行うことを検討すべきであるネットワーキングと連携するたびに。

例のプロトコルは次のようになります。

[1B =メッセージの種類] [4B =長さ] [XB =メッセージ]

- where X == BitConvert.ToInt32(length);

簡単な例:

受信者:

byte messageType = (byte)netStream.ReadByte(); 
byte[] lengthBuffer = new byte[sizeof(int)]; 
int recv = netStream.Read(lengthBuffer, 0, lengthBuffer.Length); 
if(recv == sizeof(int)) 
{ 
    int messageLen = BitConverter.ToInt32(lengthBuffer, 0); 
    byte[] messageBuffer = new byte[messageLen]; 
    recv = netStream.Read(messageBuffer, 0, messageBuffer.Length); 
    if(recv == messageLen) 
    { 
     // messageBuffer contains your whole message ... 
    } 
} 

送信者:あなたのコードの

byte messageType = (1 << 3); // assume that 0000 1000 would be XML 
byte[] message = Encoding.ASCII.GetBytes(xml); 
byte[] length = BitConverter.GetBytes(message.Length); 
byte[] buffer = new byte[sizeof(int) + message.Length + 1]; 
buffer[0] = messageType; 
for(int i = 0; i < sizeof(int); i++) 
{ 
    buffer[i + 1] = length[i]; 
} 
for(int i = 0; i < message.Length; i++) 
{ 
    buffer[i + 1 + sizeof(int)] = message[i]; 
} 
netStream.Write(buffer); 

残りは大丈夫に見えます。しかし、私の意見では、あなたのケースでは非同期操作を使用するだけで無駄です。同期呼び出しでも同じことができます。

2

ここでは正確な質問はありませんが、コードレビューのいくつかの種類のため、答えにくいです。しかし、まだいくつかのヒント:

  • 接続メカニズムが間違っているようです。私は接続が確立されるまでTcpClient.Connectedがブロックされるとは思わない。したがって、接続が進行中である場合には失敗することが多く、次にすべてを再開します。ブロックまたは非同期Connectメソッドを使用するように切り替える必要があります。
  • SocketAsyncEventArgsは、高性能の非同期データ転送の仕組みです。それはほとんど必要ありません。
  • データを非同期で送信する場合は、Asyncメソッドを使用してTaskを返す必要があります。これは、async/awaitと簡単に組み合わせることができるためです。
  • APMモデル(BeginXYZ/EndXYZ)は推奨されていません。新しいコードでは使用しないでください。 1つの問題は、EndメソッドがBeginメソッド内で同期的に呼び出されることがあり、驚くべき動作につながることがあることです。そうでない場合、完了コールバックはThreadPoolのランダムスレッドから実行されます。これはしばしばあなたが望むものではありません。 TPLメソッドはこれを回避します。
  • あなたの単純な使用例では、ブロッキングメソッドも完全にうまくいき、さまざまな非同期メソッドが複雑になることはありません。

TPLの方法とコード(未テスト)の読み出し側:あなたはどうなる

public async Task Initialize(string ip, int port) 
{ 
    tcpClient = new TcpClient; 
    await tcpClient.ConnectAsync(ip, port); 

    Console.WriteLine("Connected to: {0}:{1}", ip, port); 
} 

public async Task Read() 
{ 
    var buffer = new byte[4096]; 
    var ns = tcpClient.GetStream(); 
    while (true) 
    { 
     var bytesRead = await ns.ReadAsync(buffer, 0, buffer.Length); 
     if (bytesRead == 0) return; // Stream was closed 
     Console.WriteLine(Encoding.ASCII.GetString(buffer, 0, bytesRead)); 
    } 
} 

初期化部分で:

await client.Initialize(ip, port); 
// Start reading task 
Task.Run(() => client.Read()); 

同期メソッドは、すべてのAsync出現箇所を削除して使用するためとタスクをスレッドに置き換えます。

+0

'if(bytesRead == 0)return; 「私には間違っているようだ? – Sir

+0

いいです。 'NetworkStream.ReadAsync'は、ストリームがリモート側からクローズされたときに0を返します。 しかし、アプリケーションによっては、ここで例外を返すのではなく、ここで例外をスローする方が意味があります。これはほんの一例でした。 私の例で 'ReadAsync'の前に' await'が見つからなかったようです。私はそれを修正した。 – Matthias247

+0

リモート側からクローズドとはどういう意味ですか?それは、送信されたメッセージが完了した/ EOFに達したときとどのように比較されますか。しかし、今後もさらにメッセージが出る可能性があります。 現時点で問題を解決するために苦労しているのは、閉じられない接続でストリームから受信メッセージを読み続ける方法です。 MS docsには、永続的な接続の例はほとんどありません。読んだ直後に閉じます。 – Sir

関連する問題