Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Commit

Permalink
Merge pull request #10165 from stephentoub/websocket_refactor
Browse files Browse the repository at this point in the history
Refactor ManagedWebSocket out of ClientWebSocket on Unix
  • Loading branch information
stephentoub authored Jul 21, 2016
2 parents 588f973 + c8c5adb commit 7b62501
Show file tree
Hide file tree
Showing 9 changed files with 1,949 additions and 1,829 deletions.
50 changes: 35 additions & 15 deletions src/Common/src/System/Net/WebSockets/WebSocketValidate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,14 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

using Microsoft.Win32;

namespace System.Net.WebSockets
{
internal static class WebSocketValidate
{
internal const int MaxControlFramePayloadLength = 123;

private const int CloseStatusCodeAbort = 1006;
private const int CloseStatusCodeFailedTLSHandshake = 1015;
private const int InvalidCloseStatusCodesFrom = 0;
Expand Down Expand Up @@ -53,8 +46,7 @@ internal static void ValidateSubprotocol(string subProtocol)

if (invalidChar != null)
{
throw new ArgumentException(SR.Format(SR.net_WebSockets_InvalidCharInProtocolString, subProtocol, invalidChar),
nameof(subProtocol));
throw new ArgumentException(SR.Format(SR.net_WebSockets_InvalidCharInProtocolString, subProtocol, invalidChar), nameof(subProtocol));
}
}

Expand All @@ -65,7 +57,7 @@ internal static void ValidateCloseStatus(WebSocketCloseStatus closeStatus, strin
throw new ArgumentException(SR.Format(SR.net_WebSockets_ReasonNotNull,
statusDescription,
WebSocketCloseStatus.Empty),
nameof(statusDescription));
nameof(statusDescription));
}

int closeStatusCode = (int)closeStatus;
Expand All @@ -78,7 +70,7 @@ internal static void ValidateCloseStatus(WebSocketCloseStatus closeStatus, strin
// CloseStatus 1006 means Aborted - this will never appear on the wire and is reflected by calling WebSocket.Abort
throw new ArgumentException(SR.Format(SR.net_WebSockets_InvalidCloseStatusCode,
closeStatusCode),
nameof(closeStatus));
nameof(closeStatus));
}

int length = 0;
Expand All @@ -87,12 +79,12 @@ internal static void ValidateCloseStatus(WebSocketCloseStatus closeStatus, strin
length = Encoding.UTF8.GetByteCount(statusDescription);
}

if (length > WebSocketValidate.MaxControlFramePayloadLength)
if (length > MaxControlFramePayloadLength)
{
throw new ArgumentException(SR.Format(SR.net_WebSockets_InvalidCloseStatusDescription,
statusDescription,
WebSocketValidate.MaxControlFramePayloadLength),
nameof(statusDescription));
MaxControlFramePayloadLength),
nameof(statusDescription));
}
}

Expand All @@ -101,12 +93,40 @@ internal static void ThrowPlatformNotSupportedException()
throw new PlatformNotSupportedException(SR.net_WebSockets_UnsupportedPlatform);
}

internal static void ValidateArraySegment<T>(ArraySegment<T> arraySegment, string parameterName)
internal static void ValidateArraySegment(ArraySegment<byte> arraySegment, string parameterName)
{
if (arraySegment.Array == null)
{
throw new ArgumentNullException(parameterName + ".Array");
}
}

internal static void ThrowIfInvalidState(WebSocketState currentState, bool isDisposed, WebSocketState[] validStates)
{
string validStatesText = string.Empty;

if (validStates != null && validStates.Length > 0)
{
foreach (WebSocketState validState in validStates)
{
if (currentState == validState)
{
// Ordering is important to maintain .NET 4.5 WebSocket implementation exception behavior.
if (isDisposed)
{
throw new ObjectDisposedException(nameof(ClientWebSocket));
}

return;
}
}

validStatesText = string.Join(", ", validStates);
}

throw new WebSocketException(
WebSocketError.InvalidState,
SR.Format(SR.net_WebSockets_InvalidState, currentState, validStatesText));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,9 @@
<Compile Include="System\Net\WebSockets\WinRTWebSocket.cs" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetsUnix)' == 'true' ">
<Compile Include="System\Net\WebSockets\WebSocketHandle.Unix.cs" />
<Compile Include="System\Net\Sockets\AsyncEventArgsNetworkStream.cs" />
<Compile Include="System\Net\WebSockets\WebSocketHandle.Managed.cs" />
<Compile Include="System\Net\WebSockets\ManagedWebSocket.cs" />
<Compile Include="$(CommonPath)\System\StringExtensions.cs">
<Link>Common\System\StringExtensions.cs</Link>
</Compile>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.IO;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

namespace System.Net.Sockets
{
/// <summary>
/// A custom network stream that stores and reuses a single SocketAsyncEventArgs instance
/// for reads and a single SocketAsyncEventArgs instance for writes. This limits it to
/// supporting a single read and a single write at a time, but with much less per-operation
/// overhead than with System.Net.Sockets.NetworkStream.
/// </summary>
internal sealed class AsyncEventArgsNetworkStream : NetworkStream
{
private readonly Socket _socket;
private readonly SocketAsyncEventArgs _readArgs;
private readonly SocketAsyncEventArgs _writeArgs;

private AsyncTaskMethodBuilder<int> _readAtmb;
private AsyncTaskMethodBuilder _writeAtmb;
private bool _disposed;

public AsyncEventArgsNetworkStream(Socket socket) : base(socket, ownsSocket: true)
{
_socket = socket;

_readArgs = new SocketAsyncEventArgs();
_readArgs.Completed += ReadCompleted;

_writeArgs = new SocketAsyncEventArgs();
_writeArgs.Completed += WriteCompleted;
}

protected override void Dispose(bool disposing)
{
base.Dispose(disposing);

if (disposing && !_disposed)
{
_disposed = true;
try
{
_readArgs.Dispose();
_writeArgs.Dispose();
}
catch (ObjectDisposedException) { }
}
}

public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled<int>(cancellationToken);
}

_readAtmb = new AsyncTaskMethodBuilder<int>();
Task<int> t = _readAtmb.Task;

_readArgs.SetBuffer(buffer, offset, count);
if (!_socket.ReceiveAsync(_readArgs))
{
ReadCompleted(null, _readArgs);
}

return t;
}

private void ReadCompleted(object sender, SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
_readAtmb.SetResult(e.BytesTransferred);
}
else
{
_readAtmb.SetException(CreateException(e.SocketError));
}
}

public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled(cancellationToken);
}

_writeAtmb = new AsyncTaskMethodBuilder();
Task t = _writeAtmb.Task;

_writeArgs.SetBuffer(buffer, offset, count);
if (!_socket.SendAsync(_writeArgs))
{
// TODO: #4900 This path should be hit very frequently (sends should very frequently simply
// write into the kernel's send buffer), but it's practically never getting hit due to the current
// System.Net.Sockets.dll implementation that always completing asynchronously on success :(
// If that doesn't get fixed, we should try to come up with some alternative here. This is
// an important path, in part as it means the caller will complete awaits synchronously rather
// than spending the costs associated with yielding in each async method up the call chain.
// (This applies to ReadAsync as well, but typically to a much less extent.)
WriteCompleted(null, _writeArgs);
}

return t;
}

private void WriteCompleted(object sender, SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
_writeAtmb.SetResult();
}
else
{
_writeAtmb.SetException(CreateException(e.SocketError));
}
}

private Exception CreateException(SocketError error)
{
if (_disposed)
{
return new ObjectDisposedException(GetType().Name);
}
else if (error == SocketError.OperationAborted)
{
return new OperationCanceledException();
}
else
{
return new IOException(SR.net_WebSockets_Generic, new SocketException((int)error));
}
}
}
}
Loading

0 comments on commit 7b62501

Please sign in to comment.