首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >重写NetworkStream.BeginRead以使NetworkStream.ReadAsync被重写

重写NetworkStream.BeginRead以使NetworkStream.ReadAsync被重写
EN

Stack Overflow用户
提问于 2022-05-18 11:28:09
回答 1查看 65关注 0票数 2

我想要实现一个处理特定协议的网络客户端。我看到的最简单的选择是编写处理协议特定特性的NetworkStream子类。因此,我使用示例协议创建了类:所有数据都被打包到[[]]中。客户必须透明地接收它。作为最简单的服务器,我使用以下命令行运行ncat

代码语言:javascript
复制
>echo [[pong]] | ncat -l -p 2222

这是一个程序(不要太难判断-这是一个样本,我只是学习C# -尴尬的条款可能)

代码语言:javascript
复制
using System;
using System.Text;
using System.Net.Sockets;
using System.Threading.Tasks;

// Sample network stream that wraps all sent and received data into "[[" and "]]"
class MyNetworkStream : NetworkStream
{
  public MyNetworkStream(Socket socket)
    : base(socket)
  {
  }

  public override int Read(byte[] buffer, int offset, int count)
  {
    // Read and unwrap
    byte[] wrapperBuf = new byte[2];
    byte[] contentBuf = new byte[count];
    base.Read(wrapperBuf, 0, wrapperBuf.Length);
    int res = base.Read(contentBuf, 0, contentBuf.Length);
    contentBuf.AsSpan(0, res - 2).CopyTo(buffer.AsSpan(offset));
    return res;
  }

  public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, Object state)
  {
    throw new Exception("BeginRead is called"); // doesn't happen!
  }
}

namespace HelloWorld
{
  class Program
  {
    static async Task MainAsync()
    {
      Socket sock = new Socket(SocketType.Stream, ProtocolType.Tcp);
      await sock.ConnectAsync("localhost", 2222);
      MyNetworkStream stm = new MyNetworkStream(sock);
      byte[] buf = new byte[1024];
      int read = await stm.ReadAsync(buf.AsMemory());
      Console.WriteLine(Encoding.UTF8.GetString(buf.AsSpan(0, read))); // prints "[[pong]]"
    }

    static void Main(String[] args)
    {
      // Main async loop
      MainAsync().GetAwaiter().GetResult();
    }
  }
}

我成功地重写了同步Read,但我也想要异步。根据我在一些文章中所读到的,以及在资料来源覆盖的Begin中所读到的,应该足够让*异步工作了。但事实并非如此:异常没有上升,[[pong]]被写入控制台,所以我的自定义方法都没有被调用。

(解决了,参见下面的编辑)我也尝试过重写ReadAsync:

代码语言:javascript
复制
  public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
  {
    throw new Exception("ReadAsync is called"); // doesn't happen
  }

但也没人给它打电话。

问题如下:

  • 我在重写上做错了什么?
  • 实现我想要的目标的正确方法是什么?包装器类继承自流,将NetworkStream实例化作为内部字段?

====编辑====

通过覆盖适当的重载解决了与ReadAsync的问题。

代码语言:javascript
复制
  // NetworkStream has 2 ReadAsync overloads directly calling undeluying socket methods instead of
  // referring to BeginRead so we have to override them both.
  public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default)
  {
    return this.ReadAsync(buffer.AsMemory(offset, count), cancellationToken).AsTask();
  }

  public override ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
  {
    throw new Exception("ReadAsync is called");
  }

使用Fildor的提示解决了初始问题(NetworkStream没有从它的*异步重写调用Begin* )。然而,正如Markus建议的那样,我可能会将我的设计重新设计到另一个原则上。

EN

回答 1

Stack Overflow用户

发布于 2022-05-18 12:15:02

正如您已经提出的,我还将投票支持创建包含(Network)Stream的包装类,并遵循“组合优于继承”的原则。为什么?

继承是一种很强的关系。正如您的示例和要重写哪个ReadAsync方法的搜索所示,您依赖于基类的实现,并且它调用相应的ReadAsync重载。如果基类稍后对此进行更改,则您的代码可能不再工作。

另一方面,如果使用组合,则依赖于(Network)Stream的外部接口,并且要灵活得多。您可以创建一个类,它使用泛型流作为输入,并且不依赖于NetworkStream,而不是改变NetworkStream的行为。

代码语言:javascript
复制
public class MyNetworkStream : Stream
{
  private readonly Stream _underlyingStream;

  public MyNetworkStream(Stream underlyingStream) 
  {
    _underlyingStream = underlyingStream;
  }

  // ...
}

因此,您可以将任何流注入(传递构造函数参数)到类中,而不限于始终使用NetworkStream。在测试流的功能时,这可能是很有价值的。

如您所建议的,上面的示例来自StreamStream类是一个公共分母,因此您可以在各种场景中使用您的类。但是,您需要实现Stream的完整接口。

如果您想拥有一个更小的类接口,可以省略从Stream继承的内容,并为类创建一个接口,该接口可以完全反映场景的需要。

除了流之外,在*Reader命名空间中还有System.IO*Writer类。从概念的角度来看,包装器类可以是一个MyProtocolReader,它包含对流的引用,读取和调整检索到的内容(例如,删除前面和后面的括号)。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/72288350

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档