让TcpClient等待写入数据

时间:2018-10-15 11:31:47

标签: c# .net tcpclient

我想通过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:从控制台实用程序调用:

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服务器代码(从某些stackoverflow问题中被盗):

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 :(得分:1)

最简单的部分是,回显服务器工作缓慢,因为它在每次读取后会暂停100ms。我想那是为了让您有机会看到正在发生的事情。

关于为什么看不到所有数据的原因,我不确定,但是我认为可能正在发生以下情况:

  • 当您的客户端执行离开using块时,将丢弃该流(由于Craig.Feied在他的answer中指出执行在底层套接字完成物理传输之前进行)数据)
  • 处置NetworkStream会导致其关闭基础Socket
  • 关闭为Socket提供了机会,可以在最终关闭之前完成所有缓冲数据的发送。参考:Graceful Shutdown, Linger Options, and Socket Closure
  • 请注意,NetworkStream本身没有缓冲数据,因为它会将所有写入直接传递给套接字。因此,即使在传输完成之前就将NetworkStream丢弃了,也不会丢失任何数据
  • 处于关闭状态的套接字可以完成现有请求,但不接受新请求。

因此,您的回显服务器从正在进行的传输中接收数据(好的),但是随后在连接上发出了新的写请求(不好)。我怀疑这种写操作导致回声服务器提前退出而没有读取所有内容。数据。要么:

  • 客户端关闭连接,因为它收到了意外数据,或者
  • 回显服务器在tClient.GetStream().WriteByte(pByte);上引发未捕获的异常

应该很容易检查是否确实是以上任何一个。

答案 1 :(得分:1)

回显服务器损坏。它在每个字节之后休眠100ms。这样,回复您的邮件将花费很长时间。

Available检查总是错误的。阅读前无需检查,无需睡觉。进行连接检查也无济于事,因为客户端可以在检查后立即断开连接。

我认为这应该可行:

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

其他所有内容都可以删除。

答案 2 :(得分:-1)

您的代码将一个异步过程包装在一个using块中,因此自然地,代码执行将继续,到达using块的末尾,然后首先处理该流,然后处理TcpClient -在基础之前套接字已完成数据的物理传输。如您所见,UsingAsync并不总是很好的匹配。

您的await将方法的其余部分注册为异步操作的继续,然后立即返回给调用方。 WriteAsync返回后,继续执行。

也许您期望WriteAsync仅在成功传输所有字节之后才返回。这似乎合乎逻辑,但不正确。当WriteAsync将数据写入流时,它会返回,但这并不意味着数据已通过TCPIP发出。 WriteAsync返回时,该流将仍在使用,并且套接字活动正在进行中。

由于您的连续内容不包含任何内容,因此您将立即退出using块,并在流仍在使用时对其进行处理。

因此,我认为您的客户端或流没有问题,将其刷新将无济于事。鉴于您的Echo server的行为,您正在从编写的代码中获得预期的行为。

您可以阅读Eric Lippert关于等待异步here的博客文章。

您可以阅读涉及类似问题here的另一个SO问题。

如果您不控制Echo Server,则基本上有两个选择。您可以放弃using,而不是只实例化客户端和流,然后在某些自然情况下使用它们,只有在(您的应用程序中稍后的某个地方)对自己有信心的情况下才使用它们close与他们完成。自然,您将需要某种方法来检测您是否完成了操作(或到达要关闭它们的位置,无论数据是否在某处成功接收)。

OTOH,如果您想保留using块,则必须在延续部分中放入一些代码 来测试完成情况,因此您无需关闭流以进行进一步的通信,例如WriteAsync将数据移交给套接字并请求套接字关闭后,立即执行。

如果您要发送的消息很小,并且有信心知道发送或过时将花费多长时间,则只需添加手册await Task.Delay() -但这是一种肮脏的方法,最终会导致问题。

或者您可以使用非异步的方法。异步方法一直是异步的,因此WriteAsync将调用或实现Stream.BeginWrite之类的异步方法,这些方法当然会在传输完成之前返回。诸如Write()之类的非异步方法将调用或实现诸如Stream.Write()之类的非异步流方法-仅在套接字实际发送完字节后才返回。缺少网络问题,这通常意味着即使您未在应用程序级别进行确认,也将接收您的数据。有关流,请参见Framework

  

Write方法将一直阻塞,直到发送请求的字节数或抛出SocketException。

当然,如果您拥有Echo server,那么您可以去那里看看为什么它不再与您分配的网络流进行通信时为何停止显示数据。