我想要实现一个处理特定协议的网络客户端。我看到的最简单的选择是编写处理协议特定特性的NetworkStream子类。因此,我使用示例协议创建了类:所有数据都被打包到[[和]]中。客户必须透明地接收它。作为最简单的服务器,我使用以下命令行运行ncat
>echo [[pong]] | ncat -l -p 2222这是一个程序(不要太难判断-这是一个样本,我只是学习C# -尴尬的条款可能)
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:
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
throw new Exception("ReadAsync is called"); // doesn't happen
}但也没人给它打电话。
问题如下:
====编辑====
通过覆盖适当的重载解决了与ReadAsync的问题。
// 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建议的那样,我可能会将我的设计重新设计到另一个原则上。
发布于 2022-05-18 12:15:02
正如您已经提出的,我还将投票支持创建包含(Network)Stream的包装类,并遵循“组合优于继承”的原则。为什么?
继承是一种很强的关系。正如您的示例和要重写哪个ReadAsync方法的搜索所示,您依赖于基类的实现,并且它调用相应的ReadAsync重载。如果基类稍后对此进行更改,则您的代码可能不再工作。
另一方面,如果使用组合,则依赖于(Network)Stream的外部接口,并且要灵活得多。您可以创建一个类,它使用泛型流作为输入,并且不依赖于NetworkStream,而不是改变NetworkStream的行为。
public class MyNetworkStream : Stream
{
private readonly Stream _underlyingStream;
public MyNetworkStream(Stream underlyingStream)
{
_underlyingStream = underlyingStream;
}
// ...
}因此,您可以将任何流注入(传递构造函数参数)到类中,而不限于始终使用NetworkStream。在测试流的功能时,这可能是很有价值的。
如您所建议的,上面的示例来自Stream。Stream类是一个公共分母,因此您可以在各种场景中使用您的类。但是,您需要实现Stream的完整接口。
如果您想拥有一个更小的类接口,可以省略从Stream继承的内容,并为类创建一个接口,该接口可以完全反映场景的需要。
除了流之外,在*Reader命名空间中还有System.IO和*Writer类。从概念的角度来看,包装器类可以是一个MyProtocolReader,它包含对流的引用,读取和调整检索到的内容(例如,删除前面和后面的括号)。
https://stackoverflow.com/questions/72288350
复制相似问题