Initial WebSocket projects, handshake, framing.
This commit is contained in:
commit
dbd084cb2c
|
|
@ -0,0 +1,21 @@
|
|||
bin
|
||||
obj
|
||||
*.suo
|
||||
*.user
|
||||
_ReSharper.*
|
||||
*.DS_Store
|
||||
*.userprefs
|
||||
*.pidb
|
||||
*.vspx
|
||||
*.psess
|
||||
TestResults/*
|
||||
TestResult.xml
|
||||
nugetkey
|
||||
packages
|
||||
target
|
||||
artifacts
|
||||
StyleCop.Cache
|
||||
node_modules
|
||||
*.snk
|
||||
.nuget/NuGet.exe
|
||||
docs/build
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 2013
|
||||
VisualStudioVersion = 12.0.30203.2
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Net.WebSockets", "src\Microsoft.Net.WebSockets\Microsoft.Net.WebSockets.csproj", "{6C1D09CA-F799-44AE-8EC8-9D19C76080C1}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Net.WebSockets.Test", "test\Microsoft.Net.WebSockets.Test\Microsoft.Net.WebSockets.Test.csproj", "{EF1FE910-6E0C-4DE8-8CC1-6118B726A59E}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{6C1D09CA-F799-44AE-8EC8-9D19C76080C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6C1D09CA-F799-44AE-8EC8-9D19C76080C1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6C1D09CA-F799-44AE-8EC8-9D19C76080C1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6C1D09CA-F799-44AE-8EC8-9D19C76080C1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{EF1FE910-6E0C-4DE8-8CC1-6118B726A59E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{EF1FE910-6E0C-4DE8-8CC1-6118B726A59E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EF1FE910-6E0C-4DE8-8CC1-6118B726A59E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EF1FE910-6E0C-4DE8-8CC1-6118B726A59E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Net.WebSockets
|
||||
{
|
||||
public class ClientWebSocket : WebSocket
|
||||
{
|
||||
private readonly Stream _stream;
|
||||
private readonly string _subProtocl;
|
||||
private WebSocketState _state;
|
||||
|
||||
private byte[] _receiveBuffer;
|
||||
private int _receiveOffset;
|
||||
private int _receiveCount;
|
||||
|
||||
private FrameHeader _frameInProgress;
|
||||
private long _frameBytesRemaining = 0;
|
||||
|
||||
public ClientWebSocket(Stream stream, string subProtocol, int receiveBufferSize)
|
||||
{
|
||||
_stream = stream;
|
||||
_subProtocl = subProtocol;
|
||||
_state = WebSocketState.Open;
|
||||
_receiveBuffer = new byte[receiveBufferSize];
|
||||
}
|
||||
|
||||
public override WebSocketCloseStatus? CloseStatus
|
||||
{
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
public override string CloseStatusDescription
|
||||
{
|
||||
get { throw new NotImplementedException(); }
|
||||
}
|
||||
|
||||
public override WebSocketState State
|
||||
{
|
||||
get { return _state; }
|
||||
}
|
||||
|
||||
public override string SubProtocol
|
||||
{
|
||||
get { return _subProtocl; }
|
||||
}
|
||||
|
||||
public override async Task SendAsync(ArraySegment<byte> buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO: Validate arguments
|
||||
// TODO: Check state
|
||||
// TODO: Check concurrent writes
|
||||
// TODO: Check ping/pong state
|
||||
// TODO: Masking
|
||||
FrameHeader frameHeader = new FrameHeader(endOfMessage, GetOpCode(messageType), true, 0, buffer.Count);
|
||||
ArraySegment<byte> segment = frameHeader.Buffer;
|
||||
await _stream.WriteAsync(segment.Array, segment.Offset, segment.Count, cancellationToken);
|
||||
await _stream.WriteAsync(buffer.Array, buffer.Offset, buffer.Count, cancellationToken);
|
||||
}
|
||||
|
||||
private int GetOpCode(WebSocketMessageType messageType)
|
||||
{
|
||||
switch (messageType)
|
||||
{
|
||||
case WebSocketMessageType.Text: return Constants.OpCodes.TextFrame;
|
||||
case WebSocketMessageType.Binary: return Constants.OpCodes.BinaryFrame;
|
||||
case WebSocketMessageType.Close: return Constants.OpCodes.CloseFrame;
|
||||
default: throw new NotImplementedException(messageType.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public async override Task<WebSocketReceiveResult> ReceiveAsync(ArraySegment<byte> buffer, CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO: Validate arguments
|
||||
// TODO: Check state
|
||||
// TODO: Check concurrent reads
|
||||
// TODO: Check ping/pong state
|
||||
|
||||
// No active frame
|
||||
if (_frameInProgress == null)
|
||||
{
|
||||
await EnsureDataAvailableOrReadAsync(2, cancellationToken);
|
||||
int frameHeaderSize = FrameHeader.CalculateFrameHeaderSize(_receiveBuffer[_receiveOffset + 1]);
|
||||
await EnsureDataAvailableOrReadAsync(frameHeaderSize, cancellationToken);
|
||||
_frameInProgress = new FrameHeader(new ArraySegment<byte>(_receiveBuffer, _receiveOffset, frameHeaderSize));
|
||||
_receiveOffset += frameHeaderSize;
|
||||
_receiveCount -= frameHeaderSize;
|
||||
_frameBytesRemaining = _frameInProgress.DataLength;
|
||||
}
|
||||
|
||||
WebSocketReceiveResult result;
|
||||
// TODO: Close frame
|
||||
// TODO: Ping or Pong frames
|
||||
|
||||
// Make sure there's at least some data in the buffer
|
||||
if (_frameBytesRemaining > 0)
|
||||
{
|
||||
await EnsureDataAvailableOrReadAsync(1, cancellationToken);
|
||||
}
|
||||
|
||||
// Copy buffered data to the users buffer
|
||||
int bytesToRead = (int)Math.Min((long)buffer.Count, _frameBytesRemaining);
|
||||
if (_receiveCount > 0)
|
||||
{
|
||||
int bytesToCopy = Math.Min(bytesToRead, _receiveCount);
|
||||
Array.Copy(_receiveBuffer, _receiveOffset, buffer.Array, buffer.Offset, bytesToCopy);
|
||||
if (bytesToCopy == _frameBytesRemaining)
|
||||
{
|
||||
result = new WebSocketReceiveResult(bytesToCopy, GetMessageType(_frameInProgress.OpCode), _frameInProgress.Fin);
|
||||
_frameInProgress = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = new WebSocketReceiveResult(bytesToCopy, GetMessageType(_frameInProgress.OpCode), false);
|
||||
}
|
||||
_frameBytesRemaining -= bytesToCopy;
|
||||
_receiveCount -= bytesToCopy;
|
||||
_receiveOffset += bytesToCopy;
|
||||
}
|
||||
else
|
||||
{
|
||||
// End of an empty frame?
|
||||
result = new WebSocketReceiveResult(0, GetMessageType(_frameInProgress.OpCode), true);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task EnsureDataAvailableOrReadAsync(int bytes, CancellationToken cancellationToken)
|
||||
{
|
||||
// Insufficient data
|
||||
while (_receiveCount < bytes && bytes <= _receiveBuffer.Length)
|
||||
{
|
||||
// Some data in the buffer, shift down to make room
|
||||
if (_receiveCount > 0 && _receiveOffset > 0)
|
||||
{
|
||||
Array.Copy(_receiveBuffer, _receiveOffset, _receiveBuffer, 0, _receiveCount);
|
||||
}
|
||||
_receiveOffset = 0;
|
||||
// Add to the end
|
||||
int read = await _stream.ReadAsync(_receiveBuffer, _receiveCount, _receiveBuffer.Length - (_receiveCount), cancellationToken);
|
||||
if (read == 0)
|
||||
{
|
||||
throw new IOException("Unexpected end of stream");
|
||||
}
|
||||
_receiveCount += read;
|
||||
}
|
||||
}
|
||||
|
||||
private WebSocketMessageType GetMessageType(int opCode)
|
||||
{
|
||||
switch (opCode)
|
||||
{
|
||||
case Constants.OpCodes.TextFrame: return WebSocketMessageType.Text;
|
||||
case Constants.OpCodes.BinaryFrame: return WebSocketMessageType.Binary;
|
||||
case Constants.OpCodes.CloseFrame: return WebSocketMessageType.Close;
|
||||
default: throw new NotImplementedException(opCode.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public override Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override async Task CloseOutputAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken)
|
||||
{
|
||||
// TODO: Validate arguments
|
||||
// TODO: Check state
|
||||
// TODO: Check concurrent writes
|
||||
// TODO: Check ping/pong state
|
||||
_state = WebSocketState.CloseSent;
|
||||
|
||||
// TODO: Masking
|
||||
byte[] buffer = Encoding.UTF8.GetBytes(statusDescription);
|
||||
FrameHeader frameHeader = new FrameHeader(true, Constants.OpCodes.CloseFrame, true, 0, buffer.Length);
|
||||
ArraySegment<byte> segment = frameHeader.Buffer;
|
||||
await _stream.WriteAsync(segment.Array, segment.Offset, segment.Count, cancellationToken);
|
||||
await _stream.WriteAsync(buffer, 0, buffer.Length, cancellationToken);
|
||||
}
|
||||
|
||||
public override void Abort()
|
||||
{
|
||||
if (_state >= WebSocketState.Closed) // or Aborted
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_state = WebSocketState.Aborted;
|
||||
_stream.Dispose();
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (_state >= WebSocketState.Closed) // or Aborted
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_state = WebSocketState.Closed;
|
||||
_stream.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Net.WebSockets
|
||||
{
|
||||
public static class Constants
|
||||
{
|
||||
public static class Headers
|
||||
{
|
||||
public const string WebSocketVersion = "Sec-WebSocket-Version";
|
||||
public const string SupportedVersion = "13";
|
||||
}
|
||||
|
||||
public static class OpCodes
|
||||
{
|
||||
public const int TextFrame = 0x1;
|
||||
public const int BinaryFrame = 0x2;
|
||||
public const int CloseFrame = 0x8;
|
||||
public const int PingFrame = 0x8;
|
||||
public const int PongFrame = 0xA;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,235 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Net.WebSockets
|
||||
{
|
||||
public class FrameHeader
|
||||
{
|
||||
private byte[] _header;
|
||||
|
||||
public FrameHeader(ArraySegment<byte> header)
|
||||
{
|
||||
_header = new byte[header.Count];
|
||||
Array.Copy(header.Array, header.Offset, _header, 0, _header.Length);
|
||||
}
|
||||
|
||||
public FrameHeader(bool final, int opCode, bool masked, int maskKey, long dataLength)
|
||||
{
|
||||
int headerLength = 2;
|
||||
if (masked)
|
||||
{
|
||||
headerLength += 4;
|
||||
}
|
||||
|
||||
if (dataLength <= 125)
|
||||
{
|
||||
}
|
||||
else if (125 < dataLength && dataLength <= 0xFFFF)
|
||||
{
|
||||
headerLength += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
headerLength += 8;
|
||||
}
|
||||
_header = new byte[headerLength];
|
||||
|
||||
Fin = final;
|
||||
OpCode = opCode;
|
||||
Masked = masked;
|
||||
if (masked)
|
||||
{
|
||||
MaskKey = maskKey;
|
||||
}
|
||||
DataLength = dataLength;
|
||||
}
|
||||
|
||||
public bool Fin
|
||||
{
|
||||
get
|
||||
{
|
||||
return (_header[0] & 0x80) == 0x80;
|
||||
}
|
||||
private set
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
_header[0] |= 0x80;
|
||||
}
|
||||
else
|
||||
{
|
||||
_header[0] &= 0x7F;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int OpCode
|
||||
{
|
||||
get
|
||||
{
|
||||
return (_header[0] & 0xF);
|
||||
}
|
||||
private set
|
||||
{
|
||||
// TODO: Clear out a prior value?
|
||||
_header[0] |= (byte)(value & 0xF);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Masked
|
||||
{
|
||||
get
|
||||
{
|
||||
return (_header[1] & 0x80) == 0x80;
|
||||
}
|
||||
private set
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
_header[1] |= 0x80;
|
||||
}
|
||||
else
|
||||
{
|
||||
_header[1] &= 0x7F;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int MaskKey
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!Masked)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int offset = ExtendedLengthFieldSize + 2;
|
||||
return (_header[offset] << 24) + (_header[offset + 1] << 16)
|
||||
+ (_header[offset + 2] << 8) + _header[offset + 4];
|
||||
}
|
||||
private set
|
||||
{
|
||||
int offset = ExtendedLengthFieldSize + 2;
|
||||
_header[offset] = (byte)(value >> 24);
|
||||
_header[offset + 1] = (byte)(value >> 16);
|
||||
_header[offset + 2] = (byte)(value >> 8);
|
||||
_header[offset + 3] = (byte)value;
|
||||
}
|
||||
}
|
||||
|
||||
public int PayloadField
|
||||
{
|
||||
get
|
||||
{
|
||||
return (_header[1] & 0x7F);
|
||||
}
|
||||
private set
|
||||
{
|
||||
// TODO: Clear out a prior value?
|
||||
_header[1] |= (byte)(value & 0x7F);
|
||||
}
|
||||
}
|
||||
|
||||
public int ExtendedLengthFieldSize
|
||||
{
|
||||
get
|
||||
{
|
||||
int payloadField = PayloadField;
|
||||
if (payloadField <= 125)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if (payloadField == 126)
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
return 8;
|
||||
}
|
||||
}
|
||||
|
||||
public long DataLength
|
||||
{
|
||||
get
|
||||
{
|
||||
int extendedFieldSize = ExtendedLengthFieldSize;
|
||||
if (extendedFieldSize == 0)
|
||||
{
|
||||
return PayloadField;
|
||||
}
|
||||
if (extendedFieldSize == 2)
|
||||
{
|
||||
return (_header[2] << 8) + _header[3];
|
||||
}
|
||||
return (_header[2] << 56) + (_header[3] << 48)
|
||||
+ (_header[4] << 40) + (_header[5] << 32)
|
||||
+ (_header[6] << 24) + (_header[7] << 16)
|
||||
+ (_header[8] << 8) + _header[9];
|
||||
}
|
||||
private set
|
||||
{
|
||||
if (value <= 125)
|
||||
{
|
||||
PayloadField = (int)value;
|
||||
}
|
||||
else if (125 < value && value <= 0xFFFF)
|
||||
{
|
||||
PayloadField = 0x7E;
|
||||
|
||||
_header[2] = (byte)(value >> 8);
|
||||
_header[3] = (byte)value;
|
||||
}
|
||||
else
|
||||
{
|
||||
PayloadField = 0x7F;
|
||||
|
||||
_header[2] = (byte)(value >> 56);
|
||||
_header[3] = (byte)(value >> 48);
|
||||
_header[4] = (byte)(value >> 40);
|
||||
_header[5] = (byte)(value >> 32);
|
||||
_header[6] = (byte)(value >> 24);
|
||||
_header[7] = (byte)(value >> 16);
|
||||
_header[8] = (byte)(value >> 8);
|
||||
_header[9] = (byte)value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ArraySegment<byte> Buffer
|
||||
{
|
||||
get
|
||||
{
|
||||
return new ArraySegment<byte>(_header);
|
||||
}
|
||||
}
|
||||
|
||||
// Given the second bytes of a frame, calculate how long the whole frame header should be.
|
||||
// Range 2-12 bytes
|
||||
public static int CalculateFrameHeaderSize(byte b2)
|
||||
{
|
||||
int headerLength = 2;
|
||||
if ((b2 & 0x80) == 0x80) // Masked
|
||||
{
|
||||
headerLength += 4;
|
||||
}
|
||||
|
||||
int payloadField = (b2 & 0x7F);
|
||||
if (payloadField <= 125)
|
||||
{
|
||||
// headerLength += 0
|
||||
}
|
||||
else if (payloadField == 126)
|
||||
{
|
||||
headerLength += 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
headerLength += 8;
|
||||
}
|
||||
return headerLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{6C1D09CA-F799-44AE-8EC8-9D19C76080C1}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Microsoft.Net.WebSockets</RootNamespace>
|
||||
<AssemblyName>Microsoft.Net.WebSockets</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="ClientWebSocket.cs" />
|
||||
<Compile Include="Constants.cs" />
|
||||
<Compile Include="FrameHeader.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="WebSocketClient.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Microsoft.Net.WebSockets")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Microsoft.Net.WebSockets")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2014")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("9a9e41ae-1494-4d87-a66f-a4019ff68ce5")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Net.WebSockets.Client
|
||||
{
|
||||
public class WebSocketClient
|
||||
{
|
||||
static WebSocketClient()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Only call once
|
||||
WebSocket.RegisterPrefixes();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Already registered
|
||||
}
|
||||
}
|
||||
|
||||
public WebSocketClient()
|
||||
{
|
||||
ReceiveBufferSize = 1024;
|
||||
}
|
||||
|
||||
public int ReceiveBufferSize
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public async Task<WebSocket> ConnectAsync(Uri uri, CancellationToken cancellationToken)
|
||||
{
|
||||
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
|
||||
|
||||
request.Headers[Constants.Headers.WebSocketVersion] = Constants.Headers.SupportedVersion;
|
||||
// TODO: Sub-protocols
|
||||
|
||||
WebResponse response = await request.GetResponseAsync();
|
||||
// TODO: Validate handshake
|
||||
|
||||
Stream stream = response.GetResponseStream();
|
||||
// Console.WriteLine(stream.CanWrite + " " + stream.CanRead);
|
||||
|
||||
return new ClientWebSocket(stream, null, ReceiveBufferSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{EF1FE910-6E0C-4DE8-8CC1-6118B726A59E}</ProjectGuid>
|
||||
<OutputType>Library</OutputType>
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>Microsoft.Net.WebSockets.Test</RootNamespace>
|
||||
<AssemblyName>Microsoft.Net.WebSockets.Test</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="xunit">
|
||||
<HintPath>..\..\packages\xunit.1.9.2\lib\net20\xunit.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="xunit.extensions">
|
||||
<HintPath>..\..\packages\xunit.extensions.1.9.2\lib\net20\xunit.extensions.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="WebSocketClientTests.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.Net.WebSockets\Microsoft.Net.WebSockets.csproj">
|
||||
<Project>{6c1d09ca-f799-44ae-8ec8-9d19c76080c1}</Project>
|
||||
<Name>Microsoft.Net.WebSockets</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
|
||||
Other similar extension points exist, see Microsoft.Common.targets.
|
||||
<Target Name="BeforeBuild">
|
||||
</Target>
|
||||
<Target Name="AfterBuild">
|
||||
</Target>
|
||||
-->
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Microsoft.Net.WebSockets.Test")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("Microsoft.Net.WebSockets.Test")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2014")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("237d6e8f-6e5e-4c3f-96b4-b19cf3bf4d80")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||
|
|
@ -0,0 +1,284 @@
|
|||
using Microsoft.Net.WebSockets.Client;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.Net.WebSockets.Test
|
||||
{
|
||||
public class WebSocketClientTests
|
||||
{
|
||||
private static string ClientAddress = "ws://localhost:8080/";
|
||||
private static string ServerAddress = "http://localhost:8080/";
|
||||
|
||||
[Fact]
|
||||
public async Task Connect_Success()
|
||||
{
|
||||
using (HttpListener listener = new HttpListener())
|
||||
{
|
||||
listener.Prefixes.Add(ServerAddress);
|
||||
listener.Start();
|
||||
Task<HttpListenerContext> serverAccept = listener.GetContextAsync();
|
||||
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
Task<WebSocket> clientConnect = client.ConnectAsync(new Uri(ClientAddress), CancellationToken.None);
|
||||
|
||||
HttpListenerContext serverContext = await serverAccept;
|
||||
Assert.True(serverContext.Request.IsWebSocketRequest);
|
||||
HttpListenerWebSocketContext serverWebSocketContext = await serverContext.AcceptWebSocketAsync(null);
|
||||
|
||||
WebSocket clientSocket = await clientConnect;
|
||||
clientSocket.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendShortData_Success()
|
||||
{
|
||||
using (HttpListener listener = new HttpListener())
|
||||
{
|
||||
listener.Prefixes.Add(ServerAddress);
|
||||
listener.Start();
|
||||
Task<HttpListenerContext> serverAccept = listener.GetContextAsync();
|
||||
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
Task<WebSocket> clientConnect = client.ConnectAsync(new Uri(ClientAddress), CancellationToken.None);
|
||||
|
||||
HttpListenerContext serverContext = await serverAccept;
|
||||
Assert.True(serverContext.Request.IsWebSocketRequest);
|
||||
HttpListenerWebSocketContext serverWebSocketContext = await serverContext.AcceptWebSocketAsync(null);
|
||||
|
||||
WebSocket clientSocket = await clientConnect;
|
||||
|
||||
byte[] orriginalData = Encoding.UTF8.GetBytes("Hello World");
|
||||
await clientSocket.SendAsync(new ArraySegment<byte>(orriginalData), WebSocketMessageType.Binary, true, CancellationToken.None);
|
||||
|
||||
byte[] serverBuffer = new byte[orriginalData.Length];
|
||||
WebSocketReceiveResult result = await serverWebSocketContext.WebSocket.ReceiveAsync(new ArraySegment<byte>(serverBuffer), CancellationToken.None);
|
||||
Assert.True(result.EndOfMessage);
|
||||
Assert.Equal(orriginalData.Length, result.Count);
|
||||
Assert.Equal(WebSocketMessageType.Binary, result.MessageType);
|
||||
Assert.Equal(orriginalData, serverBuffer);
|
||||
|
||||
clientSocket.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendMediumData_Success()
|
||||
{
|
||||
using (HttpListener listener = new HttpListener())
|
||||
{
|
||||
listener.Prefixes.Add(ServerAddress);
|
||||
listener.Start();
|
||||
Task<HttpListenerContext> serverAccept = listener.GetContextAsync();
|
||||
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
Task<WebSocket> clientConnect = client.ConnectAsync(new Uri(ClientAddress), CancellationToken.None);
|
||||
|
||||
HttpListenerContext serverContext = await serverAccept;
|
||||
Assert.True(serverContext.Request.IsWebSocketRequest);
|
||||
HttpListenerWebSocketContext serverWebSocketContext = await serverContext.AcceptWebSocketAsync(null);
|
||||
|
||||
WebSocket clientSocket = await clientConnect;
|
||||
|
||||
byte[] orriginalData = Encoding.UTF8.GetBytes(new string('a', 130));
|
||||
await clientSocket.SendAsync(new ArraySegment<byte>(orriginalData), WebSocketMessageType.Text, true, CancellationToken.None);
|
||||
|
||||
byte[] serverBuffer = new byte[orriginalData.Length];
|
||||
WebSocketReceiveResult result = await serverWebSocketContext.WebSocket.ReceiveAsync(new ArraySegment<byte>(serverBuffer), CancellationToken.None);
|
||||
Assert.True(result.EndOfMessage);
|
||||
Assert.Equal(orriginalData.Length, result.Count);
|
||||
Assert.Equal(WebSocketMessageType.Text, result.MessageType);
|
||||
Assert.Equal(orriginalData, serverBuffer);
|
||||
|
||||
clientSocket.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SendLongData_Success()
|
||||
{
|
||||
using (HttpListener listener = new HttpListener())
|
||||
{
|
||||
listener.Prefixes.Add(ServerAddress);
|
||||
listener.Start();
|
||||
Task<HttpListenerContext> serverAccept = listener.GetContextAsync();
|
||||
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
Task<WebSocket> clientConnect = client.ConnectAsync(new Uri(ClientAddress), CancellationToken.None);
|
||||
|
||||
HttpListenerContext serverContext = await serverAccept;
|
||||
Assert.True(serverContext.Request.IsWebSocketRequest);
|
||||
HttpListenerWebSocketContext serverWebSocketContext = await serverContext.AcceptWebSocketAsync(null, 0xFFFF, TimeSpan.FromMinutes(100));
|
||||
|
||||
WebSocket clientSocket = await clientConnect;
|
||||
|
||||
byte[] orriginalData = Encoding.UTF8.GetBytes(new string('a', 0x1FFFF));
|
||||
await clientSocket.SendAsync(new ArraySegment<byte>(orriginalData), WebSocketMessageType.Text, true, CancellationToken.None);
|
||||
|
||||
byte[] serverBuffer = new byte[orriginalData.Length];
|
||||
WebSocketReceiveResult result = await serverWebSocketContext.WebSocket.ReceiveAsync(new ArraySegment<byte>(serverBuffer), CancellationToken.None);
|
||||
int intermediateCount = result.Count;
|
||||
Assert.False(result.EndOfMessage);
|
||||
Assert.Equal(WebSocketMessageType.Text, result.MessageType);
|
||||
|
||||
result = await serverWebSocketContext.WebSocket.ReceiveAsync(new ArraySegment<byte>(serverBuffer, intermediateCount, orriginalData.Length - intermediateCount), CancellationToken.None);
|
||||
intermediateCount += result.Count;
|
||||
Assert.False(result.EndOfMessage);
|
||||
Assert.Equal(WebSocketMessageType.Text, result.MessageType);
|
||||
|
||||
result = await serverWebSocketContext.WebSocket.ReceiveAsync(new ArraySegment<byte>(serverBuffer, intermediateCount, orriginalData.Length - intermediateCount), CancellationToken.None);
|
||||
intermediateCount += result.Count;
|
||||
Assert.True(result.EndOfMessage);
|
||||
Assert.Equal(orriginalData.Length, intermediateCount);
|
||||
Assert.Equal(WebSocketMessageType.Text, result.MessageType);
|
||||
|
||||
Assert.Equal(orriginalData, serverBuffer);
|
||||
|
||||
clientSocket.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReceiveShortData_Success()
|
||||
{
|
||||
using (HttpListener listener = new HttpListener())
|
||||
{
|
||||
listener.Prefixes.Add(ServerAddress);
|
||||
listener.Start();
|
||||
Task<HttpListenerContext> serverAccept = listener.GetContextAsync();
|
||||
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
Task<WebSocket> clientConnect = client.ConnectAsync(new Uri(ClientAddress), CancellationToken.None);
|
||||
|
||||
HttpListenerContext serverContext = await serverAccept;
|
||||
Assert.True(serverContext.Request.IsWebSocketRequest);
|
||||
HttpListenerWebSocketContext serverWebSocketContext = await serverContext.AcceptWebSocketAsync(null);
|
||||
|
||||
WebSocket clientSocket = await clientConnect;
|
||||
|
||||
byte[] orriginalData = Encoding.UTF8.GetBytes("Hello World");
|
||||
await serverWebSocketContext.WebSocket.SendAsync(new ArraySegment<byte>(orriginalData), WebSocketMessageType.Binary, true, CancellationToken.None);
|
||||
|
||||
byte[] clientBuffer = new byte[orriginalData.Length];
|
||||
WebSocketReceiveResult result = await clientSocket.ReceiveAsync(new ArraySegment<byte>(clientBuffer), CancellationToken.None);
|
||||
Assert.True(result.EndOfMessage);
|
||||
Assert.Equal(orriginalData.Length, result.Count);
|
||||
Assert.Equal(WebSocketMessageType.Binary, result.MessageType);
|
||||
Assert.Equal(orriginalData, clientBuffer);
|
||||
|
||||
clientSocket.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReceiveMediumData_Success()
|
||||
{
|
||||
using (HttpListener listener = new HttpListener())
|
||||
{
|
||||
listener.Prefixes.Add(ServerAddress);
|
||||
listener.Start();
|
||||
Task<HttpListenerContext> serverAccept = listener.GetContextAsync();
|
||||
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
Task<WebSocket> clientConnect = client.ConnectAsync(new Uri(ClientAddress), CancellationToken.None);
|
||||
|
||||
HttpListenerContext serverContext = await serverAccept;
|
||||
Assert.True(serverContext.Request.IsWebSocketRequest);
|
||||
HttpListenerWebSocketContext serverWebSocketContext = await serverContext.AcceptWebSocketAsync(null);
|
||||
|
||||
WebSocket clientSocket = await clientConnect;
|
||||
|
||||
byte[] orriginalData = Encoding.UTF8.GetBytes(new string('a', 130));
|
||||
await serverWebSocketContext.WebSocket.SendAsync(new ArraySegment<byte>(orriginalData), WebSocketMessageType.Binary, true, CancellationToken.None);
|
||||
|
||||
byte[] clientBuffer = new byte[orriginalData.Length];
|
||||
WebSocketReceiveResult result = await clientSocket.ReceiveAsync(new ArraySegment<byte>(clientBuffer), CancellationToken.None);
|
||||
Assert.True(result.EndOfMessage);
|
||||
Assert.Equal(orriginalData.Length, result.Count);
|
||||
Assert.Equal(WebSocketMessageType.Binary, result.MessageType);
|
||||
Assert.Equal(orriginalData, clientBuffer);
|
||||
|
||||
clientSocket.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReceiveLongDataInSmallBuffer_Success()
|
||||
{
|
||||
using (HttpListener listener = new HttpListener())
|
||||
{
|
||||
listener.Prefixes.Add(ServerAddress);
|
||||
listener.Start();
|
||||
Task<HttpListenerContext> serverAccept = listener.GetContextAsync();
|
||||
|
||||
WebSocketClient client = new WebSocketClient();
|
||||
Task<WebSocket> clientConnect = client.ConnectAsync(new Uri(ClientAddress), CancellationToken.None);
|
||||
|
||||
HttpListenerContext serverContext = await serverAccept;
|
||||
Assert.True(serverContext.Request.IsWebSocketRequest);
|
||||
HttpListenerWebSocketContext serverWebSocketContext = await serverContext.AcceptWebSocketAsync(null);
|
||||
|
||||
WebSocket clientSocket = await clientConnect;
|
||||
|
||||
byte[] orriginalData = Encoding.UTF8.GetBytes(new string('a', 0x1FFFF));
|
||||
await serverWebSocketContext.WebSocket.SendAsync(new ArraySegment<byte>(orriginalData), WebSocketMessageType.Binary, true, CancellationToken.None);
|
||||
|
||||
byte[] clientBuffer = new byte[orriginalData.Length];
|
||||
WebSocketReceiveResult result;
|
||||
int receivedCount = 0;
|
||||
do
|
||||
{
|
||||
result = await clientSocket.ReceiveAsync(new ArraySegment<byte>(clientBuffer, receivedCount, clientBuffer.Length - receivedCount), CancellationToken.None);
|
||||
receivedCount += result.Count;
|
||||
Assert.Equal(WebSocketMessageType.Binary, result.MessageType);
|
||||
}
|
||||
while (!result.EndOfMessage);
|
||||
|
||||
Assert.Equal(orriginalData.Length, receivedCount);
|
||||
Assert.Equal(WebSocketMessageType.Binary, result.MessageType);
|
||||
Assert.Equal(orriginalData, clientBuffer);
|
||||
|
||||
clientSocket.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReceiveLongDataInLargeBuffer_Success()
|
||||
{
|
||||
using (HttpListener listener = new HttpListener())
|
||||
{
|
||||
listener.Prefixes.Add(ServerAddress);
|
||||
listener.Start();
|
||||
Task<HttpListenerContext> serverAccept = listener.GetContextAsync();
|
||||
|
||||
WebSocketClient client = new WebSocketClient() { ReceiveBufferSize = 0xFFFFFF };
|
||||
Task<WebSocket> clientConnect = client.ConnectAsync(new Uri(ClientAddress), CancellationToken.None);
|
||||
|
||||
HttpListenerContext serverContext = await serverAccept;
|
||||
Assert.True(serverContext.Request.IsWebSocketRequest);
|
||||
HttpListenerWebSocketContext serverWebSocketContext = await serverContext.AcceptWebSocketAsync(null);
|
||||
|
||||
WebSocket clientSocket = await clientConnect;
|
||||
|
||||
byte[] orriginalData = Encoding.UTF8.GetBytes(new string('a', 0x1FFFF));
|
||||
await serverWebSocketContext.WebSocket.SendAsync(new ArraySegment<byte>(orriginalData), WebSocketMessageType.Binary, true, CancellationToken.None);
|
||||
|
||||
byte[] clientBuffer = new byte[orriginalData.Length];
|
||||
WebSocketReceiveResult result = await clientSocket.ReceiveAsync(new ArraySegment<byte>(clientBuffer), CancellationToken.None);
|
||||
Assert.True(result.EndOfMessage);
|
||||
Assert.Equal(orriginalData.Length, result.Count);
|
||||
Assert.Equal(WebSocketMessageType.Binary, result.MessageType);
|
||||
Assert.Equal(orriginalData, clientBuffer);
|
||||
|
||||
clientSocket.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="xunit" version="1.9.2" targetFramework="net45" />
|
||||
<package id="xunit.extensions" version="1.9.2" targetFramework="net45" />
|
||||
</packages>
|
||||
Loading…
Reference in New Issue