简体   繁体   中英

NetworkStream Async Read -> Cancel

Currently I try to read and write Async to/from a network stream. My software is the Client part and the server can send informations on its own or respond to commands I send him.

So I need a socket which

  • reads all the time (in case the server sends status informations)
  • stops reading when I want to send commands (commands can be sequences of data with multible Write and Read operations)

So I thought it would be a good approach to create a Semaphore and a Background Task which handles the server sent messages and in case I want to send a command I block the semaphore and have full access to read/write operations to the socket.

Here is what I do currently.

private TcpClient _tcpClient = new TcpClient();
protected SemaphoreSlim ClientSemaphore { get; } = new SemaphoreSlim(1, 1);

public async Task ConnectAsync()
{
    if (_tcpClient.Connected)
    {
        await DisconnectAsync();
    }
    await _tcpClient.ConnectAsync(Hostname, RemotePort);

    //here the background Task is started
    _ = AutoReceiveMessages();
}

private async Task AutoReceiveMessages()
{
    while (_tcpClient.Connected)
    {
        //enter and lock semaphore
        await ClientSemaphore.WaitAsync();
        try
        {
            //read from socket until timeout (ms)
            var msg = await ReadFromSocket(2000);
            foreach (var cmd in SplitMessageInTelegrams(msg))
            {
                Console.WriteLine("MESSAGE --> " + cmd);                    
            }
        }
        catch (Exception ex)
        {
        }
        finally
        {
            //release semaphore
            ClientSemaphore.Release();
        }
    }
}

private async Task<string> ReadFromSocket(double timeout = 0)
{
    var buf = new byte[4096];
    var stream = _tcpClient.GetStream();

    //read from stream or timeout
    var amountReadTask = stream.ReadAsync(buf, 0, buf.Length);
    var timeoutTask = Task.Delay(TimeSpan.FromMilliseconds(timeout));

    await Task.WhenAny(timeoutTask, amountReadTask)
              .ConfigureAwait(false);

    //timeout
    if (!amountReadTask.IsCompleted)
    {
        throw new TimeoutException("Timeout");
    }

    //no timeout
    return Encoding.ASCII.GetString(buf, 0, amountReadTask.Result);
}

But this do not work as I expected... I use this methode to send a message to the server and in WireShark I see the server resonds with the same message

protected async Task SendTelegramAsync(ITelegram telegram)
{
    await ClientSemaphore.WaitAsync();
    try
    {
        _ = telegram ?? throw new ArgumentException($"{nameof(telegram)}");
        if (!_tcpClient.Connected) throw new InvalidOperationException("Socket not connected!");

        var buf = new byte[4096];
        var stream = _tcpClient.GetStream();
        var msg = Encoding.ASCII.GetBytes("\x02" + telegram.GetCommandMessage() + "\x03");

        Console.WriteLine("WRITE --> " + msg);
        await stream.WriteAsync(msg, 0, msg.Length);

        //comment AutoReceiveMessage and remove comment from this
        //and I get responses from the server
        //var test = await ReadFromSocket(2000);
    }
    finally
    {
        ClientSemaphore.Release();
    }
}

I know in this case I do not need the semaphore but later I want to create sequences so one command consists of multible writes and reads and as long as the command is executed I do not want to use the AutoReceiveMessages method.

The problem now is

  • If I use it like this I never get a response the ReadFromSocket method always get the timeout even when wireshark tell me the server has responded
  • But even better if I disable AutoReceiveMessages (just comment _ = AutoReceiveMessages()) and use ReadFromSocket directly in SendTelegramAsync() everything work as expected.

So I think the problem is something related to the background task and the ReadAsync but I couldnt figure it out...

Got It!

stream.DataAvailable is your friend (or my friend :)).

If I check before the ReadAsync if DataIsAvailable then I have no problem anymore.

if (_tcpClient.GetStream().DataAvailable)
    var msg = await ReadFromSocket(DEFAULT_TIMEOUT);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM