【问题标题】:C# Why doesn't "Flush" force the bytes down the network stream?C# 为什么“Flush”不强制字节流到网络流中?
【发布时间】:2011-06-27 20:16:34
【问题描述】:

我有一个项目,我试图将序列化对象发送到服务器,然后等待“OK”或“ERROR”消息返回。

我似乎遇到了与以下海报类似的问题:TcpClient send/close problem

问题是我似乎能够发送原始对象的唯一方法是关闭连接,但是(当然)我迫不及待地想看看服务器是否成功处理了对象。

private void button4_Click(object sender, EventArgs e)
{
    RequestPacket req = new RequestPacket();

    /// ... Fill out request packet ...

    /// Connect to the SERVER to send the message...
    TcpClient Client = new TcpClient("localhost", 10287);
    using (NetworkStream ns = Client.GetStream())
    {
        XmlSerializer xml = new XmlSerializer(typeof(RequestPacket));
        xml.Serialize(ns, req);

        /// NOTE: This doesn't seem to do anything.... 
        ///       The server doesn't get the object I just serialized.
        ///       However, if I use ns.Close() it does... 
        ///          but then I can't get the response.
        ns.Flush();

        // Get the response. It should be "OK".
        ResponsePacket resp;

        XmlSerializer xml2 = new XmlSerializer(typeof(ResponsePacket));
        resp = (ResponsePacket)xml2.Deserialize(ns);


        /// ... EVALUATE RESPONSE ...
    }

    Client.Close()
}

更新:回应一位评论者,我认为客户不会有错。它只是在等待对象,并且对象永远不会出现,直到我关闭套接字....但是,如果我错了,我会很乐意公开吃乌鸦。 =) 这是客户端:

    static void Main(string[] args)
    {
        // Read the port from the command line, use 10287 for default
        CMD cmd = new CMD(args);
        int port = 10287;

        if (cmd.ContainsKey("p")) port = Convert.ToInt32(cmd["p"]);

        TcpListener l = new TcpListener(port);
        l.Start();

        while (true)
        {
            // Wait for a socket connection.
            TcpClient c = l.AcceptTcpClient();
            
            Thread T = new Thread(ProcessSocket);

            T.Start(c);
        }
    }


    static void ProcessSocket(object c)
    {
        TcpClient C = (TcpClient)c;

        try
        {
            RequestPacket rp;
            //// Handle the request here.
            using (NetworkStream ns = C.GetStream())
            {
                XmlSerializer xml = new XmlSerializer(typeof(RequestPacket));
                rp = (RequestPacket)xml.Deserialize(ns);
            }

            ProcessPacket(rp);
        }
        catch
        {
            // not much to do except ignore it and go on.
        }
    }

是的....就是这么简单。

【问题讨论】:

  • 1.是否调用 ProcessPacket? 2. ProcessSocket 中是否抛出任何异常(至少将异常写入日志或控制台。不要只是吃掉它)。 3. 响应是如何发回的?
  • ProcessPacket 行永远不会被命中。我在那里有一个断点。
  • 您是否尝试过使用C.GetStream().Read() 代替?您是否尝试过记录您确实得到的异常?阅读面向流与面向消息的传输协议,您就会明白为什么。
  • A) 我没有遇到异常。我什么也得不到。 B) C.GetStream().Read() 与我所做的有何不同? C)我已经把它分解成它的核心元素(Read(byteBuffer),Write(byteBuffer),WriteLine(byteBuffer)......)无济于事。
  • 您是否尝试过进行 WireShark 捕获以准确查看字节何时通过网络发送(这将需要在一台机器上运行客户端并在另一台机器上运行服务器)?有可能数据正在物理上被发送,但接收流正在缓冲该数据并且在连接被发起者终止之前不释放它。

标签: c# sockets tcp tcpclient networkstream


【解决方案1】:

您应该使用链接到您的网络流的 StreamWriter/Reader,.Flush 对 NetworkStream 没有任何作用,请参见此处:

http://www.c-sharpcorner.com/UploadFile/dottys/SocketProgDTRP11222005023030AM/SocketProgDTRP.aspx

【讨论】:

  • 如果 stream 上的刷新是惰性的,则添加额外的层(读取器/写入器)不会改变任何内容。
  • 我也试过了......仍然没有这样的运气。这也是我最初的想法。
【解决方案2】:

呃哦,你可以责怪Nagle's algorithm。虽然它与 C# 无关,但它是 TCP/IP 堆栈的默认行为。使用SetSocketOption 方法启用NoDelay 套接字选项。但请注意,禁用 Nagle 算法会降低吞吐量。

我也不确定您在套接字上使用的那个流,因为我根本不是 C# 开发人员,但请尝试删除它的实例,以便它确实可以写入 :-)

【讨论】:

  • @Jerry 我在与工业自动化设备通信时遇到了几乎相同的问题,设置NoDelay 属性对我有用,尽管我也没有使用GetStream 方法。
  • 你能在同一个流上来回进行双向通信吗?即使使用 NoDelay 选项,我也无法做到这一点。
  • Nagle 的算法在套接字关闭之前不会延迟数据包。最大延迟约为 200 毫秒。不足以解释这个问题。
【解决方案3】:

显然,短版本是,当使用 XmlSerializer(或任何其他大 blob)将数据推送到 NetworkStream 时,它只会无限期地保持线路打开,等待写入更多信息。它只会在您关闭连接后刷新连接。这会造成这种方法非常适合发送但不适合接收的情况。或相反亦然。它成为一种单向通信,对于通过同一连接继续来回通信毫无用处。

我不得不解决一些表面上看起来如此优雅的问题,但回到我以前的 C 时代,我已经诉诸于先发送“字节数”数据包,然后才是实际数据包.这使我能够在另一端读取确切的字节数,因此我永远不会陷入阻塞模式。

为了简化我的生活,我创建了一个类,其中包含一些用于发送和接收的静态方法。这个类可以通过网络发送任何 XML-serializable 类,所以它可以做我需要它做的事情。

如果有人有更优雅的解决方案,我愿意倾听。

public class PacketTransit
{
    public static void SendPacket(TcpClient C, object Packet)
    {
        MemoryStream ms = new MemoryStream();
        XmlSerializer xml = new XmlSerializer(Packet.GetType());
        xml.Serialize(ms, Packet);
        ms.Position = 0;
        byte[] b = ms.GetBuffer();
        ms.Dispose();

        byte [] sizePacket = BitConverter.GetBytes(b.Length);
        // Send the 4-byte size packet first.
        C.Client.Send(sizePacket, sizePacket.Length, SocketFlags.None);
        C.Client.Send(b, b.Length, SocketFlags.None);
    }

    /// The string is the XML file that needs to be converted.
    public static string ReceivePacket(TcpClient C, Type PacketType)
    {
        byte [] FirstTen = new byte[1024];
        int size = 0;
        byte[] sizePacket = BitConverter.GetBytes(size);

        // Get the size packet
        int sp = C.Client.Receive(sizePacket, sizePacket.Length, SocketFlags.None);
        if (sp <= 0) return "";

        size = BitConverter.ToInt32(sizePacket, 0);

        // read until "size" is met
        StringBuilder sb = new StringBuilder();
        while (size > 0)
        {
            byte[] b = new byte[1024];
            int x = size;
            if (x > 1024) x = 1024;
            int r = C.Client.Receive(b, x, SocketFlags.None);
            size -= r;
            sb.Append(UTF8Encoding.UTF8.GetString(b));
        }

        return sb.ToString();
    }

    /// The XML data that needs to be converted back to the appropriate type.
    public static object Decode(string PacketData, Type PacketType)
    {
        MemoryStream ms = new MemoryStream(UTF8Encoding.UTF8.GetBytes(PacketData));
        XmlSerializer xml = new XmlSerializer(PacketType);
        object obj = xml.Deserialize(ms);
        ms.Dispose();

        return obj;
    }

    public static RequestPacket GetRequestPacket(TcpClient C)
    {
        string str = ReceivePacket(C, typeof(RequestPacket));

        if (str == "") return new RequestPacket();

        RequestPacket req = (RequestPacket) Decode(str, typeof(RequestPacket));

        return req;
    }

    public static ResponsePacket GetResponsePacket(TcpClient C)
    {
        string str = ReceivePacket(C, typeof(ResponsePacket));

        if (str == "") return new ResponsePacket();

        ResponsePacket res = (ResponsePacket)Decode(str, typeof(ResponsePacket));

        return res;
    }
}

要使用这个类,我只需要调用PacketTransit.SendPacket(myTcpClient, SomePacket) 来发送任何给定的 XML-Serializable 对象。然后我可以使用PacketTransit.GetResponsePacketPacketTransit.GetRequestPacket 在另一端接收它。

对我来说,这很有效,但它比最初预期的要多得多。

【讨论】:

【解决方案4】:

我相信这里真正的问题可能是 XmlDeserializer 在从流中读取 EOS 之前可能不会返回。您可能需要关闭发送流以进行输出以强制执行此操作。

【讨论】:

    猜你喜欢
    • 2018-07-13
    • 2015-10-27
    • 2019-02-16
    • 2011-01-21
    • 1970-01-01
    • 1970-01-01
    • 2021-06-13
    • 1970-01-01
    相关资源
    最近更新 更多