C # отправлять и получать сообщения через NetworkStream - простой код, но не работает должным образом - PullRequest
2 голосов
/ 12 марта 2012

Я пытаюсь написать небольшое приложение на C #, которое сможет отправлять и получать зашифрованные сообщения в режиме клиент / сервер.

Этот пример MSDN довольно близок к тому, что мне нужно (просто чтобы получить базовую функциональность) http://msdn.microsoft.com/en-us/library/as0w18af.aspx http://msdn.microsoft.com/en-us/library/te15te69.aspx

поэтому клиент шифрует сообщение, отправляет его на сервер, сервер расшифровывает его и записывает открытый текст в консоль.

все работает отлично, в этом направлении

однако, когда я пытаюсь отправить сообщение (не важно, зашифровано ли оно или незашифровано) с сервера на клиент, оно завершается неудачей.

Код сервера

using System;
using System.Net.Sockets;
using System.Threading;
using System.IO;
using System.Net;
using System.Security.Cryptography;

class Class1
{
   static void Main(string[] args)
   { 
      try
      {
         byte[] Key = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16};
         byte[] IV = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16};

         string ipAddress = "127.0.0.1";
         TcpListener TCPListen = new TcpListener(IPAddress.Parse(ipAddress),11000);

         TCPListen.Start();

         while(!TCPListen.Pending())
         {
            Console.WriteLine("Still listening. Will try in 5 seconds.");
            Thread.Sleep(5000);
         }

         TcpClient TCP = TCPListen.AcceptTcpClient();

         NetworkStream NetStream = TCP.GetStream();


         RijndaelManaged RMCrypto = new RijndaelManaged();



         CryptoStream CStream_READ = new CryptoStream(NetStream, RMCrypto.CreateDecryptor(Key, IV), CryptoStreamMode.Read);
         StreamReader SReader = new StreamReader(CStream_READ);
         Console.WriteLine("The decrypted original message: {0}", SReader.ReadToEnd());

// so far so good, but the next portion of the code does not run properly 

         CryptoStream CStream_WRITE = new CryptoStream(NetStream, RMCrypto.CreateEncryptor(Key,IV),CryptoStreamMode.Write);
         StreamWriter SWriter = new StreamWriter(CStream_WRITE);
         SWriter.WriteLine("message from server");
         SWriter.Flush();
         Console.WriteLine("The message was sent.");



         SReader.Close();
         SWriter.Close();
         NetStream.Close();
         TCP.Close();
      }
      //Catch any exceptions. 
      catch
      {
         Console.WriteLine("The Listener Failed.");
      }
   }
}

Код клиента

using System;
using System.IO;
using System.Security.Cryptography;
using System.Net.Sockets;

public class main
{
   public static void Main(string[] args)
   {
      try
      {

         TcpClient TCP = new TcpClient("localhost",11000);

         NetworkStream NetStream = TCP.GetStream();

         RijndaelManaged RMCrypto = new RijndaelManaged();

         byte[] Key = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16};
         byte[] IV = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16};

         CryptoStream CStreamWRITE = new CryptoStream(NetStream, RMCrypto.CreateEncryptor(Key, IV), CryptoStreamMode.Write);

         StreamWriter SWriter = new StreamWriter(CStreamWRITE);

         SWriter.WriteLine("Hello World!");
         SWriter.Flush();

         Console.WriteLine("The message was sent.");

  // so far so good, but the next portion of the code does not run properly 
         CryptoStream CStreamREAD = new CryptoStream(NetStream, RMCrypto.CreateDecryptor(Key,IV),CryptoStreamMode.Read);
         StreamReader SReader = new StreamReader(CStreamREAD);
         Console.WriteLine("od servera som dostal: {0}", SReader.ReadToEnd());

         SWriter.Close();
         SWriter.Close();
         CStreamWRITE.Close();
         NetStream.Close();
         TCP.Close();
      }
      catch
      {
         Console.WriteLine("The connection failed.");
      }
   }
}

Что я сделал, так это то, что я только что зеркально отразил код, который мне кажется уместным для выполнения функции поэтому сервер должен сначала получить сообщение от клиента, а затем отправить сообщение клиенту

если я закомментирую клиента, отправляющего сообщение на сервер, то сервер успешно отправит сообщение клиенту

Не могли бы вы помочь мне и скажите, почему это не работает в обоих направлениях одновременно, но отдельно это нормально?

Заранее спасибо

Ответы [ 2 ]

2 голосов
/ 12 марта 2012

Вы звоните ReadToEnd на получателя - однако отправитель не сказал , что он завершил отправку - отправитель закрывает поток и т. Д. после входящего сообщения, которое никогда не придет; по сути, вы зашли в тупик.

Существует много способов решения этой проблемы, в том числе:

  • с использованием префикса длины сообщения, чтобы получатель знал, какой объем данных ожидать заранее (и может соответствующим образом ограничить себя)
  • использование терминатора для сообщения (может быть, cr / lf), чтобы получатель знал, что он достиг конца сообщения; для текстового протокола может быть полезен ReadLine
  • закрытие исходящего потока

последнее простое, если вы отправляете только одно сообщение; просто используйте:

socket.Shutdown(SocketShutdown.Send);

после отправки сообщения, и получатель узнает, что больше не будет получать данные в сокете. Также обратите внимание, что вы должны очистить такие вещи, как криптопоток до того, как сделает это, чтобы гарантировать, что данные фактически все отправлены (а не буферизованы и не потеряны).

Лично я склонен использовать первый вариант (префикс длины), главным образом потому, что я обычно имею дело с двоичными протоколами с несколькими сообщениями.

0 голосов
/ 12 марта 2012

Насколько я знаю, Read() -метод на NetworkStream не блокируется, поэтому попытка чтения через ReadToEnd() сразу после отправки исходного сообщения не найдет никаких данных и, следовательно, не получит сообщение (возвращаетс пустой строкой).Попробуйте использовать свойство .DataAvailable для ожидания данных перед вызовом метода чтения.См. MSDN для примера.

Вторая проблема в вашем коде - это отсутствие FlushFinalBlock() после записи в CryptoStream, поскольку потоки сохраняют его состояние и в противном случае не отправляютчастичные криптоблоки, пока они не будут закрыты (обратите внимание, что это серьезная разница между Flush() и FlushFinalBlock()).Поэтому вам следует позвонить клиенту CStreamWRITE.FlushFinalBlock(), прежде чем ждать чтения ответа.

...