11using Microsoft . Extensions . Logging ;
22using System ;
3+ using System . Buffers ;
34using System . Net . WebSockets ;
45using System . Runtime . InteropServices ;
56using 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}
0 commit comments