如何在 C# .NET 中保持 TCP 连接打开并执行多次写入/读取?

Posted

技术标签:

【中文标题】如何在 C# .NET 中保持 TCP 连接打开并执行多次写入/读取?【英文标题】:How to keep a TCP connection open and perform multiple Writes/Reads in C# .NET? 【发布时间】:2021-12-16 11:21:25 【问题描述】:

有多个帖子描述了保持 TCP 连接打开而不是在每次需要读取或写入时关闭和打开的性能优势。例如:

Best practice: Keep TCP/IP connection open or close it after each transfer?

我正在与基于 RPC 的设备进行通信,该设备采用 json 命令。我从设备供应商那里获得的示例每次发送命令时都会打开和关闭连接。这是我目前在 using 语句中通过 TcpClient 所做的,但我想看看是否有什么可以改进我已经完成的工作。事实上,我在开始项目时曾尝试过此操作,但不知道如何操作,因此每次都出于沮丧和必要性而关闭。我最近使用套接字的实验,因为所有帖子都表明这样做是较低级别控制的必要条件:

public class Connection

    private Socket tcpSocket = null;
    public string IpAddress = "192.168.0.30";
    public int Port = 50002;

    public Connection(string ipAddress, int port)
    
        this.IpAddress = ipAddress;
        this.Port = port;
    

    public void Connect()
    
        DnsEndPoint ipe = new DnsEndPoint(this.IpAddress, this.Port);
        Socket tempSocket =
            new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        tempSocket.Connect(ipe);
        if (tempSocket.Connected)
        
            this.tcpSocket = tempSocket;
            this.tcpSocket.NoDelay = true;
            this.tcpSocket.
            //this.tcpSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive,true);
            Console.WriteLine("Successfully connected.");
        
        else
        
            Console.WriteLine("Error.");
        

    

    public void Disconnect()
    
        this.tcpSocket.Disconnect(true);
        this.tcpSocket.Dispose();
        Console.WriteLine("Successfuly closed.");
    

    public string SendCommand()
    
        string path = @"C:\Users\me\Desktop\request.json";
        string request = File.ReadAllText(path);
        Byte[] bytesSent = Encoding.UTF8.GetBytes(request);
        this.tcpSocket.Send(bytesSent);
        this.tcpSocket.Shutdown(SocketShutdown.Send);
        var respBytes = ReceiveAll();
        string s = System.Text.Encoding.UTF8.GetString(respBytes, 0, respBytes.Length);
        return s;
    

    public byte[] ReceiveAll()
    
        var buffer = new List<byte>();
        var currByte = new Byte[1];
        var byteCounter = this.tcpSocket.Receive(currByte, currByte.Length, SocketFlags.None);
        while (this.tcpSocket.Available > 0)
        
            currByte = new Byte[1];
            byteCounter = this.tcpSocket.Receive(currByte, currByte.Length, SocketFlags.None);
            if (byteCounter.Equals(1))
            
                buffer.Add(currByte[0]);
            
        

        return buffer.ToArray();
    


控制台应用:

    static void Main(string[] args)
    
        Connection s = new Connection();
        s.Connect();
        Console.WriteLine(s.SendCommand());
        Console.WriteLine(s.SendCommand());
        Thread.Sleep(5000);
        s.Disconnect();
        Console.ReadKey();
    

这种方法一次有效。我第一次调用发送命令。这不是第二次(引发异常),因为我在 SendCommand() 中的 Send 上调用了 socket.Shutdown()。我这样做是因为这篇文章:

TCPClient not receiving data

但是,似乎没有办法在调用 Shutdown() 后重新启用发送功能。所以现在我只是不知道如果你必须读写,是否有可能保持 tcp 连接打开。此外,我在网上真的找不到有用的例子。有谁知道如何在.NET 中这样做?这甚至可能吗?

【问题讨论】:

为什么您认为保持连接打开是一种改进?除非您可以指出确实需要这样做,否则它很可能不会有所改进,并且您可能会无缘无故地浪费大量时间。您也很有可能在部署中遇到问题,您不知道如何足够快地修复。你知道有问题的协议是否被设计成这样使用吗?如果没有,你可能会自找麻烦。 最终,我正在使用的设备将被整合到制造环境中,用于其他设备的工厂生产。制造设备的速度与制造的经济性直接相关,并且这些类型的系统是在线的并且可以长时间不间断地运行。因此,任何类型的性能提升(我假设是在打开和关闭连接的情况下的执行速度)都是可取的。但我开始意识到,我可能只需要每次打开和关闭一个连接。 小公司发布的基于 TCP 的协议存在无可救药的缺陷,并且存在固有的性能问题,这种情况非常普遍。如果这里也出现这种情况,那么保持连接打开不会解决问题。在这种情况下,解决方案是要求创建者修复协议。 有缺陷的协议的正常问题是它们依赖于客户端来打开、发送和关闭,或者打开、接收和关闭。由于 TCP 是流而不是数据包,因此它不起作用。您可能会错过数据,或者更糟糕的是,您的速度会变慢。我不会解释为什么。客户端至少应该打开、发送、接收和关闭。你的协议是这样做的吗?如果是的话,太好了!双方都必须知道预期有多少,并继续阅读,可能不止一次,直到收到预期的所有内容。期望多少通常由标头中的长度字段或文本协议中的文本标记(例如 eol)给出。 我不是 TCP 专家,尽管我在创建协议和解决问题方面拥有丰富的经验。如果有人看到纠正我的解释的理由,请这样做。 【参考方案1】:

TCP/IP 是一种流式传输协议。要使用它传递消息,您需要一个“框架协议”,以便对等方可以确定消息何时完成。

发出消息结束信号的一种简单方法是在发送完最后一个字节后关闭套接字。但这会阻止套接字重用。有关这方面的示例,请参阅 HTTP 的演变。

如果这是您的设备所做的,则无法重复使用套接字。

【讨论】:

【参考方案2】:

是否可以保持连接打开以接收更多消息取决于应用程序协议。如果协议不支持,则无法强制执行此操作。因此,请询问供应商或查看协议规范(如果存在)以了解是否支持以及如何支持。

但是,似乎没有办法在调用 Shutdown() 后重新启用发送功能。

没有办法。 TCP 写关闭意味着不想再发送任何信息。这是不可能收回的。如果协议支持多个消息交换,那么它需要有一种不同于调用 Shutdown 的方式来检测消息的结束。

【讨论】:

调用 Shutdown 是有道理的。但是我不确定我是否遵循您对应用程序协议的意思(但我会联系供应商)。根据您的回复,我认为 TCP 是一种通信方式,但不一定是应用程序协议。应用程序协议是设备(服务器)如​​何处理传入请求?只是想了解要问什么。 @hallibut:应用程序协议类似于 HTTP,其中每条消息(HTTP 请求、HTTP 响应)都有明确定义的语法和明确定义的结束 - 这使得使用相同的消息发送多个消息成为可能TCP 流,因为很清楚一个结束,另一个开始。 感谢您的澄清,我相信我现在明白了。供应商 api 文档确实包含来自用户提供的 json 请求的预期 json 响应。但是通过进一步阅读框架协议,我相信为了让服务器了解我发送的消息的结尾,它需要遵循预期的长度前缀或分隔符。我猜原因,我必须调用shutdown是因为它既不这样做,所以服务器不知道传出消息何时结束,直到客户端传出流关闭? 整个规范应该包含 TCP 服务器和客户端如何与消息交互的描述,以及消息的样子。更复杂的协议将具有事务。一个简单的事务可以是“你叫什么名字?”、“我的名字是 Server1”。一个复杂的事务可能是一长串来回消息,例如将您的邮件列表从 MS Exchange 服务器传输到 Outlook 客户端。一个简单的设备协议可能只有一个事务,即“给我你的数据”。和“some json...”和“谢谢,现在我关闭了”。 示例中是否没有任何内容显示可能缺少的部分是什么? json 通常以一些非 json 长度为前缀,表示为文本。最坏的情况,有一些二进制标头。此外,行尾可以有多种形式。

以上是关于如何在 C# .NET 中保持 TCP 连接打开并执行多次写入/读取?的主要内容,如果未能解决你的问题,请参考以下文章

确认:我可以在保持 TCP 套接字打开的同时调试应用程序吗?

我需要心跳来保持 TCP 连接打开吗?

在 C# 中获取套接字对象的流

如何在C# winform和JS的基础上,结合SOCKET编程,通过SOCKET接收到的数据,直接显示在网页上。

如何解决 C# IIS 托管的 WCF NET TCP 服务超时问题

最佳实践:每次传输后保持 TCP/IP 连接打开还是关闭?