如何安全地结束NetworkStream.Read()? (在C#或VB.net中)

时间:2013-02-23 08:23:19

标签: c# sockets tcpclient networkstream

我写了一个类,试图读取服务器发送的字节数据,当我的应用程序结束时,我也希望循环结束,但是如果没有数据可用,它会显示NetworkStream.Read()等待。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.Net;
using System.Threading.Tasks;
namespace Stream
{
    class Program
    {
        private static TcpClient client = new TcpClient("127.0.0.1", 80);
        private static NetworkStream stream = client.GetStream();

        static void Main(string[] args)
        {
            var p = new Program();
            p.Run();
        }

        void Run()
        {
            ThreadPool.QueueUserWorkItem(x =>
            {
                while (true)
                {
                    stream.Read(new byte[64], 0, 64);
                }
            });

            client.Close();
            // insert Console.ReadLine(); if you want to see an exception occur.
        }
    }
}

使用此代码,我们得到

  1. System.IO.IOException说“无法从传输连接读取数据:远程主机强行关闭现有连接”,
  2. ObjectDisposedException说“无法访问已处置的对象”或
  3. System.IO.IOException说“通过调用WSACancelBlockingCall来中断阻塞操作”。
  4. 那我怎么能安全地结束这个方法呢?

3 个答案:

答案 0 :(得分:5)

  1. 为避免阻塞,只在DataAvailable为true时调用Read()。
  2. ThreadPool不适合长时间运行的任务,因此重构你的while循环
  3. 在读取连接之前关闭连接(因为工作项执行异步)。
  4. 这可能会有所帮助

         void Run() {
            ThreadPool.QueueUserWorkItem(ReadLoop);
         }
    
         void ReadLoop(object x) {
             if (stream.DataAvailable) 
               stream.Read(new byte[64], 0, 64);
             else 
                 Thread.Sleep(TimeSpan.FromMilliseconds(200));
    
             if (Finished)
                   client.Close();
             else if (!Disposed && !Finished) 
                   ThreadPool.QueueUserWorkItem(ReadLoop);
         }
    

    您需要管理Finished& amp;把自己安排在真正的班上。

答案 1 :(得分:5)

从我在运行程序时看到的,第一个例外“无法从传输连接中读取数据......”不是由Stream.Read引发的,而是由TcpClient的构造函数引发的,很可能是由于事实在127.0.0.1:80没有服务器接受连接。

现在,如果你想在Stream.Read上很好地结束,我会异步进行。通过这种方式,您可以捕获在Read和清理程序期间抛出的任何异常。以下是您的程序的修改版本:

using System;
using System.Net.Sockets;

namespace Stream
{
    class Program
    {
        private static TcpClient client;
        private static NetworkStream stream;

        static void Main(string[] args)
        {
            var p = new Program();
            p.Run();
        }

        void Run()
        {
            try
            {
                client = new TcpClient("127.0.0.1", 80);
                stream = client.GetStream();
                byte[] buffer = new byte[64];

                stream.BeginRead(buffer, 0, buffer.Length, new AsyncCallback(OnRead), buffer);

                client.Close();
                Console.ReadKey();
            }
            catch (Exception)
            {
                //...
            }
        }

        void OnRead(IAsyncResult result)
        {
            try
            {
                stream.EndRead(result);
                byte[] buffer = result.AsyncState as byte[];

                if (buffer != null)
                {
                    //...
                }

                // continue to read the next 64 bytes
                buffer = new byte[64];
                stream.BeginRead(buffer, 0, buffer.Length, new AsyncCallback(OnRead), buffer);
            }
            catch (Exception)
            {
                // From here you can get exceptions thrown during the asynchronous read
            }
        }
    }
}

答案 2 :(得分:3)

如果实现了简单的控制台客户端应用程序,那么为什么要使用另一个线程连接并从服务器接收数据?

static void Main(string[] args)
{
    var client = new TcpClient("...", ...);
    var buffer = new byte[1024];
    using (var networkStream = client.GetStream())
    {
        int bytesRead;
        while ((bytesRead = networkStream.Read(buffer, 0, buffer.Length)) > 0)
        {
            var hexString = BitConverter.ToString(buffer, 0, bytesRead);
            Console.WriteLine("Bytes received: {0}", hexString);
        }
    }
}