Martin
Martin

Reputation: 21

C# send & receive messages via NetworkStream -- simple code but does not work as expected

I am trying to write a small C# application that will be able to send and receive encrypted messages in a client/server manner.

This MSDN example is pretty close to something I need (just to get the basic functionality) http://msdn.microsoft.com/en-us/library/as0w18af.aspx http://msdn.microsoft.com/en-us/library/te15te69.aspx

so the client encrypts the message, sends it to the server, the server decrypts it and writes the cleartext to console.

everything works fine, in this one direction

however when I try to send the message (does not matter if encrypted or cleartext) from the server to the client, it fails.

Server code

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.");
      }
   }
}

Client code

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.");
      }
   }
}

What I did is that I just mirrored the code I think is relevant to perform the function so the server is supposed to 1st receive the message from the client and then send message to the client

if I comment out the client sending the message to the server, then server successfully sends the message to the client

could you please help me out and tell me why it does not work in both directions at one time but separately it is OK?

Thanks in advance

Upvotes: 2

Views: 12262

Answers (2)

Marc Gravell
Marc Gravell

Reputation: 1064114

You are calling ReadToEnd on the receiver - however, the sender has not said that it has finished sending - the sender only closes the stream etc after the incoming message that will never come; basically, you've deadlocked yourself.

There are many ways to approach this type of problem, including:

  • using a length-prefix to a message so the receiver knows how much data to expect in advance (and can limit itself accordingly)
  • using a terminator to a message (maybe a cr/lf) so the receiver knows it has reached the end of a message; for a text protocol, ReadLine may then be useful
  • closing the outbound stream

the last is simple if you are only sending one message; just use:

socket.Shutdown(SocketShutdown.Send);

after sending the message, and the receiver will know it isn't going to get any more data on the socket. Note also that you must clear down things like the crypto stream before doing this, to ensure the data is actually all sent (and not buffered and lost).

Personally, I tend to use the first option (length prefix), mainly because I'm usually dealing in multi-message binary protocols.

Upvotes: 3

Stefan
Stefan

Reputation: 308

As far as i know The Read()-method on a NetworkStream is not blocking, so trying to read via ReadToEnd() immediately after sending the initiating message will not find any data and thus not receive a message (returns with an empty string). Try using the .DataAvailable-property to wait for data before calling a reading method. See MSDN for an example.

The second issue in your code is the missing of a FlushFinalBlock() after writing to the CryptoStream, as the streams preserves it state and does otherwise not send out partial crypto-blocks until it is closed (note that this is a severe difference between Flush() and FlushFinalBlock()). So you should call the CStreamWRITE.FlushFinalBlock() on the client before waiting for reading an answer.

Upvotes: 0

Related Questions