DataReader of SocketStream for UWP App

2019-09-08 14:09发布

问题:

I've seen a few questions similar to this, but so far I've not seen an actual solution that works for the UWP platform.

I'm connecting to some UPnP devices on the network, which send JSON back over port 1255. I have the initial connection working and the send request that tells the devices to send me some info.

StreamSocket socket;
using (socket = new StreamSocket())
{
    hostName = new HostName("192.168.0.nnn");
    await socket.ConnectAsync(hostName, "1255");
    //Code that does the request for info
    await this.read();
}

public async Task<String> read()
{
    string runStr = "";
    uint inBufferCnt = 0;

    using (reader = new DataReader(socket.InputStream))
    {
        reader.InputStreamOptions = Windows.Storage.Streams.InputStreamOptions.ReadAhead;
        reader.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
        reader.ByteOrder = Windows.Storage.Streams.ByteOrder.LittleEndian;

        inBufferCnt = await reader.LoadAsync(256); //InBuffer is always 256, 
            //even if there is more data waiting. If I put a task.delay in it will always return 256

        while (reader.UnconsumedBufferLength > 0)
        {
            var iRead = reader.ReadString(reader.UnconsumedBufferLength);
            runStr = runStr + iRead;
            inBufferCnt = await reader.LoadAsync(64); //always hangs here. 
                //Even if I reduce to 1, it will sit waiting for new data
        }
...

Everything is working fine, up till the last LoadAsync. While there is data in the stream, then as long as it's less than 64 bytes then it'll spin back around the While loop. If there is less than this tho, then it just sits there waiting for more data. So there could be 50 bytes sat in the buffer that I can't get hold of. In the above the inBufferCnt is always the exact size of the value passed to LoadAsync.

I've been trying various things, such as adding a timeout, but I can't access anything of use after the exception is triggered, so I still don't know how much data is in the stream.

CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(5000);
...
inBufferCnt = await reader.LoadAsync(64).AsTask(cts.Token);

I have no idea how much data I will get back from the device, I have no control whatsoever over the device communication (Other than to request info). I think I've read half of the web while looking for a solution, but I can't find anything that relates directly to this specific issue.

So the main question is "How are you supposed to read from the socket stream and then when there is no more data (at this time), just return what is sitting in the current stream. There could be any size left in the buffer waiting to be read.

Help...

回答1:

I have no idea how much data I will get back from the device, I have no control whatsoever over the device communication (Other than to request info). So the main question is "How are you supposed to read from the socket stream and then when there is no more data (at this time), just return what is sitting in the current stream. There could be any size left in the buffer waiting to be read.

According to your description, I have found a solution for you:

First, set the read options for the input stream to Partial.

reader.InputStreamOptions = Windows.Storage.Streams.InputStreamOptions.Partial;

By doing this, the asynchronous read operation completes when one or more bytes is available. So the asynchronous read operation sits and waits for new data only when there is no any bytes is available, then you can add a timeout to handle this situation as you have mentioned above.

CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(5000);
...
inBufferCnt = await reader.LoadAsync(sizeToReadEachTime).AsTask(cts.Token);

Following is the sample code I’ve verified:

public async Task<String> read()
{
    string runStr = "";
    uint inBufferCnt = 0;
    //size of bytes to load each time.
    uint sizeToReadEachTime = 256;
    CancellationTokenSource cts = new CancellationTokenSource();
    //set timeout here.
    cts.CancelAfter(5000);

    using (reader = new DataReader(socket.InputStream))
    {
        //set the read options for the input stream to Partial.
        reader.InputStreamOptions = Windows.Storage.Streams.InputStreamOptions.Partial;
        reader.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
        reader.ByteOrder = Windows.Storage.Streams.ByteOrder.LittleEndian;

        while (true)
        {
            try
            {
                //add a timeout for the asynchronous load operation.
                inBufferCnt = await reader.LoadAsync(sizeToReadEachTime).AsTask(cts.Token);
                runStr += reader.ReadString(inBufferCnt);
            }
            catch (System.Threading.Tasks.TaskCanceledException)
            {
                //load operation will get timeout only when there is no data available.
                cts.Dispose();
                break;
            }
        }

        reader.DetachStream();
    }
    return runStr;
}