Initial WebSocket projects, handshake, framing.

This commit is contained in:
Chris Ross 2014-03-04 21:09:12 -08:00
commit dbd084cb2c
12 changed files with 1062 additions and 0 deletions

21
.gitignore vendored Normal file
View File

@ -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

28
WebSockets.sln Normal file
View File

@ -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

View File

@ -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();
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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>

View File

@ -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")]

View File

@ -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);
}
}
}

View File

@ -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>

View File

@ -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")]

View File

@ -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();
}
}
}
}

View File

@ -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>