C# 如何使用websocket ReceiveAsync(Memory<;byte>;)和MemoryPool<;字节>;
我有一个C# 如何使用websocket ReceiveAsync(Memory<;byte>;)和MemoryPool<;字节>;,c#,sockets,.net-core,C#,Sockets,.net Core,我有一个ClientWebSocket通过ReceiveAsync(ArraySegment buffer,CancellationToken CancellationToken)方法接收数据块: while (webSocket.State == WebSocketState.Open && cancellationToken.IsCancellationRequested == false) { var result = await webSocket.Receive
ClientWebSocket
通过ReceiveAsync(ArraySegment buffer,CancellationToken CancellationToken)
方法接收数据块:
while (webSocket.State == WebSocketState.Open && cancellationToken.IsCancellationRequested == false)
{
var result = await webSocket.ReceiveAsync(buffer, cancellationToken);
var isEndOfMessage = resultProcessor.Receive(result, buffer, out var frame);
if (isEndOfMessage)
{
if (frame == null)
break; // End of message with no data means socket closed - break.
else
await this.targetBlock.SendAsync(frame); // Process It.
}
}
我将部分帧块存储在一个只读序列中
:
public bool Receive(WebSocketReceiveResult result, ArraySegment<byte> buffer, out ReadOnlySequence<byte> frame)
{
if (result.EndOfMessage && result.MessageType == WebSocketMessageType.Close)
{
frame = default;
return false;
}
var slice = buffer
.Slice(0, result.Count)
.ToArray(); // Take a local copy to avoid corruption as buffer is reused by caller.
if (startChunk == null)
startChunk = currentChunk = new Chunk<byte>(slice);
else
currentChunk = currentChunk.Add(slice);
if(result.EndOfMessage && startChunk != null)
{
if (startChunk.Next == null)
frame = new ReadOnlySequence<byte>(startChunk.Memory);
else
frame = new ReadOnlySequence<byte>(startChunk, 0, currentChunk, currentChunk.Memory.Length);
startChunk = currentChunk = null; // Reset so we can accept new chunks from scratch.
return true;
}
else
{
frame = default;
return false;
}
}
在我当前的配置中,我在ArraySegment
和我的区块结构之间使用新数组进行复制,以避免内存损坏(因为缓冲区不断被套接字覆盖)
我看到了webSocket.ReceiveAsync
替代方法,它使用内存。我认为,使用这一范式的高层次过程将是:
从内存池管理器请求内存块,让websocket.ReciveAsync
写入此租用内存
在WebSocketReceiveResultProcessor
函数中,将内存链接成块(我们的ReadOnlySequenceSegment
实现)
发送到应用程序(即解码json或类似文件)
将内存返回到内存池管理器
伪代码如下:
while (webSocket.State == WebSocketState.Open && cancellationToken.IsCancellationRequested == false)
{
var buffer = memoryPoolManager.Rent();
var result = await webSocket.ReceiveAsync(buffer, cancellationToken);
// Modify the resultProcessor.Receive to receive Memory<byte> and return
// the same ReadOnlySequenceSegment implementation of Chunk chains.
var isEndOfMessage = resultProcessor.Receive(result, buffer, out var frame);
if (isEndOfMessage)
{
if (frame == null)
break; // End of message with no data means socket closed - break.
else
await this.targetBlock.SendAsync(frame); // Process It.
// Go through the loop of chunks and return back to the memory pool.
}
}
while(webSocket.State==WebSocketState.Open&&cancellationToken.IsCancellationRequested==false)
{
var buffer=memoryPoolManager.Rent();
var结果=等待webSocket.ReceiveAsync(缓冲区、取消令牌);
//修改resultProcessor.Receive以接收内存并返回
//块链的相同ReadOnlySequenceSegment实现。
var isEndOfMessage=resultProcessor.Receive(结果、缓冲区、输出var帧);
如果(isEndOfMessage)
{
if(frame==null)
break;//没有数据的消息结尾表示套接字已关闭-break。
其他的
等待这个.targetBlock.SendAsync(frame);//处理它。
//遍历区块循环并返回内存池。
}
}
我正在寻找已知的github代码,用于使用websocket的这个dequeing/过程,请澄清上述过程。提前感谢。我通过以下帮助程序功能实现了这一点:
class Chunk<T> : ReadOnlySequenceSegment<T>
{
public Chunk(ReadOnlyMemory<T> memory)
{
Memory = memory;
}
public Chunk<T> Add(ReadOnlyMemory<T> mem)
{
var segment = new Chunk<T>(mem)
{
RunningIndex = RunningIndex + Memory.Length
};
Next = segment;
return segment;
}
}
sealed class WebSocketReceiveResultProcessor : IDisposable
{
Chunk<byte> startChunk = null;
Chunk<byte> currentChunk = null;
private readonly bool isUsingArrayPool;
public WebSocketReceiveResultProcessor(bool isUsingArrayPool)
{
this.isUsingArrayPool = isUsingArrayPool;
}
public bool Receive(WebSocketReceiveResult result, ArraySegment<byte> buffer, out ReadOnlySequence<byte> frame)
{
if (result.EndOfMessage && result.MessageType == WebSocketMessageType.Close)
{
frame = default;
return false;
}
// If not using array pool, take a local copy to avoid corruption as buffer is reused by caller.
var slice = isUsingArrayPool ? buffer.Slice(0, result.Count) : buffer.Slice(0, result.Count).ToArray();
if (startChunk == null)
startChunk = currentChunk = new Chunk<byte>(slice);
else
currentChunk = currentChunk.Add(slice);
if (result.EndOfMessage && startChunk != null)
{
if (startChunk.Next == null)
frame = new ReadOnlySequence<byte>(startChunk.Memory);
else
frame = new ReadOnlySequence<byte>(startChunk, 0, currentChunk, currentChunk.Memory.Length);
startChunk = currentChunk = null; // Reset so we can accept new chunks from scratch.
return true;
}
else
{
frame = default;
return false;
}
}
public void Dispose()
{
if (this.isUsingArrayPool)
{
var chunk = startChunk;
while (chunk != null)
{
if (MemoryMarshal.TryGetArray(chunk.Memory, out var segment))
ArrayPool<byte>.Shared.Return(segment.Array);
chunk = (Chunk<byte>)chunk.Next;
}
}
// Suppress finalization.
GC.SuppressFinalize(this);
}
}
如果使用阵列池(isUsingArrayPool=true
),则调度
调用中的高级函数必须返回缓冲区:
// Where frame is the ReadOnlySequence<byte> passed into dispatch function.
foreach (var chunk in frame)
{
if (MemoryMarshal.TryGetArray(chunk, out var segment))
ArrayPool<byte>.Shared.Return(segment.Array);
}
//其中frame是传递到dispatch函数的ReadOnlySequence。
foreach(框架中的变量块)
{
if(MemoryMarshal.TryGetArray(块,out-var段))
ArrayPool.Shared.Return(segment.Array);
}
池中的内存缓冲区是一个完美的解决方案,因为-除了转换或传输之外,您实际上不需要这些字节。我的意思是,你要么通过压缩、加密等方式转换这些数据,要么将其反序列化为某个结构化流/对象,要么保持原样,在你的应用程序中进一步发送它们。这两种情况都适用于任何阵列,无论是来自池还是LOH中的新阵列。他们不会管理它们,也不会改变它们,只是阅读。总共-消除所有ToArray()调用并使用预分配的缓冲区。
// Snipped the rest of the connection class.
byte[] GetBuffer()
{
if (this.isUsingArrayPool)
return ArrayPool<byte>.Shared.Rent(ReceiveChunkSize); // Rent a buffer.
else
{
if(this.internalBufferIfArrayPoolNotUsed == null)
this.internalBufferIfArrayPoolNotUsed = new byte[ReceiveChunkSize];
return this.internalBufferIfArrayPoolNotUsed;
}
}
async Task Loop(IEnumerable<string> tickers, Func<ReadOnlySequence<byte>,Task> dispatch, CancellationToken cancellationToken)
{
while (cancellationToken.IsCancellationRequested == false)
{
var webSocket = new ClientWebSocket();
webSocket.Options.KeepAliveInterval = this.keepAliveInterval;
var resultProcessor = new WebSocketReceiveResultProcessor(this.isUsingArrayPool);
try
{
this.logger.LogInformation($"Connecting.");
await webSocket.ConnectAsync(uri, cancellationToken);
while (webSocket.State == WebSocketState.Open && cancellationToken.IsCancellationRequested == false)
{
var buffer = GetBuffer();
var result = await webSocket.ReceiveAsync(buffer, cancellationToken);
var isEndOfMessage = resultProcessor.Receive(result, buffer, out var frame);
if (isEndOfMessage)
{
if (frame.IsEmpty == true)
break; // End of message with no data means socket closed - break so we can reconnect.
else
await dispatch(frame);
}
}
}
catch (WebSocketException ex)
{
this.logger.LogError(ex, ex.Message);
}
catch (Exception ex)
{
this.logger.LogCritical(ex, ex.Message);
return;
}
finally
{
webSocket.Dispose();
resultProcessor.Dispose();
}
}
}
// Where frame is the ReadOnlySequence<byte> passed into dispatch function.
foreach (var chunk in frame)
{
if (MemoryMarshal.TryGetArray(chunk, out var segment))
ArrayPool<byte>.Shared.Return(segment.Array);
}