Skip to content

Commit 1bc98f0

Browse files
authored
Support ReadOnlySequence type (#146)
1 parent 43bfa6b commit 1bc98f0

3 files changed

Lines changed: 119 additions & 27 deletions

File tree

src/Websocket.Client/RequestMessage.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Buffers;
23

34
namespace Websocket.Client
45
{
@@ -33,4 +34,14 @@ public RequestBinarySegmentMessage(ArraySegment<byte> data)
3334
Data = data;
3435
}
3536
}
37+
38+
internal class RequestBinarySequenceMessage : RequestMessage
39+
{
40+
public ReadOnlySequence<byte> Data { get; }
41+
42+
public RequestBinarySequenceMessage(ReadOnlySequence<byte> data)
43+
{
44+
Data = data;
45+
}
46+
}
3647
}

src/Websocket.Client/WebsocketClient.Sending.cs

Lines changed: 107 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Microsoft.Extensions.Logging;
22
using System;
3+
using System.Buffers;
34
using System.Net.WebSockets;
45
using System.Runtime.InteropServices;
56
using System.Threading;
@@ -15,12 +16,13 @@ public partial class WebsocketClient
1516
SingleReader = true,
1617
SingleWriter = false
1718
});
18-
private readonly Channel<ArraySegment<byte>> _messagesBinaryToSendQueue = Channel.CreateUnbounded<ArraySegment<byte>>(new UnboundedChannelOptions()
19+
private readonly Channel<ReadOnlySequence<byte>> _messagesBinaryToSendQueue = Channel.CreateUnbounded<ReadOnlySequence<byte>>(new UnboundedChannelOptions()
1920
{
2021
SingleReader = true,
2122
SingleWriter = false
2223
});
2324

25+
private static readonly byte[] EMPTY_ARRAY = { };
2426

2527
/// <summary>
2628
/// Send text message to the websocket channel.
@@ -45,7 +47,7 @@ public bool Send(byte[] message)
4547
{
4648
Validations.Validations.ValidateInput(message, nameof(message));
4749

48-
return _messagesBinaryToSendQueue.Writer.TryWrite(new ArraySegment<byte>(message));
50+
return _messagesBinaryToSendQueue.Writer.TryWrite(new ReadOnlySequence<byte>(message));
4951
}
5052

5153
/// <summary>
@@ -58,6 +60,19 @@ public bool Send(ArraySegment<byte> message)
5860
{
5961
Validations.Validations.ValidateInput(message, nameof(message));
6062

63+
return _messagesBinaryToSendQueue.Writer.TryWrite(new ReadOnlySequence<byte>(message));
64+
}
65+
66+
/// <summary>
67+
/// Send binary message to the websocket channel.
68+
/// It inserts the message to the queue and actual sending is done on another thread
69+
/// </summary>
70+
/// <param name="message">Binary message to be sent</param>
71+
/// <returns>true if the message was written to the queue</returns>
72+
public bool Send(ReadOnlySequence<byte> message)
73+
{
74+
Validations.Validations.ValidateInput(message, nameof(message));
75+
6176
return _messagesBinaryToSendQueue.Writer.TryWrite(message);
6277
}
6378

@@ -84,7 +99,7 @@ public Task SendInstant(string message)
8499
/// <param name="message">Message to be sent</param>
85100
public Task SendInstant(byte[] message)
86101
{
87-
return SendInternalSynchronized(new ArraySegment<byte>(message));
102+
return SendInternalSynchronized(message);
88103
}
89104

90105
/// <summary>
@@ -115,6 +130,20 @@ public bool SendAsText(ArraySegment<byte> message)
115130
return _messagesTextToSendQueue.Writer.TryWrite(new RequestBinarySegmentMessage(message));
116131
}
117132

133+
/// <summary>
134+
/// Send already converted text message to the websocket channel.
135+
/// Use this method to avoid double serialization of the text message.
136+
/// It inserts the message to the queue and actual sending is done on another thread
137+
/// </summary>
138+
/// <param name="message">Message to be sent</param>
139+
/// <returns>true if the message was written to the queue</returns>
140+
public bool SendAsText(ReadOnlySequence<byte> message)
141+
{
142+
Validations.Validations.ValidateInput(message, nameof(message));
143+
144+
return _messagesTextToSendQueue.Writer.TryWrite(new RequestBinarySequenceMessage(message));
145+
}
146+
118147
/// <summary>
119148
/// Stream/publish fake message (via 'MessageReceived' observable).
120149
/// Use for testing purposes to simulate a server message.
@@ -230,57 +259,109 @@ private async Task SendInternalSynchronized(RequestMessage message)
230259

231260
private async Task SendInternal(RequestMessage message)
232261
{
233-
if (!IsClientConnected())
234-
{
235-
_logger.LogDebug(L("Client is not connected to server, cannot send: {message}"), Name, message);
236-
return;
237-
}
238-
239-
_logger.LogTrace(L("Sending: {message}"), Name, message);
240-
241-
ReadOnlyMemory<byte> payload;
242-
243262
switch (message)
244263
{
245264
case RequestTextMessage textMessage:
246-
payload = MemoryMarshal.AsMemory<byte>(GetEncoding().GetBytes(textMessage.Text));
265+
await SendTextMessage(textMessage).ConfigureAwait(false);
247266
break;
248267
case RequestBinaryMessage binaryMessage:
249-
payload = MemoryMarshal.AsMemory<byte>(binaryMessage.Data);
268+
await SendBinaryMessage(binaryMessage).ConfigureAwait(false);
250269
break;
251270
case RequestBinarySegmentMessage segmentMessage:
252-
payload = segmentMessage.Data.AsMemory();
271+
await SendBinarySegmentMessage(segmentMessage).ConfigureAwait(false);
272+
break;
273+
case RequestBinarySequenceMessage sequenceMessage:
274+
await SendBinarySequenceMessage(sequenceMessage).ConfigureAwait(false);
253275
break;
254276
default:
255277
throw new ArgumentException($"Unknown message type: {message.GetType()}");
256278
}
279+
}
280+
281+
private async Task SendTextMessage(RequestTextMessage textMessage)
282+
{
283+
var payload = MemoryMarshal.AsMemory<byte>(GetEncoding().GetBytes(textMessage.Text));
284+
await SendInternal(payload, WebSocketMessageType.Text).ConfigureAwait(false);
285+
}
257286

258-
await _client!
259-
.SendAsync(payload, WebSocketMessageType.Text, true, _cancellation?.Token ?? CancellationToken.None)
260-
.ConfigureAwait(false);
287+
private async Task SendBinaryMessage(RequestBinaryMessage binaryMessage)
288+
{
289+
var payload = MemoryMarshal.AsMemory<byte>(binaryMessage.Data);
290+
await SendInternal(payload, WebSocketMessageType.Text).ConfigureAwait(false);
261291
}
262292

263-
private async Task SendInternalSynchronized(ArraySegment<byte> message)
293+
private async Task SendBinarySegmentMessage(RequestBinarySegmentMessage segmentMessage)
294+
{
295+
await SendInternal(segmentMessage.Data, WebSocketMessageType.Text).ConfigureAwait(false);
296+
}
297+
298+
private async Task SendBinarySequenceMessage(RequestBinarySequenceMessage sequenceMessage)
299+
{
300+
await SendInternal(sequenceMessage.Data, WebSocketMessageType.Text).ConfigureAwait(false);
301+
}
302+
303+
private async Task SendInternalSynchronized(ReadOnlySequence<byte> message)
264304
{
265305
using (await _locker.LockAsync())
266306
{
267-
await SendInternal(message);
307+
await SendInternal(message, WebSocketMessageType.Binary);
308+
}
309+
}
310+
private async Task SendInternalSynchronized(ReadOnlyMemory<byte> message)
311+
{
312+
using (await _locker.LockAsync())
313+
{
314+
await SendInternal(message, WebSocketMessageType.Binary);
268315
}
269316
}
270317

271-
private async Task SendInternal(ArraySegment<byte> payload)
318+
private async Task SendInternal(ReadOnlySequence<byte> payload, WebSocketMessageType messageType)
272319
{
273-
if (!IsClientConnected())
320+
if (payload.IsSingleSegment)
321+
{
322+
await SendInternal(payload.First, messageType).ConfigureAwait(false);
323+
return;
324+
}
325+
326+
if (!BeforeSendInternal(payload.Length))
274327
{
275-
_logger.LogDebug(L("Client is not connected to server, cannot send binary, length: {length}"), Name, payload.Count);
276328
return;
277329
}
278330

279-
_logger.LogTrace(L("Sending binary, length: {length}"), Name, payload.Count);
331+
foreach (var memory in payload)
332+
{
333+
await _client!
334+
.SendAsync(memory, messageType, false, _cancellation?.Token ?? CancellationToken.None)
335+
.ConfigureAwait(false);
336+
}
337+
338+
await _client!
339+
.SendAsync(EMPTY_ARRAY, messageType, true, _cancellation?.Token ?? CancellationToken.None)
340+
.ConfigureAwait(false);
341+
}
342+
343+
private async Task SendInternal(ReadOnlyMemory<byte> payload, WebSocketMessageType messageType)
344+
{
345+
if (!BeforeSendInternal(payload.Length))
346+
{
347+
return;
348+
}
280349

281350
await _client!
282-
.SendAsync(payload, WebSocketMessageType.Binary, true, _cancellation?.Token ?? CancellationToken.None)
351+
.SendAsync(payload, messageType, true, _cancellation?.Token ?? CancellationToken.None)
283352
.ConfigureAwait(false);
284353
}
354+
355+
private bool BeforeSendInternal(long length)
356+
{
357+
if (!IsClientConnected())
358+
{
359+
_logger.LogDebug(L("Client is not connected to server, cannot send binary, length: {length}"), Name, length);
360+
return false;
361+
}
362+
363+
_logger.LogTrace(L("Sending binary, length: {length}"), Name, length);
364+
return true;
365+
}
285366
}
286367
}

src/Websocket.Client/WebsocketClient.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ private async Task Listen(WebSocket client, CancellationToken token)
458458
do
459459
{
460460
ValueWebSocketReceiveResult result;
461-
var ms = (RecyclableMemoryStream)_memoryStreamManager.GetStream();
461+
var ms = _memoryStreamManager.GetStream();
462462

463463
while (true)
464464
{

0 commit comments

Comments
 (0)