Search code examples
c#.nettcpclient

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);
    }
}

Solution

  • 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.