Заставьте TcpClient ждать, пока данные не будут записаны - PullRequest
0 голосов
/ 15 октября 2018

Я хочу отправить данные по tcp на определенный ip \ port. Я написал пример, который должен отправить туда некоторую строку:

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

Проблема здесь в том, что мой поток удаляется до того, как клиент tcp получитотправил все данные.Как мне переписать мой код, чтобы дождаться записи всех данных и только после этого утилизировать все ресурсы?Спасибо

UPD: вызвано из консоли 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: код эхо-сервера (был украден из-за какого-то вопроса стекопотока):

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

Ответы [ 3 ]

0 голосов
/ 18 октября 2018

Самое простое, что эхо-сервер работает медленно, потому что он останавливается на 100 мс после каждого чтения.Я полагаю, что у вас есть шанс увидеть, что происходит.

Почему вы не видите все данные, я точно не уверен, но я думаю, что это может произойти:

  • Когда выполнение вашего клиента покидает блок using, поток удаляется (спасибо Craig.Feied в его ответе за указание, что выполнение продолжается до того, как нижележащие сокеты завершили физическую передачуданных)
  • Удаление NetworkStream заставляет его выполнить отключение для базового Socket
  • Завершение работы дает возможность для Socket завершить отправку любых буферизованных данных доэто наконец закрывается.Ссылка: Изящное завершение работы, параметры задержки и закрытие сокета
  • Обратите внимание, что данные не буферизуются самой NetworkStream, поскольку она передает все записи непосредственно в сокет.Таким образом, даже если NetworkStream удаляется до завершения передачи, данные не теряются
  • Закрытый сокет может завершить существующие запросы, но не будет принимать новые запросы.

Итак, ваш эхо-сервер получает данные о передаче, которая уже выполняется (хорошо), но затем выдает новый запрос на запись в соединение (не в порядке.) Я подозреваю, что эта запись вызывает эхо-серверрано выходить без чтения всех данных.Либо:

  • клиент закрывает соединение, потому что он получает данные, которых он не ожидает, либо
  • сервер эха выдает необработанное исключение на tClient.GetStream().WriteByte(pByte);

Должно быть легко проверить, действительно ли это одно из перечисленных выше.

0 голосов
/ 19 октября 2018

Эхо-сервер не работает.Он спит после каждого байта в течение 100 мс.Таким образом, ответ на ваше сообщение займет много времени.

Проверка Available всегда неверна.Не нужно проверять перед чтением и не нужно спать.Проверка на подключение также ничего не делает, потому что клиент может отключиться сразу после проверки.

Я думаю, что это должно работать:

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

Все остальное можно удалить.

0 голосов
/ 15 октября 2018

Ваш код оборачивает асинхронный процесс в блок using, поэтому, естественно, выполнение кода продолжается, достигает конца блока using и удаляет сначала поток, а затем TcpClient - до того, как нижележащие сокеты завершатфизическая передача данных.Using и Async не всегда соответствуют друг другу, как вы видели.

Ваш await регистрирует остаток метода как продолжение асинхронной операции, а затем возвращается кзвонящий сразу.Продолжение выполняется, как только WriteAsync возвращает.

Возможно, вы ожидали, что WriteAsync вернется только после успешной передачи всех байтов.Это кажется логичным, но не правильно.WriteAsync возвращается, когда записывает данные в поток, но это не означает, что данные вышли через TCPIP.Поток все еще будет использоваться с активностью сокета, продолжающейся, когда WriteAsync возвращает.

Поскольку ваше продолжение не содержит ничего, вы немедленно выходите из блока using, и поток удаляется, пока он еще используется.

Так что я думаю, что с вашим клиентом или вашим потоком все в порядкеи промывка не поможет.Вы получаете именно то поведение, которое ожидается от кода, который вы написали, учитывая поведение вашего Echo server.

. Вы можете прочитать сообщение в блоге Эрика Липперта о await async здесь .

Вы можете прочитать еще один вопрос SO, связанный с аналогичной проблемой здесь .

Если вы не контролируете Echo Server, тогда у вас в основном есть два варианта.Вы можете отказаться от using - вместо этого просто создайте экземпляр клиента и потока, а затем используйте их при некотором естественном наборе обстоятельств и только close их, когда (где-то позже в вашем приложении) вы уверены, что с ними покончено,Естественно, вам понадобится какой-то способ определить, что вы сделали (или добраться до некоторой точки, где вы хотите закрыть их, независимо от того, были ли данные успешно получены где-то).

OTOH, если вы хотите сохранить using block, тогда вы должны поместить некоторый код в свое продолжение для проверки на завершение, поэтому вы не закрываете поток для дальнейшей связи, как только WriteAsync передает данные сокету и запрашивает отключение сокета.,

Если вы отправляете очень маленькие сообщения и уверены, что знаете, сколько времени потребуется, чтобы они были отправлены или устарели, вы можете просто добавить руководство await Task.Delay() - но это грязный подход, которыйв конечном итоге вызовет проблемы.

Или вы можете использовать метод, который не является асинхронным.Асинхронные методы полностью асинхронны, поэтому WriteAsync будет вызывать или реализовывать асинхронные методы, такие как Stream.BeginWrite, которые, конечно, возвращаются до завершения передачи.Не асинхронные методы, такие как Write(), будут вызывать или реализовывать не асинхронные потоковые методы, такие как Stream.Write(), которые возвращаются только после того, как сокет фактически отправил байт.Отсутствие проблем с сетью, это чаще всего означает, что ваши данные будут получены, даже если вы не делаете подтверждения на уровне приложения.См. Framework для потоков.

Метод Write блокируется до тех пор, пока не будет отправлено запрошенное количество байтов или не сгенерировано исключение SocketException.

КонечноЕсли у вас есть Echo server, вы можете посмотреть, почему он перестает отображать данные, когда он больше не может обмениваться данными с вашим удаленным сетевым потоком.

...