Migrate to Stream-less approach.

This commit is contained in:
Christian
2018-04-17 22:29:23 +02:00
parent ae11e31048
commit 17b74f20c9
22 changed files with 260 additions and 332 deletions
@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading;
using System.Threading.Tasks;
using Windows.Networking;
using Windows.Networking.Sockets;
@@ -17,36 +18,35 @@ namespace MQTTnet.Implementations
{
// ReSharper disable once MemberCanBePrivate.Global
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global
public static int BufferSize { get; set; } = 4096 * 20; // Can be changed for fine tuning by library user.
public static int BufferSize { get; set; } = 4096; // Can be changed for fine tuning by library user.
private readonly int _bufferSize = BufferSize;
private readonly MqttClientTcpOptions _options;
private StreamSocket _socket;
private Stream _readStream;
private Stream _writeStream;
public MqttTcpChannel(MqttClientTcpOptions options)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
_bufferSize = _options.BufferSize;
_bufferSize = options.BufferSize;
}
public MqttTcpChannel(StreamSocket socket)
{
_socket = socket ?? throw new ArgumentNullException(nameof(socket));
CreateStreams();
}
public Stream SendStream { get; private set; }
public Stream ReceiveStream { get; private set; }
public static Func<MqttClientTcpOptions, IEnumerable<ChainValidationResult>> CustomIgnorableServerCertificateErrorsResolver { get; set; }
public async Task ConnectAsync()
public async Task ConnectAsync(CancellationToken cancellationToken)
{
if (_socket == null)
{
_socket = new StreamSocket();
_socket.Control.NoDelay = true;
}
if (!_options.TlsOptions.UseTls)
@@ -65,7 +65,8 @@ namespace MQTTnet.Implementations
await _socket.ConnectAsync(new HostName(_options.Server), _options.GetPort().ToString(), SocketProtectionLevel.Tls12);
}
CreateStreams();
_readStream = _socket.InputStream.AsStreamForRead(_bufferSize);
_writeStream = _socket.OutputStream.AsStreamForWrite(_bufferSize);
}
public Task DisconnectAsync()
@@ -74,11 +75,22 @@ namespace MQTTnet.Implementations
return Task.FromResult(0);
}
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return _readStream.ReadAsync(buffer, offset, count, cancellationToken);
}
public async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
await _writeStream.WriteAsync(buffer, offset, count, cancellationToken);
await _writeStream.FlushAsync(cancellationToken);
}
public void Dispose()
{
try
{
SendStream?.Dispose();
_readStream?.Dispose();
}
catch (ObjectDisposedException)
{
@@ -88,12 +100,12 @@ namespace MQTTnet.Implementations
}
finally
{
SendStream = null;
_readStream = null;
}
try
{
ReceiveStream?.Dispose();
_writeStream?.Dispose();
}
catch (ObjectDisposedException)
{
@@ -103,7 +115,7 @@ namespace MQTTnet.Implementations
}
finally
{
ReceiveStream = null;
_writeStream = null;
}
try
@@ -122,12 +134,6 @@ namespace MQTTnet.Implementations
}
}
private void CreateStreams()
{
SendStream = _socket.OutputStream.AsStreamForWrite(_bufferSize);
ReceiveStream = _socket.InputStream.AsStreamForRead(_bufferSize);
}
private static Certificate LoadCertificate(MqttClientTcpOptions options)
{
if (options.TlsOptions.Certificates == null || !options.TlsOptions.Certificates.Any())
@@ -7,6 +7,7 @@ using System.Security.Cryptography.X509Certificates;
using System.Threading.Tasks;
using System.IO;
using System.Linq;
using System.Threading;
using MQTTnet.Channel;
using MQTTnet.Client;
@@ -14,20 +15,10 @@ namespace MQTTnet.Implementations
{
public sealed class MqttTcpChannel : IMqttChannel
{
#if NET452 || NET461 || NETSTANDARD2_0
// ReSharper disable once MemberCanBePrivate.Global
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global
public static int BufferSize { get; set; } = 4096 * 20; // Can be changed for fine tuning by library user.
private readonly int _bufferSize = BufferSize;
#else
private readonly int _bufferSize = 0;
#endif
private readonly MqttClientTcpOptions _options;
private Socket _socket;
private SslStream _sslStream;
private Stream _stream;
/// <summary>
/// called on client sockets are created in connect
@@ -35,7 +26,6 @@ namespace MQTTnet.Implementations
public MqttTcpChannel(MqttClientTcpOptions options)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
_bufferSize = options.BufferSize;
}
/// <summary>
@@ -45,21 +35,17 @@ namespace MQTTnet.Implementations
public MqttTcpChannel(Socket socket, SslStream sslStream)
{
_socket = socket ?? throw new ArgumentNullException(nameof(socket));
_sslStream = sslStream;
CreateStreams();
CreateStream(sslStream);
}
public Stream SendStream { get; private set; }
public Stream ReceiveStream { get; private set; }
public static Func<X509Certificate, X509Chain, SslPolicyErrors, MqttClientTcpOptions, bool> CustomCertificateValidationCallback { get; set; }
public async Task ConnectAsync()
public async Task ConnectAsync(CancellationToken cancellationToken)
{
if (_socket == null)
{
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
_socket = new Socket(SocketType.Stream, ProtocolType.Tcp) { NoDelay = true };
}
#if NET452 || NET461
@@ -68,15 +54,14 @@ namespace MQTTnet.Implementations
await _socket.ConnectAsync(_options.Server, _options.GetPort()).ConfigureAwait(false);
#endif
_socket.NoDelay = true;
SslStream sslStream = null;
if (_options.TlsOptions.UseTls)
{
_sslStream = new SslStream(new NetworkStream(_socket, true), false, InternalUserCertificateValidationCallback);
await _sslStream.AuthenticateAsClientAsync(_options.Server, LoadCertificates(), SslProtocols.Tls12, _options.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false);
sslStream = new SslStream(new NetworkStream(_socket, true), false, InternalUserCertificateValidationCallback);
await sslStream.AuthenticateAsClientAsync(_options.Server, LoadCertificates(), SslProtocols.Tls12, _options.TlsOptions.IgnoreCertificateRevocationErrors).ConfigureAwait(false);
}
CreateStreams();
CreateStream(sslStream);
}
public Task DisconnectAsync()
@@ -85,13 +70,21 @@ namespace MQTTnet.Implementations
return Task.FromResult(0);
}
public Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return _stream.ReadAsync(buffer, offset, count, cancellationToken);
}
public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return _stream.WriteAsync(buffer, offset, count, cancellationToken);
}
public void Dispose()
{
var oneStreamIsUsed = SendStream != null && ReceiveStream != null && ReferenceEquals(SendStream, ReceiveStream);
try
{
SendStream?.Dispose();
_stream?.Dispose();
}
catch (ObjectDisposedException)
{
@@ -101,40 +94,7 @@ namespace MQTTnet.Implementations
}
finally
{
SendStream = null;
}
try
{
if (!oneStreamIsUsed)
{
ReceiveStream?.Dispose();
}
}
catch (ObjectDisposedException)
{
}
catch (NullReferenceException)
{
}
finally
{
ReceiveStream = null;
}
try
{
_sslStream?.Dispose();
}
catch (ObjectDisposedException)
{
}
catch (NullReferenceException)
{
}
finally
{
_sslStream = null;
_stream = null;
}
try
@@ -200,25 +160,16 @@ namespace MQTTnet.Implementations
return certificates;
}
private void CreateStreams()
private void CreateStream(Stream stream)
{
Stream stream;
if (_sslStream != null)
if (stream != null)
{
stream = _sslStream;
_stream = stream;
}
else
{
stream = new NetworkStream(_socket, true);
_stream = new NetworkStream(_socket, true);
}
#if NET452 || NET461 || NETSTANDARD2_0
SendStream = new BufferedStream(stream, _bufferSize);
ReceiveStream = new BufferedStream(stream, _bufferSize);
#else
SendStream = stream;
ReceiveStream = stream;
#endif
}
}
}
@@ -62,6 +62,8 @@ namespace MQTTnet.Implementations
{
try
{
args.Socket.Control.NoDelay = true;
var clientAdapter = new MqttChannelAdapter(new MqttTcpChannel(args.Socket), new MqttPacketSerializer(), _logger);
ClientAccepted?.Invoke(this, new MqttServerAdapterClientAcceptedEventArgs(clientAdapter));
}
@@ -43,7 +43,7 @@ namespace MQTTnet.Implementations
_defaultEndpointSocket.Bind(new IPEndPoint(options.DefaultEndpointOptions.BoundIPAddress, options.GetDefaultEndpointPort()));
_defaultEndpointSocket.Listen(options.ConnectionBacklog);
Task.Run(async () => await AcceptDefaultEndpointConnectionsAsync(_cancellationTokenSource.Token).ConfigureAwait(false), _cancellationTokenSource.Token).ConfigureAwait(false);
Task.Run(() => AcceptDefaultEndpointConnectionsAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token);
}
if (options.TlsEndpointOptions.IsEnabled)
@@ -63,7 +63,7 @@ namespace MQTTnet.Implementations
_tlsEndpointSocket.Bind(new IPEndPoint(options.TlsEndpointOptions.BoundIPAddress, options.GetTlsEndpointPort()));
_tlsEndpointSocket.Listen(options.ConnectionBacklog);
Task.Run(async () => await AcceptTlsEndpointConnectionsAsync(_cancellationTokenSource.Token).ConfigureAwait(false), _cancellationTokenSource.Token).ConfigureAwait(false);
Task.Run(() => AcceptTlsEndpointConnectionsAsync(_cancellationTokenSource.Token), _cancellationTokenSource.Token);
}
return Task.FromResult(0);
@@ -1,11 +1,13 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Channel;
using MQTTnet.Client;
using MQTTnet.Exceptions;
namespace MQTTnet.Implementations
{
@@ -13,20 +15,25 @@ namespace MQTTnet.Implementations
{
// ReSharper disable once MemberCanBePrivate.Global
// ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global
public static int BufferSize { get; set; } = 4096 * 20; // Can be changed for fine tuning by library user.
public static int BufferSize { get; set; } = 4096; // Can be changed for fine tuning by library user.
private readonly byte[] _chunckBuffer = new byte[BufferSize];
private readonly Queue<byte> _buffer = new Queue<byte>(BufferSize);
private readonly MqttClientWebSocketOptions _options;
private ClientWebSocket _webSocket;
private WebSocket _webSocket;
public MqttWebSocketChannel(MqttClientWebSocketOptions options)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
}
public Stream SendStream { get; private set; }
public Stream ReceiveStream { get; private set; }
public MqttWebSocketChannel(WebSocket webSocket)
{
_webSocket = webSocket ?? throw new ArgumentNullException(nameof(webSocket));
}
public async Task ConnectAsync()
public async Task ConnectAsync(CancellationToken cancellationToken)
{
var uri = _options.Uri;
if (!uri.StartsWith("ws://", StringComparison.OrdinalIgnoreCase) && !uri.StartsWith("wss://", StringComparison.OrdinalIgnoreCase))
@@ -41,13 +48,13 @@ namespace MQTTnet.Implementations
}
}
_webSocket = new ClientWebSocket();
var clientWebSocket = new ClientWebSocket();
if (_options.RequestHeaders != null)
{
foreach (var requestHeader in _options.RequestHeaders)
{
_webSocket.Options.SetRequestHeader(requestHeader.Key, requestHeader.Value);
clientWebSocket.Options.SetRequestHeader(requestHeader.Key, requestHeader.Value);
}
}
@@ -55,28 +62,26 @@ namespace MQTTnet.Implementations
{
foreach (var subProtocol in _options.SubProtocols)
{
_webSocket.Options.AddSubProtocol(subProtocol);
clientWebSocket.Options.AddSubProtocol(subProtocol);
}
}
if (_options.CookieContainer != null)
{
_webSocket.Options.Cookies = _options.CookieContainer;
clientWebSocket.Options.Cookies = _options.CookieContainer;
}
if (_options.TlsOptions?.UseTls == true && _options.TlsOptions?.Certificates != null)
{
_webSocket.Options.ClientCertificates = new X509CertificateCollection();
clientWebSocket.Options.ClientCertificates = new X509CertificateCollection();
foreach (var certificate in _options.TlsOptions.Certificates)
{
_webSocket.Options.ClientCertificates.Add(new X509Certificate(certificate));
clientWebSocket.Options.ClientCertificates.Add(new X509Certificate(certificate));
}
}
await _webSocket.ConnectAsync(new Uri(uri), CancellationToken.None).ConfigureAwait(false);
SendStream = new WebSocketStream(_webSocket);
ReceiveStream = SendStream;
await clientWebSocket.ConnectAsync(new Uri(uri), cancellationToken).ConfigureAwait(false);
_webSocket = clientWebSocket;
}
public async Task DisconnectAsync()
@@ -94,6 +99,56 @@ namespace MQTTnet.Implementations
Dispose();
}
public async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
var bytesRead = 0;
// Use existing date from buffer.
while (count > 0 && _buffer.Any())
{
buffer[offset] = _buffer.Dequeue();
count--;
bytesRead++;
offset++;
}
if (count == 0)
{
return bytesRead;
}
// Fetch new data if the buffer is not full.
while (_webSocket.State == WebSocketState.Open)
{
await FetchChunkAsync(cancellationToken).ConfigureAwait(false);
while (count > 0 && _buffer.Any())
{
buffer[offset] = _buffer.Dequeue();
count--;
bytesRead++;
offset++;
}
if (count == 0)
{
return bytesRead;
}
}
if (_webSocket.State == WebSocketState.Closed)
{
throw new MqttCommunicationException("WebSocket connection closed.");
}
return bytesRead;
}
public Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return _webSocket.SendAsync(new ArraySegment<byte>(buffer, offset, count), WebSocketMessageType.Binary, true, cancellationToken);
}
public void Dispose()
{
try
@@ -108,5 +163,26 @@ namespace MQTTnet.Implementations
_webSocket = null;
}
}
private async Task FetchChunkAsync(CancellationToken cancellationToken)
{
var response = await _webSocket.ReceiveAsync(new ArraySegment<byte>(_chunckBuffer, 0, _chunckBuffer.Length), cancellationToken).ConfigureAwait(false);
if (response.MessageType == WebSocketMessageType.Close)
{
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false);
}
else if (response.MessageType == WebSocketMessageType.Binary)
{
for (var i = 0; i < response.Count; i++)
{
_buffer.Enqueue(_chunckBuffer[i]);
}
}
else if (response.MessageType == WebSocketMessageType.Text)
{
throw new MqttProtocolViolationException("WebSocket channel received TEXT message.");
}
}
}
}
@@ -1,137 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;
using MQTTnet.Exceptions;
namespace MQTTnet.Implementations
{
public class WebSocketStream : Stream
{
private readonly byte[] _chunckBuffer = new byte[MqttWebSocketChannel.BufferSize];
private readonly Queue<byte> _buffer = new Queue<byte>(MqttWebSocketChannel.BufferSize);
private readonly WebSocket _webSocket;
public WebSocketStream(WebSocket webSocket)
{
_webSocket = webSocket ?? throw new ArgumentNullException(nameof(webSocket));
}
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => true;
public override long Length => throw new NotSupportedException();
public override long Position
{
get => throw new NotSupportedException();
set => throw new NotSupportedException();
}
public override void Flush()
{
}
public override Task FlushAsync(CancellationToken cancellationToken)
{
return Task.FromResult(0);
}
public override int Read(byte[] buffer, int offset, int count)
{
return ReadAsync(buffer, offset, count).GetAwaiter().GetResult();
}
public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
var bytesRead = 0;
// Use existing date from buffer.
while (count > 0 && _buffer.Any())
{
buffer[offset] = _buffer.Dequeue();
count--;
bytesRead++;
offset++;
}
if (count == 0)
{
return bytesRead;
}
// Fetch new data if the buffer is not full.
while (_webSocket.State == WebSocketState.Open)
{
await FetchChunkAsync(cancellationToken).ConfigureAwait(false);
while (count > 0 && _buffer.Any())
{
buffer[offset] = _buffer.Dequeue();
count--;
bytesRead++;
offset++;
}
if (count == 0)
{
return bytesRead;
}
}
if (_webSocket.State == WebSocketState.Closed)
{
throw new MqttCommunicationException("WebSocket connection closed.");
}
return bytesRead;
}
public override void Write(byte[] buffer, int offset, int count)
{
WriteAsync(buffer, offset, count).GetAwaiter().GetResult();
}
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
return _webSocket.SendAsync(new ArraySegment<byte>(buffer, offset, count), WebSocketMessageType.Binary, true, cancellationToken);
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
private async Task FetchChunkAsync(CancellationToken cancellationToken)
{
var response = await _webSocket.ReceiveAsync(new ArraySegment<byte>(_chunckBuffer, 0, _chunckBuffer.Length), cancellationToken).ConfigureAwait(false);
if (response.MessageType == WebSocketMessageType.Close)
{
await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, cancellationToken).ConfigureAwait(false);
}
else if (response.MessageType == WebSocketMessageType.Binary)
{
for (var i = 0; i < response.Count; i++)
{
_buffer.Enqueue(_chunckBuffer[i]);
}
}
else if (response.MessageType == WebSocketMessageType.Text)
{
throw new MqttProtocolViolationException("WebSocket channel received TEXT message.");
}
}
}
}