几天以来,我一直在尝试实现网络库以从头开始完成Astron,而我有几个问题。为了正确处理Nagle,我必须具有帧解析逻辑。因此,在使用新的管道API进行此操作时,我注意到输出是作为只读序列提供的,因此我不得不制作一个自定义的SequenceReader,但是在测试它时遇到了一些问题。我认为我误解了新的Span<T>
和Memory<T>
API:/
为了测试尽可能多的行为,我必须实现ReadOnlySequenceSegment<T>
抽象类,我使用Moq
库来完成该工作,以了解何时获得下一个片段。 :
public class SequenceSegment : ReadOnlySequenceSegment<byte>
{
private readonly Mock<SequenceSegment> _mock;
private ReadOnlySequenceSegment<byte> _next;
public new ReadOnlySequenceSegment<byte> Next
{
get
{
var mockNext = _mock.Object.Next; // simulate get from the mock
return _next;
}
protected set => _next = value;
}
public SequenceSegment(ReadOnlyMemory<byte> memory)
{
Memory = memory;
_mock = new Mock<SequenceSegment>(memory);
}
public SequenceSegment Add(ReadOnlyMemory<byte> mem)
{
var segment = new SequenceSegment(mem)
{
RunningIndex = RunningIndex + Memory.Length
};
Next = segment;
return segment;
}
public void VerifyGetNext() => _mock.VerifyGet(ss => ss.Next);
}
如您所见,我不得不用new
关键字覆盖Next属性,这是一个不好的做法,但是我认为在测试时还可以吗?
这是未通过的测试:
[Fact]
public void TryRead_ShouldReturnTrue_OnSegmentSplitted()
{
var buffer = _createBuffer(); // int + int = 8bytes buffer
var firstSegment = new SequenceSegment(buffer.Slice(0, 3));
var secondSegment = firstSegment.Add(buffer.Slice(3, 5));
var input = new ReadOnlySequence<byte>(
firstSegment, 0,
secondSegment, 4);
var reader = new SequenceReader(input);
reader.TryRead(BinaryPrimitives.ReadInt32LittleEndian, out var firstValue); // throw here
reader.TryRead(BinaryPrimitives.ReadInt32LittleEndian, out var secondValue);
Assert.Equal(_firstValue, firstValue);
Assert.Equal(_secondValue, secondValue);
firstSegment.VerifyGetNext();
}
测试输出:
Message: System.ArgumentOutOfRangeException : Specified argument was out of the range of valid values.
Parameter name: start
我注释了在测试中引发异常的行,因此我假设我的序列逻辑还可以吗?让我们看一下我的SequenceReader的代码,并在其中加上注释:
public class SequenceReader
{
private const int _maxStackalloc = 128;
protected ReadOnlySequence<byte> _input;
public SequenceReader(ReadOnlySequence<byte> input) => _input = input;
public delegate T ReadDelegate<out T>(ReadOnlySpan<byte> src);
/// <summary>
/// Try to read a <see cref="T"/> with the <see cref="ReadDelegate{T}"/> specified as an arg.
/// The <see cref="SequenceReader"/> then advance the current position according to the size of <see cref="T"/>.
/// <see cref="T"/> must be a struct :
/// <see cref="byte"/>, <see cref="sbyte"/>, <see cref="bool"/>, <see cref="short"/>,
/// <see cref="ushort"/>, <see cref="int"/>, <see cref="uint"/>, <see cref="long"/>,
/// <see cref="ulong"/>, <see cref="float"/>, <see cref="double"/>, <see cref="decimal"/>,
/// </summary>
/// <typeparam name="T">The type to read.</typeparam>
/// <param name="read">The delegate to read the <see cref="T"/>. Must be a method from <see cref="BinaryPrimitives"/></param>
/// <param name="result">The result returned.</param>
/// <returns>Returns true if the read was successful, else returns false.</returns>
public unsafe bool TryRead<T>(ReadDelegate<T> read, out T result) where T : unmanaged
{
result = default;
var size = sizeof(T);
if (size > _maxStackalloc) return false;
if (size > _input.Length) return false;
if (_input.First.Length >= size)
result = read(_input.First.Span);
else
{
Span<byte> local = stackalloc byte[size];
_input.Slice(size).CopyTo(local); // throws at the slice
result = read(local);
}
_input = _input.Slice(size);
return true;
}
}
我已经尝试过将行更改为_input.Slice(0, size)
,但是没有任何改变,也没有在此成功的测试中:
[Fact]
public void TryRead_ShouldReturnTrue_OnSegmentComplete()
{
var input = new ReadOnlySequence<byte>(_createBuffer());
var reader = new SequenceReader(input);
reader.TryRead(BinaryPrimitives.ReadInt32LittleEndian, out var firstValue);
reader.TryRead(BinaryPrimitives.ReadInt32LittleEndian, out var secondValue);
Assert.Equal(_firstValue, firstValue);
Assert.Equal(_secondValue, secondValue);
}
我想知道我在做错什么,如果您有任何想法,请帮助我会很棒!