12

因此,似乎阻塞的 Read() 可以在完成接收所有发送给它的数据之前返回。反过来,我们用一个循环包装 Read(),该循环由相关流中的 DataAvailable 值控制。问题是你可以在这个while循环中接收更多的数据,但是没有后台处理让系统知道这一点。我在网上找到的大多数解决方案都不适用于我。

我最终做的是作为循环的最后一步,在从流中读取每个块后,我做了一个简单的 Thread.Sleep(1) 。这似乎给了系统更新的时间,我没有得到准确的结果,但这似乎有点 hacky 并且对于解决方案来说有点“间接”。

以下是我正在处理的情况列表: IIS 应用程序和独立应用程序之间的单个 TCP 连接,两者都是用 C# 编写的,用于发送/接收通信。它发送一个请求,然后等待响应。此请求是由 HTTP 请求发起的,但我在从 HTTP 请求中读取数据时没有遇到此问题,这是事后的情况。

这是处理传入连接的基本代码

protected void OnClientCommunication(TcpClient oClient)
{
    NetworkStream stream = oClient.GetStream();
    MemoryStream msIn = new MemoryStream();

    byte[] aMessage = new byte[4096];
    int iBytesRead = 0;

    while ( stream.DataAvailable )
    {
        int iRead = stream.Read(aMessage, 0, aMessage.Length);
        iBytesRead += iRead;
        msIn.Write(aMessage, 0, iRead);
        Thread.Sleep(1);
    }
    MemoryStream msOut = new MemoryStream();

    // .. Do some processing adding data to the msOut stream

    msOut.WriteTo(stream);
    stream.Flush();

    oClient.Close();
}

欢迎所有反馈以获得更好的解决方案,或者只是在我们检查 DataAvailable 值之前需要给予 Sleep(1) 以允许事情正确更新的竖起大拇指。

我想我希望 2 年后这个问题的答案不是现在的情况:)

4

8 回答 8

12

您必须知道需要读取多少数据;在没有更多数据之前,您不能简单地循环读取数据,因为您永远无法确定不会有更多数据。

这就是为什么 HTTP GET 结果在 HTTP 标头中有一个字节计数的原因:因此客户端将知道它何时收到了所有数据。

根据您是否可以控制对方发送的内容,这里有两种解决方案:

  1. 使用“框架”字符:(SB)data(EB),其中 SB 和 EB 是开始块和结束块字符(由您选择),但不能出现在数据中。当您“看到”EB 时,您就知道您已经完成了。

  2. 在每条消息前面实现一个长度字段,以指示后面有多少数据:(len)data。读取(len),然后读取(len)字节;根据需要重复。

这不像从一个零长度读取意味着数据结束的文件中读取(这确实意味着另一端已断开连接,但这是另一回事)。

第三种(不推荐)解决方案是您可以实现计时器。 开始获取数据后,设置计时器如果接收循环空闲了一段时间(比如几秒钟,如果数据不经常出现),您可能会假设没有更多数据出现。最后一种方法是最后的手段……它不是很可靠,难以调整,而且很脆弱。

于 2012-01-31T23:19:04.730 回答
10

我看到了这个问题。
您期望通信将比while()循环更快,这是不太可能的。一旦没有更多数据,循环将立即结束,在它退出后几毫秒可能不是这种情况
while()

您是否期望一定数量的字节?
多久被OnClientCommunication()解雇一次?谁触发它?

你如何处理while()循环后的数据?您是否继续附加到以前的数据?

DataAvailable 返回 false,因为您的阅读速度比通信速度快,所以只有当您继续返回此代码块以处理传入的更多数据时才可以。

于 2010-11-23T22:11:53.057 回答
2

我试图在从网络流中读取数据之前检查 DataAvailable,它会返回 false,尽管在读取单个字节后它会返回 true。所以我检查了 MSDN 文档,他们在检查之前也阅读了。我会将 while 循环重新安排为 do while 循环以遵循此模式。

http://msdn.microsoft.com/en-us/library/system.net.sockets.networkstream.dataavailable.aspx

        // Check to see if this NetworkStream is readable. 
        if(myNetworkStream.CanRead){
            byte[] myReadBuffer = new byte[1024];
            StringBuilder myCompleteMessage = new StringBuilder();
            int numberOfBytesRead = 0;

            // Incoming message may be larger than the buffer size. 
            do{
                 numberOfBytesRead = myNetworkStream.Read(myReadBuffer, 0, myReadBuffer.Length);

                 myCompleteMessage.AppendFormat("{0}", Encoding.ASCII.GetString(myReadBuffer, 0, numberOfBytesRead));

            }
            while(myNetworkStream.DataAvailable);

            // Print out the received message to the console.
            Console.WriteLine("You received the following message : " +
                                         myCompleteMessage);
        }
        else{
             Console.WriteLine("Sorry.  You cannot read from this NetworkStream.");
        }
于 2012-08-19T10:02:16.137 回答
2

当我有这个代码时:

    var readBuffer = new byte[1024];
    using (var memoryStream = new MemoryStream())
    {
        do
        {
            int numberOfBytesRead = networkStream.Read(readBuffer, 0, readBuffer.Length);
            memoryStream.Write(readBuffer, 0, numberOfBytesRead);
        }
        while (networkStream.DataAvailable);
    }

据我观察:

  • 当发送者发送 1000 个字节并且读者想要读取它们时。然后我怀疑 NetworkStream 不知何故“知道”它应该接收 1000 个字节。
  • 当我在任何数据从 NetworkStream 到达之前调用 .Read 时,.Read 应该被阻塞,直到它获得超过 0 个字节(或者如果 .NoDelay 在 networkStream 上为假,则更多)
  • 然后,当我读取第一批数据时,我怀疑 .Read 正在以某种方式从其结果中更新 NetworkStream 的那 1000 个字节的计数器,在此之前我怀疑,此时 .DataAvailable 设置为 false 并且在计数器之后如果计数器数据小于 1000 字节,则将 .DataAvailable 设置为正确值。当你考虑它时,它是有道理的。因为否则它会在检查 1000 个字节到达之前进入下一个周期,并且 .Read 方法将无限期地阻塞,因为读取器可能已经读取了 1000 个字节并且不会再有数据到达。
  • 正如詹姆斯已经说过的那样,我认为这是失败的地方:

是的,这正是这些库的工作方式。他们需要有时间运行以完全验证传入的数据。– 詹姆斯 2016 年 4 月 20 日在 5:24

  • 我怀疑 .Read 结束和访问 .DataAvailable 之前内部计数器的更新不是原子操作(事务),因此 TcpClient 需要更多时间来正确设置 DataAvailable。

当我有这个代码时:

    var readBuffer = new byte[1024];
    using (var memoryStream = new MemoryStream())
    {
        do
        {
            int numberOfBytesRead = networkStream.Read(readBuffer, 0, readBuffer.Length);
            memoryStream.Write(readBuffer, 0, numberOfBytesRead);

            if (!networkStream.DataAvailable)
                System.Threading.Thread.Sleep(1); //Or 50 for non-believers ;)
        }
        while (networkStream.DataAvailable);
    }

然后 NetworkStream 有足够的时间正确设置 .DataAvailable 并且此方法应该可以正常工作。

有趣的事实......这似乎与操作系统版本有关。因为第一个不带睡眠的功能在 Win XP 和 Win 10 上对我有用,但在 Win 7 上无法接收整个 1000 字节。不要问我为什么,但我对它进行了非常彻底的测试,并且很容易重现。

于 2017-04-14T20:26:35.593 回答
0

使用 TcpClient.Available 将允许此代码每次准确读取可用的内容。当剩余要读取的数据量大于或等于 TcpClient.ReceiveBufferSize 时,TcpClient.Available 会自动设置为 TcpClient.ReceiveBufferSize。否则,它被设置为剩余数据的大小。因此,您可以通过设置 TcpClient.ReceiveBufferSize(例如,oClient.ReceiveBufferSize = 4096;)来指示每次读取可用的最大数据量。

        protected void OnClientCommunication(TcpClient oClient)
        {
            NetworkStream stream = oClient.GetStream();
            MemoryStream msIn = new MemoryStream();

            byte[] aMessage;
            oClient.ReceiveBufferSize = 4096;
            int iBytesRead = 0;

            while (stream.DataAvailable)
            {
                int myBufferSize = (oClient.Available < 1) ? 1 : oClient.Available;
                aMessage = new byte[oClient.Available];

                int iRead = stream.Read(aMessage, 0, aMessage.Length);
                iBytesRead += iRead;
                msIn.Write(aMessage, 0, iRead);
            }
            MemoryStream msOut = new MemoryStream();

            // .. Do some processing adding data to the msOut stream

            msOut.WriteTo(stream);
            stream.Flush();

            oClient.Close();
        }
于 2020-06-27T04:11:19.290 回答
0
public class NetworkStream
{
    private readonly Socket m_Socket;

    public NetworkStream(Socket socket)
    {
        m_Socket = socket ?? throw new ArgumentNullException(nameof(socket));
    }

    public void Send(string message)
    {
        if (message is null)
        {
            throw new ArgumentNullException(nameof(message));
        }

        byte[] data = Encoding.UTF8.GetBytes(message);
        SendInternal(data);
    }

    public string Receive()
    {
        byte[] buffer = ReceiveInternal();
        string message = Encoding.UTF8.GetString(buffer);
        return message;
    }

    private void SendInternal(byte[] message)
    {
        int size = message.Length;

        if (size == 0)
        {
            m_Socket.Send(BitConverter.GetBytes(size), 0, sizeof(int), SocketFlags.None);
        }
        else
        {
            m_Socket.Send(BitConverter.GetBytes(size), 0, sizeof(int), SocketFlags.None);
            m_Socket.Send(message, 0, size, SocketFlags.None);
        }
    }

    private byte[] ReceiveInternal()
    {
        byte[] sizeData = CommonReceiveMessage(sizeof(int));
        int size = BitConverter.ToInt32(sizeData);

        if (size == 0)
        {
            return Array.Empty<byte>();
        }

        return CommonReceiveMessage(size);
    }

    private byte[] CommonReceiveMessage(int messageLength)
    {
        if (messageLength < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(messageLength), messageLength, "Размер сообщения не может быть меньше нуля.");
        }

        if (messageLength == 0)
        {
            return Array.Empty<byte>();
        }

        byte[] buffer = new byte[m_Socket.ReceiveBufferSize];
        int currentLength = 0;
        int receivedDataLength;

        using (MemoryStream memoryStream = new())
        {
            do
            {
                receivedDataLength = m_Socket.Receive(buffer, 0, m_Socket.ReceiveBufferSize, SocketFlags.None);
                currentLength += receivedDataLength;
                memoryStream.Write(buffer, 0, receivedDataLength);
            }
            while (currentLength < messageLength);

            return memoryStream.ToArray();
        }
    }
}
于 2021-08-24T05:26:42.970 回答
0

此示例介绍了一种用于发送和接收数据(即文本消息)的算法。您也可以发送文件。

using System;
using System.IO;
using System.Net.Sockets;
using System.Text;

namespace Network
{
    /// <summary>
    /// Represents a network stream for transferring data.
    /// </summary>
    public class NetworkStream
    {
        #region Fields
        private static readonly byte[] EmptyArray = Array.Empty<byte>();
        private readonly Socket m_Socket;
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the class <seealso cref="NetworkStream"/>.
        /// </summary>
        /// <param name="socket">
        /// Berkeley socket interface.
        /// </param>
        public NetworkStream(Socket socket)
        {
            m_Socket = socket ?? throw new ArgumentNullException(nameof(socket));
        }
        #endregion

        #region Properties

        #endregion

        #region Methods
        /// <summary>
        /// Sends a message.
        /// </summary>
        /// <param name="message">
        /// Message text.
        /// </param>
        /// <exception cref="ArgumentNullException"/>
        public void Send(string message)
        {
            if (message is null)
            {
                throw new ArgumentNullException(nameof(message));
            }

            byte[] data = Encoding.UTF8.GetBytes(message);
            Write(data);
        }

        /// <summary>
        /// Receives the sent message.
        /// </summary>
        /// <returns>
        /// Sent message.
        /// </returns>
        public string Receive()
        {
            byte[] data = Read();
            return Encoding.UTF8.GetString(data);
        }

        /// <summary>
        /// Receives the specified number of bytes from a bound <seealso cref="Socket"/>.
        /// </summary>
        /// <param name="socket">
        /// <seealso cref="Socket"/> for receiving data.
        /// </param>
        /// <param name="size">
        /// The size of the received data.
        /// </param>
        /// <returns>
        /// Returns an array of received data.
        /// </returns>
        private byte[] Read(int size)
        {
            if (size < 0)
            {
                // You can throw an exception.
                return null;
            }

            if (size == 0)
            {
                // Don't throw an exception here, just return an empty data array.
                return EmptyArray;
            }

            // There are many examples on the Internet where the
            // Socket.Available property is used, this is WRONG!

            // Important! The Socket.Available property is not working as expected.
            // Data packages may be in transit, but the Socket.Available property may indicate otherwise.
            // Therefore, we use a counter that will allow us to receive all data packets, no more and no less.
            // The cycle will continue until we receive all the data packets or the timeout is triggered.

            // Note. This algorithm is not designed to work with big data.

            SimpleCounter counter = new(size, m_Socket.ReceiveBufferSize);
            byte[] buffer = new byte[counter.BufferSize];
            int received;

            using MemoryStream storage = new();

            // The cycle will run until we get all the data.
            while (counter.IsExpected)
            {
                received = m_Socket.Receive(buffer, 0, counter.Available, SocketFlags.None);
                // Pass the size of the received data to the counter.
                counter.Count(received);
                // Write data to memory.
                storage.Write(buffer, 0, received);
            }

            return storage.ToArray();
        }

        /// <summary>
        /// Receives the specified number of bytes from a bound <seealso cref="Socket"/>.
        /// </summary>
        /// <returns>
        /// Returns an array of received data.
        /// </returns>
        private byte[] Read()
        {
            byte[] sizeData;
            // First, we get the size of the master data.
            sizeData = Read(sizeof(int));
            // We convert the received data into a number.
            int size = BitConverter.ToInt32(sizeData);

            // If the data size is less than 0 then throws an exception.
            // We inform the recipient that an error occurred while reading the data.

            if (size < 0)
            {
                // Or return the value null.
                throw new SocketException();
            }

            // If the data size is 0, then we will return an empty array.
            // Do not allow an exception here.

            if (size == 0)
            {
                return EmptyArray;
            }

            // Here we read the master data.
            byte[] data = Read(size);
            return data;
        }

        /// <summary>
        /// Writes data to the stream.
        /// </summary>
        /// <param name="data"></param>
        private void Write(byte[] data)
        {
            if (data is null)
            {
                // Throw an exception.
                // Or send a negative number that will represent the value null.
                throw new ArgumentNullException(nameof(data));
            }

            byte[] sizeData = BitConverter.GetBytes(data.Length);

            // In any case, we inform the recipient about the size of the data.
            m_Socket.Send(sizeData, 0, sizeof(int), SocketFlags.None);

            if (data.Length != 0)
            {
                // We send data whose size is greater than zero.
                m_Socket.Send(data, 0, data.Length, SocketFlags.None);
            }
        }
        #endregion

        #region Classes
        /// <summary>
        /// Represents a simple counter of received data over the network.
        /// </summary>
        private class SimpleCounter
        {
            #region Fields
            private int m_Received;
            private int m_Available;
            private bool m_IsExpected;
            #endregion

            #region Constructors
            /// <summary>
            /// Initializes a new instance of the class <seealso cref="SimpleCounter"/>.
            /// </summary>
            /// <param name="dataSize">
            /// Data size.
            /// </param>
            /// <param name="bufferSize">
            /// Buffer size.
            /// </param>
            /// <exception cref="ArgumentOutOfRangeException"/>
            public SimpleCounter(int dataSize, int bufferSize)
            {
                if (dataSize < 0)
                {
                    throw new ArgumentOutOfRangeException(nameof(dataSize), dataSize, "Data size cannot be less than 0");
                }

                if (bufferSize < 0)
                {
                    throw new ArgumentOutOfRangeException(nameof(dataSize), bufferSize, "Buffer size cannot be less than 0");
                }

                DataSize = dataSize;
                BufferSize = bufferSize;

                // Update the counter data.
                UpdateCounter();
            }
            #endregion

            #region Properties
            /// <summary>
            /// Returns the size of the expected data.
            /// </summary>
            /// <value>
            /// Size of expected data.
            /// </value>
            public int DataSize { get; }

            /// <summary>
            /// Returns the size of the buffer.
            /// </summary>
            /// <value>
            /// Buffer size.
            /// </value>
            public int BufferSize { get; }

            /// <summary>
            /// Returns the available buffer size for receiving data.
            /// </summary>
            /// <value>
            /// Available buffer size.
            /// </value>
            public int Available
            {
                get
                {
                    return m_Available;
                }
            }

            /// <summary>
            /// Returns a value indicating whether the thread should wait for data.
            /// </summary>
            /// <value>
            /// <see langword="true"/> if the stream is waiting for data; otherwise, <see langword="false"/>.
            /// </value>
            public bool IsExpected
            {
                get
                {
                    return m_IsExpected;
                }
            }
            #endregion

            #region Methods
            // Updates the counter.
            private void UpdateCounter()
            {
                int unreadDataSize = DataSize - m_Received;
                m_Available = unreadDataSize < BufferSize ? unreadDataSize : BufferSize;
                m_IsExpected = m_Available > 0;
            }

            /// <summary>
            /// Specifies the size of the received data.
            /// </summary>
            /// <param name="bytes">
            /// The size of the received data.
            /// </param>
            public void Count(int bytes)
            {
                // NOTE: Counter cannot decrease.

                if (bytes > 0)
                {
                    int received = m_Received += bytes;
                    // NOTE: The value of the received data cannot exceed the size of the expected data.
                    m_Received = (received < DataSize) ? received : DataSize;

                    // Update the counter data.
                    UpdateCounter();
                }
            }

            /// <summary>
            /// Resets counter data.
            /// </summary>
            public void Reset()
            {
                m_Received = 0;
                UpdateCounter();
            }
            #endregion
        }
        #endregion
    }
}
于 2021-09-06T08:07:08.067 回答
0

使用 do-while 循环。这将确保内存流指针已移动。第一个 Read 或 ReadAsync 将导致 memorystream 指针移动,然后“.DataAvailable”属性将继续返回 true,直到我们到达流的末尾。

微软文档的一个例子:

        // 检查此 NetworkStream 是否可读。
        如果(myNetworkStream.CanRead){
            字节[] myReadBuffer = 新字节[1024];
            StringBuilder myCompleteMessage = new StringBuilder();
            int numberOfBytesRead = 0;
    
            // 传入消息可能大于缓冲区大小。
            做{
               numberOfBytesRead = myNetworkStream.Read(myReadBuffer, 0, myReadBuffer.Length);
    
               myCompleteMessage.AppendFormat("{0}", Encoding.ASCII.GetString(myReadBuffer, 0, numberOfBytesRead));
           }
          而(myNetworkStream.DataAvailable);
    
          // 将收到的消息打印到控制台。
          Console.WriteLine("您收到以下消息:" +
                                     我的完整消息);
       }
       别的{
         Console.WriteLine("对不起。您无法从此 NetworkStream 中读取数据。");
      }

微软原始文档

于 2022-02-15T20:26:56.323 回答