Create an empty stream from a string on which Stre

2019-09-07 23:59发布

问题:

I am trying to replace the stream i get from TcpClient.GetStream() by a stream I create from a string.

I am using the following method to create said Stream:

public Stream GenerateStreamFromString(string s)
{
    MemoryStream stream = new MemoryStream();
    StreamWriter writer = new StreamWriter(stream);
    writer.Write(s);
    writer.Flush();
    stream.Position = 0;
    return stream;
}

However this stream is read by using Stream.Read() somewhere in a library i do not WANT to change. The problem is I create this stream with an empty string because the object needs a stream to start up, normally when using the TcpClient stream it would stop at Stream.Read() until it would have things to read but not with the stream i created from my string.

So my question, how do I create an empty stream to which i can later add data from a string?

回答1:

Using internally a BlockingCollection<> as a queue, you could write something like:

public class WatitingStream : Stream
{
    private BlockingCollection<byte[]> Packets = new BlockingCollection<byte[]>();

    private byte[] IncompletePacket;
    private int IncompletePacketOffset;

    public WatitingStream()
    {
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            Packets.CompleteAdding();
        }

        base.Dispose(disposing);
    }

    public override bool CanRead
    {
        get { return Packets.IsCompleted; }
    }

    public override bool CanSeek
    {
        get { return false; }
    }

    public override bool CanWrite
    {
        get { return Packets.IsAddingCompleted; }
    }

    public override void Flush()
    {
    }

    public override long Length
    {
        get
        {
            throw new NotSupportedException();
        }
    }

    public override long Position
    {
        get
        {
            throw new NotSupportedException();
        }
        set
        {
            throw new NotSupportedException();
        }
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        if (count == 0)
        {
            return 0;
        }

        byte[] packet;
        int packetOffset;

        if (IncompletePacket != null)
        {
            packet = IncompletePacket;
            packetOffset = IncompletePacketOffset;
        }
        else
        {
            if (Packets.IsCompleted)
            {
                return 0;
            }

            packet = Packets.Take();
            packetOffset = 0;
        }

        int read = Math.Min(packet.Length - packetOffset, count);
        Buffer.BlockCopy(packet, packetOffset, buffer, offset, read);

        packetOffset += read;

        if (packetOffset < packet.Length)
        {
            IncompletePacket = packet;
            IncompletePacketOffset = packetOffset;
        }
        else
        {
            IncompletePacket = null;
            IncompletePacketOffset = 0;
        }

        return read;
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        throw new NotSupportedException();
    }

    public override void SetLength(long value)
    {
        throw new NotSupportedException();
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        if (count == 0)
        {
            return;
        }

        byte[] packet = new byte[count];
        Buffer.BlockCopy(buffer, offset, packet, 0, count);
        Packets.Add(packet);
    }
}

You use it as a normal stream. The Write doesn't block. The Read blocks.

Some decisions had to be taken: this Stream is "packet" based. It won't Write zero-length packets, and a Read will return the data of one packet. The Read won't continue on the next packet. If there is data remaining in a packet after a Read, that data is saved for the next Read. The Dispose() will stop the Write (so if the "client" does a Dispose() before the "server", the server will get an exception if it tries to do a Write). If the "server" does the Dispose() first, the "client" can finish reading the packets still present. Clearly it is possible (easy) to split this class in two classes (one Server and one Client), where the Server keeps the BlockingCollection<> and the Client has a reference to the "server". This would solve the "Dispose()" anomaly/problem (but would double the code size :-) )



回答2:

As I said in a comment to your other question, you'd probably need to use a pipe. Since you couldn't be bothered, given that hint, to try something out, here's a basic example:

using System;
using System.IO;
using System.Threading.Tasks;
using System.IO.Pipes;

namespace ConsoleApplication10
{
    class Program
    {
        static void Main(string[] args)
        {
            var sending = new AnonymousPipeServerStream(PipeDirection.Out);
            var receiving = new AnonymousPipeClientStream(PipeDirection.In, sending.ClientSafePipeHandle);
            var ds = new DoStuff(new StreamReader(receiving));

            System.Threading.Thread.Sleep(500);

            Console.WriteLine("Not sent yet");
            var other = new StreamWriter(sending);

            other.WriteLine("Hello!");
            other.Flush();

            Console.WriteLine("Sent");

            Console.ReadLine();
        }
    }

    class DoStuff
    {
        public DoStuff(StreamReader s)
        {
            Task.Run(() =>
            {
                Console.WriteLine("About to receive");
                var k = s.ReadLine();
                Console.WriteLine("Received");
                Console.WriteLine(k);
                Console.WriteLine("Task done");
            });
        }
    }
}

Which prints:

About to receive
Not sent yet
Sent
Received
Hello!
Task done

(Other display orders are possible, but Received will always appear after Not sent yet).

As you can see, the task that wanted to receive text called ReadLine() well before the code which sent the string made its call to WriteLine(). Depending on your use-case, you may either choose to remove the Flush call at the sending end (you're sending lots of text and it can just buffer up) or turn AutoFlush on (you want each line to go to the receiver as soon as possible)