【问题标题】:reading line-oriented data using C# Async sockets使用 C# 异步套接字读取面向行的数据
【发布时间】:2011-06-21 03:38:01
【问题描述】:

我是 C# 新手,但从事 TCP 和套接字编程多年。 我想创建一个执行异步 I/O(仅接收)的程序 多台服务器。数据是文本,由换行符分隔(非常简单)。

我的问题是我是否可以假设如果我使用 StreamReader 在我的异步读取回调函数中,我是否可以假设

  1. 它将至少能够获得完整的一行数据,并且
  2. 它不会以某种方式阻塞。

按照我的理解,我想在我的 StreamReader ReadLine 异步读取回调。但是 BeginRead 中的什么知道我真的 想做面向行的数据?我显然不知道阅读的长度 那时。

我希望这是有道理的,在此先感谢!

米奇

【问题讨论】:

    标签: c# asynchronous tcp


    【解决方案1】:

    从您的帖子中,我认为您需要创建自己的类,并将 SYNCHRONOUS 套接字包装到其中。您将以阻塞方式Read() 套接字,缓冲数据,拆分行并在整行可用时向类消费者触发事件。

    【讨论】:

    • +1 确实如此。 (我确实编辑了你的问题,使 SYNC italic+bold 以 really 指出)。
    【解决方案2】:

    根据我对您的问题的理解:您打算像阻塞流一样使用StreamReader,但您使用的是异步套接字......即使我的理解是错误的,请仍然查看以下建议,因为它们还涵盖了您真正打算使用 StreamReader 进行异步读取的情况。

    1. 至少能得到完整的一行数据,并且
      是和否:只要底层缓冲区有一个新行,它就会读取一整行数据,但是,这是一个异步事件,所以一旦 StreamReader 用完数据(即它到达最后一个新行)它即使您收到另一个异步回调,也将不再有任何可阅读的内容。基本上,您将接收缓冲区绑定到SocketAsyncEventArgs,当您收到回调时,您的缓冲区已被填满。之后你对缓冲区的处理取决于你(你可以将其输入StreamReader 以读取行输入)。

    2. 它不会以某种方式阻塞。
      它不会阻塞,但它不会获取您正在寻找的所有数据,具体取决于您实现异步回调的方式。这让我想到了下一点……

    我的下一点:我不知道这是一个连续的数据流,还是只是一些行分隔的“批次”数据,但是,您有几个选择:

    1. 使用 1 个内存流和一个与该内存流关联的读/写流。每当您收到回调时,您使用 StreamWriter 写入缓冲区并使用 StreamReader 读取行,直到您无法再读取任何新行。
    2. 继续将缓冲区写入流,直到您不再从套接字接收到字节(即收到的字节为 0),然后使用该流初始化您的 StreamReader,然后读取所有行。这假设您有固定大小的传入数据,而不是连续的“输入”流。
    3. 创建一个阻塞读取器并让它在单独的线程上运行,它仅在异步读取引发某种标志时读取(想想ManualResetEvent 或类似的东西)。它有点违背了异步套接字的目的,但它仍然可以让您利用这些好处。它比纯阻塞套接字好一点,因为阻塞完全由你控制。

    我会假设您的最佳选择是 #1,但其他 2 个可能不是不可能的,具体取决于您要做什么。

    附带说明:我有 created an async HTTP client,所以请随意查看并清除您认为有用的任何内容。我认为这两个功能对你来说是最重要的:

    开始接收:

    private void BeginReceive()
    {
        if ( _clientState == EClientState.Receiving)
        {
            if (_asyncTask.BytesReceived != 0 && _asyncTask.TotalBytesReceived <= _maxPageSize)
            {
                SocketAsyncEventArgs e = new SocketAsyncEventArgs();
                e.SetBuffer(_asyncTask.ReceiveBuffer, 0, _asyncTask.ReceiveBuffer.Length);
                e.Completed += new EventHandler<SocketAsyncEventArgs>(ReceiveCallback);
                e.UserToken = _asyncTask.Host;
    
                bool comletedAsync = false;
                try
                {
                    comletedAsync = _socket.ReceiveAsync(e);
                }
                catch (SocketException se)
                {
                    Console.WriteLine("Error receiving data from: " + _asyncTask.Host);
                    Console.WriteLine("SocketException: {0} Error Code: {1}", se.Message, se.NativeErrorCode);
    
                    ChangeState(EClientState.Failed);
                }
    
                if (!comletedAsync)
                {
                    // The call completed synchronously so invoke the callback ourselves
                    ReceiveCallback(this, e);
                }
            }
            else
            {
                //Console.WriteLine("Num bytes received: " + _asyncTask.TotalBytesReceived);
                ChangeState(EClientState.ReceiveDone);
            }
        }
    }
    

    还有 ReceiveCallback:

    private void ReceiveCallback(object sender, SocketAsyncEventArgs args)
    {
        lock (_sync) // re-entrant lock
        {
            // Fast fail: should not be receiving data if the client
            // is not in a receiving state.
            if (_clientState == EClientState.Receiving)
            {
                String host = (String)args.UserToken;
    
                if (_asyncTask.Host == host && args.SocketError == SocketError.Success)
                {
                    try
                    {
                        Encoding encoding = Encoding.ASCII;
                        _asyncTask.BytesReceived = args.BytesTransferred;
                        _asyncTask.TotalBytesReceived += _asyncTask.BytesReceived;
    
                        // ********************************************
                        // You will need to replace the following line:
                        _asyncTask.DocSource += encoding.GetString(_asyncTask.ReceiveBuffer, 0, _asyncTask.BytesReceived);
    
                        // Write the contents of the ReceiveBuffer to a Stream using a StreamWriter
    
                        // Use the StreamReader associated with the same Stream to read the next line(s)
                        // ********************************************
                        BeginReceive();
                    }
                    catch (SocketException e)
                    {
                        Console.WriteLine("Error receiving data from: " + host);
                        Console.WriteLine("SocketException: {0} Error Code: {1}", e.Message, e.NativeErrorCode);
    
                        ChangeState(EClientState.Failed);
                    }
                }
                else if (_asyncTask.Host != host)
                {
                    Console.WriteLine("Warning: received a callback for {0}, but the client is currently working on {1}.",
                        host, _asyncTask.Host);
                }
                else
                {
                    Console.WriteLine("Socket Error: {0} when receiving from {1}",
                       args.SocketError,
                       _asyncTask.Host);
                    ChangeState(EClientState.Failed);
                }
            }
        }
    } 
    

    【讨论】:

      【解决方案3】:

      据我所知,使用内置类您将无法做到这一点。

      您可以在NetworkStream 上使用StreamReader 来读取整行,但您需要自己编写异步部分,因为StreamReader 不公开异步接口。或者,您可以使用异步的NetworkStream.BeginRead,但是您必须编写代码以使其识别基于行的输入。

      【讨论】:

      • 感谢您的快速回复。我的第一个想法是使用单独的线程,但后来有几个人告诉我他们不会像使用异步 I/O 那样扩展。
      • @Mitchell McConnell:然后必须有几个人学习 KISS 原则。尽可能做最简单的解决方案,直到它不再扩展。然后重构以使代码更快。
      • @MitchellMcConnell:把我加到那些人中。因此,BeginRead/custom 解析方法似乎最适合您。查看System.Text.Decoder(您可以通过 System.Text.Encoding.GetDecoder` 获得),它专门用于解码从流中读取的文本。
      • @Jon:请解释一下为什么他在编写客户端时会更好(尽管连接到多个服务器)?
      • @jgauffin:我不认为显式线程本质上比显式字节缓冲区操作更容易编写。但我们确实知道异步 I/O 本质上比线程更好。
      猜你喜欢
      • 1970-01-01
      • 2016-12-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多