Olha Shumeliuk
Olha Shumeliuk

Reputation: 740

Make TcpClient wait untill data is written

I want to send data over tcp to a specific ip\port I've written a sample which should send there some string:

internal class TcpSender : BaseDataSender
{
    public TcpSender(Settings settings) : base(settings)
    {
    }

    public async override Task SendDataAsync(string data)
    {
        Guard.ArgumentNotNullOrEmptyString(data, nameof(data));

        byte[] sendData = Encoding.UTF8.GetBytes(data);
        using (var client = new TcpClient(Settings.IpAddress, Settings.Port))
        using (var stream = client.GetStream())
        {
            await stream.WriteAsync(sendData, 0, sendData.Length);
        }
    }
}

The issue here that my stream is disposed before tcp client have sent all the data. How should I rewrite my code to wait all data to be written and only after that dispose all resources? Thanks

UPD: called from console util:

static void Main(string[] args)
{
    // here settings and date are gotten from args
    try
    {
        GenerateAndSendData(settings, date)
                .GetAwaiter()
                .GetResult();
    }
    catch (Exception e)
    {
        Console.ForegroundColor = ConsoleColor.Red;
        Console.WriteLine(e);
    }
}

public static async Task GenerateAndSendData(Settings settings, DateTime date)
{
    var sender = new TcpSender(settings);
    await sender.SendDataAsync("Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.");
}

Upd2: Echo server code (was stolen from some stackoverflow question):

class TcpEchoServer
{
    static TcpListener listen;
    static Thread serverthread;

    public static void Start()
    {
        listen = new TcpListener(System.Net.IPAddress.Parse("127.0.0.1"), 514);
        serverthread = new Thread(new ThreadStart(DoListen));
        serverthread.Start();
    }

    private static void DoListen()
    {
        // Listen
        listen.Start();
        Console.WriteLine("Server: Started server");

        while (true)
        {
            Console.WriteLine("Server: Waiting...");
            TcpClient client = listen.AcceptTcpClient();
            Console.WriteLine("Server: Waited");

            // New thread with client
            Thread clientThread = new Thread(new ParameterizedThreadStart(DoClient));
            clientThread.Start(client);
        }
    }

    private static void DoClient(object client)
    {
        // Read data
        TcpClient tClient = (TcpClient)client;

        Console.WriteLine("Client (Thread: {0}): Connected!", Thread.CurrentThread.ManagedThreadId);
        do
        {
            if (!tClient.Connected)
            {
                tClient.Close();
                Thread.CurrentThread.Abort();       // Kill thread.
            }

            if (tClient.Available > 0)
            {
                byte pByte = (byte)tClient.GetStream().ReadByte();
                Console.WriteLine("Client (Thread: {0}): Data {1}", Thread.CurrentThread.ManagedThreadId, pByte);
                tClient.GetStream().WriteByte(pByte);
            }

            // Pause
            Thread.Sleep(100);
        } while (true);
    }
}

Upvotes: 3

Views: 1710

Answers (3)

Craig.Feied
Craig.Feied

Reputation: 2860

Your code wraps an async process in a using block, so naturally code execution continues, hits the end of the using block, and disposes first the stream and then the TcpClient -- before the underlying sockets have finished the physical transmission of data. Using and Async aren't always a good match, as you have seen.

Your await signs up the remainder of the method as the continuation for the async operation, and then it returns to the caller immediately. The continuation is executed as soon as WriteAsync returns.

Perhaps you were expecting that WriteAsync would return only after all the bytes had been successfully transmitted. That seems logical but is not correct. WriteAsync returns when it has written data to the stream, but that doesn't mean the data has gone out over TCPIP. The stream will still be in use with socket activity ongoing when WriteAsync returns.

Since your continuation contains nothing, you exit the using block immediately and the stream gets disposed while it's still in use.

So I think there is nothing wrong with your client or your stream, and flushing it won't help. You are getting exactly the behavior expected from the code you've written, given the behavior of your Echo server.

You can read Eric Lippert's blog post about await async here.

You can read another SO question involving a similar problem here.

If you don't control the Echo Server then you basically have two options. You can give up using -- instead just instantiate the client and the stream, then use them over some natural set of circumstances, and only close them when (somewhere later in your application) you are confident you are done with them. Naturally you will need some way to detect that you are done (or get to some point where you want to close them whether or not the data was successfully received somewhere).

OTOH, if you want to retain the using block then you must put some code inside your continuation to test for completion, so you don't close the stream for further communications as soon as WriteAsync has handed off the data to the socket and requested socket shutdown.

If you are sending very small messages and are confident you know how long it will take for them to be sent or become stale, then you could just add a manual await Task.Delay() -- but that's a dirty approach that will eventually cause problems.

Or you could use a method that is not asynchronous. The async methods are async all the way down, so WriteAsync will call or implement async methods like Stream.BeginWrite, which of course returns before transmission is complete. The Non-async methods, such as Write() will call or implement non-async stream methods like Stream.Write() -- which return only after the byte has actually been sent by the socket. Absent network problems, this most often means your data will be received even if you aren't doing acknowledgement at the application level. See the Framework for streams.

The Write method blocks until the requested number of bytes is sent or a SocketException is thrown.

Of course, if you own the Echo server then you can look there to see why it stops showing data when it can no longer communicate with your disposed network stream.

Upvotes: -1

usr
usr

Reputation: 171246

The echo server is broken. It sleeps after every byte for 100ms. That way it will take a long time to reply to your message.

The Available check is always wrong. No need check before reading and not need to sleep. The check for being connected also does nothing because the client could disconnect right after the check.

I think this should work:

tClient.GetStream().CopyTo(tClient.GetStream());

Everything else can be deleted.

Upvotes: 1

pere57
pere57

Reputation: 677

The easy part is that the echo server works slowly because it pauses for 100ms after each read. I guess that's so you get a chance to see what's happening.

For why you don't see all the data, I'm not sure exactly, but what I think might be happening is:

  • When your client execution leaves the using block, the stream is disposed (thanks to Craig.Feied in his answer for pointing out that execution proceeds before the underlying sockets have finished the physical transmission of the data)
  • Disposing the NetworkStream causes it to issue a shutdown to the underlying Socket
  • The shutdown gives the chance for the Socket to finish sending any buffered data before it finally closes. Reference: Graceful Shutdown, Linger Options, and Socket Closure
  • Note there is no data buffered by the NetworkStream itself as it passes all writes directly to the socket. So even if the NetworkStream is disposed before the transfer completes no data is lost
  • A socket in shutdown can complete existing requests but won't accept new requests.

So, your echo server receives data from the transfer already in progress (okay) but then issues a new write request on the connection (not okay.) I suspect this write is causing the echo server to exit early without reading all the data. Either:

  • the client closes the connection because it receives data it's not expecting, or
  • the echo server throws an uncaught exception on tClient.GetStream().WriteByte(pByte);

It should be easy to check if it is indeed either of the above.

Upvotes: 1

Related Questions