4

I'm using a NamedPipeStream, client and server, I'm sending data from client to server, the data is a serialize object which include binary data.

When the server side receive the data, it always have MAX 1024 size while client send much more!! so when try to serialize the data, this cause the following exception: "Unterminated string. Expected delimiter: ". Path 'Data', line 1, position 1024."

the server buffer size defined as:

protected const int BUFFER_SIZE = 4096*4;
var stream = new NamedPipeServerStream(PipeName,
                                                   PipeDirection.InOut,
                                                   1,
                                                   PipeTransmissionMode.Message,
                                                   PipeOptions.Asynchronous,
                                                   BUFFER_SIZE,
                                                   BUFFER_SIZE,
                                                   pipeSecurity);


        stream.ReadMode = PipeTransmissionMode.Message;

I'm using :

    /// <summary>
    /// StreamWriter for writing messages to the pipe.
    /// </summary>
    protected StreamWriter PipeWriter { get; set; }

The read function:

/// <summary>
/// Reads a message from the pipe.
/// </summary>
/// <param name="stream"></param>
/// <returns></returns>
protected static byte[] ReadMessage(PipeStream stream)
{
    MemoryStream memoryStream = new MemoryStream();

    byte[] buffer = new byte[BUFFER_SIZE];

    try
    {
        do
        {
            if (stream != null)
            {
                memoryStream.Write(buffer, 0, stream.Read(buffer, 0, buffer.Length));
            }

        } while ((m_stopRequested != false) && (stream != null) && (stream.IsMessageComplete == false));
    }
    catch
    {
        return null;
    }
    return memoryStream.ToArray();
}


protected override void ReadFromPipe(object state)
{
    //int i = 0;
    try
    {
        while (Pipe != null && m_stopRequested == false)
        {
            PipeConnectedSignal.Reset();

            if (Pipe.IsConnected == false)
            {//Pipe.WaitForConnection();
                var asyncResult = Pipe.BeginWaitForConnection(PipeConnected, this);

                if (asyncResult.AsyncWaitHandle.WaitOne(5000))
                {
                    if (Pipe != null)
                    {
                        Pipe.EndWaitForConnection(asyncResult);
                        // ...
                        //success;
                    }
                }
                else
                {
                    continue;
                }
            }
            if (Pipe != null && Pipe.CanRead)
            {
                byte[] msg = ReadMessage(Pipe);

                if (msg != null)
                {
                    ThrowOnReceivedMessage(msg);
                }
            }
        }
    }
    catch (System.Exception ex)
    {
        System.Diagnostics.Debug.WriteLine(" PipeName.ReadFromPipe Ex:" + ex.Message);
    }
}

I don't see in the client side where I can define or change the buffer size!

Any idea?!

Joseph
  • 1,716
  • 3
  • 24
  • 42
  • 1
    Buffer size doesn't really matter much - you're dealing with *streams* of data. How are you handling the serialization and deserialization? – Luaan Aug 11 '15 at 07:53
  • What does the client code look like? – alexm Aug 11 '15 at 07:58
  • @Luaan: Message mode is somewhat special. A quota from MSDN regarding it: "The pipe treats the bytes written during each write operation as a message unit" – alexm Aug 11 '15 at 08:10
  • @Joseph: On the client is there a single write from an array with properly formatted message? – alexm Aug 11 '15 at 08:17
  • @alexm True, but it's still a stream of stream(-message)s - you just have to keep reading over and over until you get `PipeStream.IsMessageComplete`. You will never read more than one message per `Read`, but the message *can* be fragmented. – Luaan Aug 11 '15 at 08:20
  • 1
    @Luaan: thanks, you just uncovered a bug in my code :) – alexm Aug 11 '15 at 08:29
  • @alexm there is a single write in client: this.Pipe.Write(myObject.Message);, Message{ get{ return JsonConvert.SerializeObject(this);}} – Joseph Aug 11 '15 at 08:31
  • @Luaan: I serialize the object by: JsonConvert.SerializeObject(this); – Joseph Aug 11 '15 at 08:32
  • @Joseph Wait, `Message` is a string? How do you serialize that? If you want help, you should really post the minimal reproducible example of your issue. – Luaan Aug 11 '15 at 08:44
  • @Luaan, what the problem, the object I'm sending is serialazable, and have a function Message() that return JsonConvert.SerializeObject(this);} – Joseph Aug 11 '15 at 08:49
  • @Joseph Yeah, but doesn't `SerializeObject` return a `string`? How are you converting it to a `byte[]`? – Luaan Aug 11 '15 at 08:50
  • StreamWriter.WriteLine can get string as well !!!, no need to do anything! – Joseph Aug 11 '15 at 08:54
  • 1
    @Joseph You never said you're using `StreamWriter`, that's exactly the kind of information *we've been asking for from the very beginning*. That's in fact your problem! It will issue several separate `Write`s, which will result in separate messages. – Luaan Aug 11 '15 at 09:06
  • @Luaan OK, sorry , but in client side I wait until compleate: while ( (stream != null) && (stream.IsMessageComplete == false)); – Joseph Aug 11 '15 at 09:12
  • Yes, but the message *is* complete as far as the pipe is concerned - it doesn't know or care about your `StreamWriter`, it only cares about calls to the pipe streams `Write` - and the `StreamWriter` is going to call it multiple times, resulting in multiple messages. – Luaan Aug 11 '15 at 09:13
  • http://referencesource.microsoft.com/#mscorlib/system/io/streamwriter.cs,62bd8ad495f57b21 – Hans Passant Aug 11 '15 at 10:21

1 Answers1

6

The basic problem is that you didn't read enough. You need to repeat the read operation if PipeStream.IsMessageComplete is false, and keep doing that until it returns true - that tells you the whole message has been read. Depending on your deserializer, you might need to store the data in a buffer of your own, or create some wrapper stream to handle this for you.

A simple example of how this could work for simple string deserialization:

void Main()
{
  var serverTask = Task.Run(() => Server()); // Just to keep this simple and stupid

  using (var client = new NamedPipeClientStream(".", "Pipe", PipeDirection.InOut))
  {
    client.Connect();
    client.ReadMode = PipeTransmissionMode.Message;

    var buffer = new byte[1024];
    var sb = new StringBuilder();

    int read;
    // Reading the stream as usual, but only the first message
    while ((read = client.Read(buffer, 0, buffer.Length)) > 0 && !client.IsMessageComplete)
    {
      sb.Append(Encoding.ASCII.GetString(buffer, 0, read));
    }

    Console.WriteLine(sb.ToString());
  }
}

void Server()
{
  using (var server
    = new NamedPipeServerStream("Pipe", PipeDirection.InOut, 1, 
                                PipeTransmissionMode.Message, PipeOptions.Asynchronous)) 
  {
    server.ReadMode = PipeTransmissionMode.Message;      
    server.WaitForConnection();

    // On the server side, we need to send it all as one byte[]
    var buffer = Encoding.ASCII.GetBytes(File.ReadAllText(@"D:\Data.txt"));
    server.Write(buffer, 0, buffer.Length); 
  }
}

Just as a side-note - I can easily read or write as much data as I want at once - the limiting factor is the buffer I use, not the one the pipes use; although I'm using a local named pipe, it might be different for a TCP pipe (though it'd be a bit annoying - it's supposed to be abstracted away from you).

EDIT:

Okay, now it's finally obvious what your problem is. You can't use a StreamWriter - when sending a message long enough, it will result in multiple Write calls on the pipe stream, resulting in multiple separate message for your data. If you want the whole message as a single message, you must use a single Write call. For example:

var data = Encoding.ASCII.GetBytes(yourJsonString);
Write(data, 0, data.Length);

The 1024-long buffer is StreamWriters, it has nothing to do with the named pipes. Using StreamWriter/StreamReader is a bad idea in any networking scenario, even when working with raw TCP streams. It just isn't what it's designed for.

Luaan
  • 62,244
  • 7
  • 97
  • 116
  • the server do that, it is in loop :while ( (stream != null) && (stream.IsMessageComplete == false)); – Joseph Aug 11 '15 at 08:42
  • @Joseph The `stream != null` seems unnecessary. In any case, can you just post your "read the stream and call the deserializer" code? It's probably where the problem is. – Luaan Aug 11 '15 at 08:46
  • @Joseph I'm pretty sure the problem is on the *sending* side, not the receiving side. My answer already says where I think the problem is and how to fix it, can't you just try it? There's still weird stuff on the receiving side (why do you keep checking the stream for null all the time? It can't *become* null. Why do you use the asynchronous connect just to wait on it synchronously?), but nothing game-breaking. – Luaan Aug 11 '15 at 10:55
  • now I see your update, and now it seems the answer to my issue, I'll fix my side and update (mark as answer) about the results! – Joseph Aug 11 '15 at 11:27