I want to implement a network client that handles some specific protocol. The easiest option I see is to write NetworkStream descendant class that processes protocol-specific features. So I created class with sample protocol: all data is transferred wrapped into [[ and ]]. Client must receive it transparently.
As a simplest server I run ncat with the following command line
>echo [[pong]] | ncat -l -p 2222
and here's the program (don't judje it too tough - it's a sample and I'm just learning C# - awkward clauses possible)
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();
}
}
}
I succeeded in overriding sync Read but I want async as well. From what I've read in some articles and also in the sources overriding Begin* should be enough to have *Async working. But it's not: exception is not risen and [[pong]] is written to console so none of my custom methods is called.
(solved, see edit below) I also tried overriding ReadAsync:
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
throw new Exception("ReadAsync is called"); // doesn't happen
}
but it doesn't get called either.
The questions:
- What I'm doing wrong in overriding?
- What's the proper way to achieve what I want? Wrapper class inherited from Stream having NetworkStream instane as internal field?
==== Edit ====
issue with ReadAsync was solved by overriding the proper overload.
// 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");
}
initial issue was solved with the hint from Fildor (NetworkStream doesn't call Begin* from its *Async overrides). However I'll likely rework my design to another principle as Markus suggested.