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