TcpClient.GetStream().DataAvailable 返回 false,但流有更多数据

发布于 2024-10-04 05:45:34 字数 1307 浏览 6 评论 0原文

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

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

以下是我正在处理的情况的列表: 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();
}

欢迎所有反馈以获得更好的解决方案,或者只是对需要尝试一下 Sleep(1) 以允许在检查 DataAvailable 值之前正确更新内容表示赞同。

我想我希望两年后能够得到这个问题的答案 事情已经不是这样了:)

So, it would seem that a blocking Read() can return before it is done receiving all of the data being sent to it. In turn we wrap the Read() with a loop that is controlled by the DataAvailable value from the stream in question. The problem is that you can receive more data while in this while loop, but there is no behind the scenes processing going on to let the system know this. Most of the solutions I have found to this on the net have not been applicable in one way or another to me.

What I have ended up doing is as the last step in my loop, I do a simple Thread.Sleep(1) after reading each block from the stream. This appears to give the system time to update and I am not getting accurate results but this seems a bit hacky and quite a bit 'circumstantial' for a solution.

Here is a list of the circumstances I am dealing with: Single TCP Connection between an IIS Application and a standalone application, both written in C# for send/receive communication. It sends a request and then waits for a response. This request is initiated by an HTTP request, but I am not having this issue reading data from the HTTP Request, it is after the fact.

Here is the basic code for handling an incoming connection

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

All feedback welcome for a better solution or just a thumbs up on needing to give that Sleep(1) a go to allow things to update properly before we check the DataAvailable value.

Guess I am hoping after 2 years that the answer to this question isn't how things still are :)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

梦途 2024-10-11 05:45:34

你必须知道你需要读取多少数据;您不能简单地循环读取数据,直到不再有数据为止,因为您永远无法确定不会再有数据了。

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

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

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

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

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

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

You have to know how much data you need to read; you cannot simply loop reading data until there is no more data, because you can never be sure that no more is going to come.

This is why HTTP GET results have a byte count in the HTTP headers: so the client side will know when it has received all the data.

Here are two solutions for you depending on whether you have control over what the other side is sending:

  1. Use "framing" characters: (SB)data(EB), where SB and EB are start-block and end-block characters (of your choosing) but which CANNOT occur inside the data. When you "see" EB, you know you are done.

  2. Implement a length field in front of each message to indicate how much data follows: (len)data. Read (len), then read (len) bytes; repeat as necessary.

This isn't like reading from a file where a zero-length read means end-of-data (that DOES mean the other side has disconnected, but that's another story).

A third (not recommended) solution is that you can implement a timer. Once you start getting data, set the timer. If the receive loop is idle for some period of time (say a few seconds, if data doesn't come often), you can probably assume no more data is coming. This last method is a last resort... it's not very reliable, hard to tune, and it's fragile.

穿越时光隧道 2024-10-11 05:45:34

我发现这有问题。
您期望通信速度比 while() 循环更快,但这是不太可能的。
while() 循环一旦没有更多数据就会结束,但在退出后几毫秒可能不会出现这种情况。

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

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

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

I'm seeing a problem with this.
You're expecting that the communication will be faster than the while() loop, which is very unlikely.
The while() loop will finish as soon as there is no more data, which may not be the case a few milliseconds just after it exits.

Are you expecting a certain amount of bytes?
How often is OnClientCommunication() fired? Who triggers it?

What do you do with the data after the while() loop? Do you keep appending to previous data?

DataAvailable WILL return false because you're reading faster than the communication, so that's fine only if you keep coming back to this code block to process more data coming in.

与他有关 2024-10-11 05:45:34

我试图在从网络流读取数据之前检查 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.");
        }

I was trying to check DataAvailable before reading data from a network stream and it would return false, although after reading a single byte it would return true. So I checked the MSDN documentation and they also read before checking. I would re-arrange the while loop to a do while loop to follow this pattern.

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.");
        }
筱武穆 2024-10-11 05:45:34

当我有这个代码时:

    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 上为 false,则更多)
  • 然后当我读取第一批数据时,我怀疑 .Read 不知何故根据其结果更新 NetworkStream 中这 1000 个字节的计数器,在发生这种情况之前,我怀疑此时 .DataAvailable 设置为 false,并且在更新计数器后,如果计数器数据,则 .DataAvailable 将设置为正确的值小于 1000 字节。仔细想想,这是有道理的。因为否则它会在检查 1000 字节到达之前进入下一个周期,并且 .Read 方法将无限期地阻塞,因为 reader 可能已经读取了 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 字节。不要问我为什么,但我对其进行了相当彻底的测试,并且很容易重现。

When I have this code:

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

From what I can observe:

  • When sender sends 1000 bytes and reader wants to read them. Then I suspect that NetworkStream somehow "knows" that it should receive 1000 bytes.
  • When I call .Read before any data arrives from NetworkStream then .Read should be blocking until it gets more than 0 bytes (or more if .NoDelay is false on networkStream)
  • Then when I read first batch of data I suspect that .Read is somehow updating from its result the counter of those 1000 bytes at NetworkStream and before this happens I suspect, that in this time the .DataAvailable is set to false and after the counter is updated then the .DataAvailable is then set to correct value if the counter data is less than 1000 bytes. It makes sense when you think about it. Because otherwise it would go to the next cycle before checking that 1000 bytes arrived and the .Read method would be blocking indefinitely, because reader could have already read 1000 bytes and no more data would arrive.
  • This I think is the point of failure here as already James said:

Yes, this is just the way these libraries work. They need to be given time to run to fully validate the data incoming. – James Apr 20 '16 at 5:24

  • I suspect that the update of internal counter between end of .Read and before accessing .DataAvailable is not as atomic operation (transaction) so the TcpClient needs more time to properly set the DataAvailable.

When I have this code:

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

Then the NetworkStream have enough time to properly set .DataAvailable and this method should function correctly.

Fun fact... This seems to be somehow OS Version dependent. Because the first function without sleep worked for me on Win XP and Win 10, but was failing to receive whole 1000 bytes on Win 7. Don't ask me why, but I tested it quite thoroughly and it was easily reproducible.

垂暮老矣 2024-10-11 05:45:34

使用 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();
        }

Using TcpClient.Available will allow this code to read exactly what is available each time. TcpClient.Available is automatically set to TcpClient.ReceiveBufferSize when the amount of data remaining to be read is greater than or equal to TcpClient.ReceiveBufferSize. Otherwise it is set to the size of the remaining data.
Hence, you can indicate the maximum amount of data that is available for each read by setting TcpClient.ReceiveBufferSize (e.g., 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();
        }
陌路黄昏 2024-10-11 05:45:34
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();
        }
    }
}
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();
        }
    }
}
秋日私语 2024-10-11 05:45:34

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

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

This example presents an algorithm for sending and receiving data, namely text messages. You can also send files.

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
    }
}
笔落惊风雨 2024-10-11 05:45:34

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

微软文档的示例:

        // 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.");
      }

原始微软文档

Use a do-while loop. This will make sure the memory stream pointers have moved. The first Read or ReadAsync will cause the memorystream pointer to move and then onwards the ".DataAvailable" property will continue to return true until we hit the end of the stream.

An example from microsoft docs:

        // 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.");
      }

Original Micorosoft Doc

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文