Added WebSocketServer

This commit is contained in:
sta 2012-08-04 15:51:31 +09:00
parent b1463379af
commit ec79f59229
64 changed files with 936 additions and 68 deletions

Binary file not shown.

View File

@ -73,8 +73,9 @@ namespace Example
ThreadPool.QueueUserWorkItem(notifyMsg);
using (WebSocket ws = new WebSocket("ws://echo.websocket.org", "echo"))
//using (WebSocket ws = new WebSocket("ws://echo.websocket.org", "echo"))
//using (WebSocket ws = new WebSocket("wss://echo.websocket.org", "echo"))
using (WebSocket ws = new WebSocket("ws://localhost:4649"))
{
ws.OnOpen += (sender, e) =>
{
@ -88,7 +89,7 @@ namespace Example
ws.OnError += (sender, e) =>
{
enNfMessage("[WebSocket] Error", e.Data, "notification-message-im");
enNfMessage("[WebSocket] Error", e.Message, "notification-message-im");
};
ws.OnClose += (sender, e) =>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -108,7 +108,7 @@ namespace Example
_ws.OnError += (sender, e) =>
{
enNfMessage("[AudioStreamer] error", "WS: Error: " + e.Data, "notification-message-im");
enNfMessage("[AudioStreamer] error", "WS: Error: " + e.Message, "notification-message-im");
};
_ws.OnClose += (sender, e) =>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

6
Example2/App.config Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<add key="ServerCertPath" value="/path/to/server.cer" />
</appSettings>
</configuration>

27
Example2/AssemblyInfo.cs Normal file
View File

@ -0,0 +1,27 @@
using System.Reflection;
using System.Runtime.CompilerServices;
// Information about this assembly is defined by the following attributes.
// Change them to the values specific to your project.
[assembly: AssemblyTitle("Example2")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("")]
[assembly: AssemblyCopyright("sta")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
// The form "{Major}.{Minor}.*" will automatically update the build and revision,
// and "{Major}.{Minor}.{Build}.*" will update just the revision.
[assembly: AssemblyVersion("1.0.*")]
// The following attributes are used to specify the signing key for the assembly,
// if desired. See the Mono documentation for more information about signing.
//[assembly: AssemblyDelaySign(false)]
//[assembly: AssemblyKeyFile("")]

67
Example2/Example2.csproj Normal file
View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="3.5" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>9.0.21022</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{B81A24C8-25BB-42B2-AF99-1E1EACCE74C7}</ProjectGuid>
<OutputType>Exe</OutputType>
<RootNamespace>Example2</RootNamespace>
<AssemblyName>example2</AssemblyName>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Externalconsole>true</Externalconsole>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>none</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Externalconsole>true</Externalconsole>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug_Ubuntu|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug_Ubuntu</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Externalconsole>true</Externalconsole>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release_Ubuntu|AnyCPU' ">
<DebugType>none</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Release_Ubuntu</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Externalconsole>true</Externalconsole>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
</ItemGroup>
<ItemGroup>
<Compile Include="AssemblyInfo.cs" />
<Compile Include="Program.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
<ProjectReference Include="..\websocket-sharp\websocket-sharp.csproj">
<Project>{B357BAC7-529E-4D81-A0D2-71041B19C8DE}</Project>
<Name>websocket-sharp</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
</ItemGroup>
</Project>

BIN
Example2/Example2.pidb Normal file

Binary file not shown.

36
Example2/Program.cs Normal file
View File

@ -0,0 +1,36 @@
using System;
using System.Threading;
using WebSocketSharp;
namespace Example
{
public class Program
{
public static void Main (string[] args)
{
//WebSocketServer wssv = new WebSocketServer("ws://localhost");
WebSocketServer wssv = new WebSocketServer("ws://localhost:4649");
wssv.OnConnection += (sender, e) =>
{
WebSocket ws = e.Socket;
ws.OnMessage += (sender_, e_) =>
{
// Echo
ws.Send(e_.Data);
// Chat
//wssv.Send(e_.Data);
};
};
wssv.Start();
Console.WriteLine(
"WebSocket Server ({0}) listening on address: {1} port: {2}\n", wssv.Url, wssv.Address, wssv.Port);
Console.WriteLine("Press any key to stop server...");
Console.ReadLine();
wssv.Stop();
}
}
}

BIN
Example2/bin/Debug/example2.exe Executable file

Binary file not shown.

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<add key="ServerCertPath" value="/path/to/server.cer" />
</appSettings>
</configuration>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<add key="ServerCertPath" value="/path/to/server.cer" />
</appSettings>
</configuration>

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
Example2/bin/Release/example2.exe Executable file

Binary file not shown.

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<add key="ServerCertPath" value="/path/to/server.cer" />
</appSettings>
</configuration>

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<add key="ServerCertPath" value="/path/to/server.cer" />
</appSettings>
</configuration>

Binary file not shown.

129
README.md
View File

@ -1,10 +1,12 @@
# websocket-sharp #
**websocket-sharp** is a C# implementation of a WebSocket protocol client.
**websocket-sharp** is a C# implementation of a WebSocket protocol client & server.
## Usage ##
### Step 1 ###
### WebSocket Client ###
#### Step 1 ####
Required namespaces.
@ -13,7 +15,7 @@ Required namespaces.
In `WebSocketSharp` namespace `WebSocket` class exists, in `WebSocketSharp.Frame` namespace WebSocket data frame resources (e.g. `WsFrame` class) exist.
### Step 2 ###
#### Step 2 ####
Creating instance of `WebSocket` class.
@ -22,13 +24,13 @@ Creating instance of `WebSocket` class.
...
}
So `WebSocket` class inherits `IDisposable` interface, you can use `using` statement.
`WebSocket` class inherits `IDisposable` interface, so you can use `using` statement.
### Step 3 ###
#### Step 3 ####
Setting `WebSocket` event handlers.
#### WebSocket.OnOpen event ####
##### WebSocket.OnOpen event #####
`WebSocket.OnOpen` event is emitted immediately after WebSocket connection has been established.
@ -37,9 +39,9 @@ Setting `WebSocket` event handlers.
...
};
So `e` has come across as `EventArgs.Empty`, there is no operation on `e`.
`e` has come across as `EventArgs.Empty`, so there is no operation on `e`.
#### WebSocket.OnMessage event ####
##### WebSocket.OnMessage event #####
`WebSocket.OnMessage` event is emitted each time WebSocket data frame is received.
@ -48,7 +50,7 @@ So `e` has come across as `EventArgs.Empty`, there is no operation on `e`.
...
};
So **type** of received WebSocket data frame is stored in `e.Type` (`WebSocketSharp.MessageEventArgs.Type`, its type is `WebSocketSharp.Frame.Opcode`), you check it out and you determine which item you should operate.
**type** of received WebSocket data frame is stored in `e.Type` (`WebSocketSharp.MessageEventArgs.Type`, its type is `WebSocketSharp.Frame.Opcode`), so you check it out and you determine which item you should operate.
switch (e.Type)
{
@ -66,7 +68,7 @@ If `e.Type` is `Opcode.TEXT`, you operate `e.Data` (`WebSocketSharp.MessageEvent
If `e.Type` is `Opcode.BINARY`, you operate `e.RawData` (`WebSocketSharp.MessageEventArgs.RawData`, its type is `byte[]`).
#### WebSocket.OnError event ####
##### WebSocket.OnError event #####
`WebSocket.OnError` event is emitted when some error is occurred.
@ -75,9 +77,9 @@ If `e.Type` is `Opcode.BINARY`, you operate `e.RawData` (`WebSocketSharp.Message
...
};
So error message is stored in `e.Data` (`WebSocketSharp.MessageEventArgs.Data`, its type is `string`), you operate it.
Error message is stored in `e.Message` (`WebSocketSharp.ErrorEventArgs.Message`, its type is `string`), so you operate it.
#### WebSocket.OnClose event ####
##### WebSocket.OnClose event #####
`WebSocket.OnClose` event is emitted when WebSocket connection is closed.
@ -86,15 +88,15 @@ So error message is stored in `e.Data` (`WebSocketSharp.MessageEventArgs.Data`,
...
};
So close status code is stored in `e.Code` (`WebSocketSharp.CloseEventArgs.Code`, its type is `WebSocketSharp.Frame.CloseStatusCode`) and reason of close is stored in `e.Reason` (`WebSocketSharp.CloseEventArgs.Reason`, its type is `string`), you operate them.
Close status code is stored in `e.Code` (`WebSocketSharp.CloseEventArgs.Code`, its type is `WebSocketSharp.Frame.CloseStatusCode`) and reason of close is stored in `e.Reason` (`WebSocketSharp.CloseEventArgs.Reason`, its type is `string`), so you operate them.
### Step 4 ###
#### Step 4 ####
Connecting to server using WebSocket.
ws.Connect();
### Step 5 ###
#### Step 5 ####
Sending data.
@ -104,7 +106,7 @@ Sending data.
`data` types are `string`, `byte[]` and `FileInfo` class.
### Step 6 ###
#### Step 6 ####
Closing WebSocket connection.
@ -116,6 +118,96 @@ Type of `code` is `WebSocketSharp.Frame.CloseStatusCode`, type of `reason` is `s
`WebSocket.Close` method is overloaded (In addition `Close()` and `Close(code)` exist).
### WebSocket Server ###
#### Step 1 ####
Required namespaces.
using WebSocketSharp;
using WebSocketSharp.Frame;
Same as **WebSocket Client**.
#### Step 2 ####
Creating instance of `WebSocketServer` class.
WebSocketServer wssv = new WebSocketServer("ws://example.com:4649");
If you set WebSocket url without port number, `WebSocketServer` set 80 or 443 to port number automatically.
So it is necessary to run with root permission.
$ sudo mono example2.exe
#### Step 3 ####
Setting WebSocketServer event handlers.
##### WebSocketServer.OnConnection event #####
`WebSocketServer.OnConnection` event is emitted each time client makes a connection request.
wssv.OnConnection += (sender, e) =>
{
...
};
`WebSocket` to communicate with client is stored in `e.Socket` (`WebSocketSharp.ConnectionEventArgs.Socket`, its type is `WebSocketSharp.WebSocket`), so you operate it.
WebSocket ws = e.Socket;
ws.OnMessage += (sender_, e_) =>
{
...
};
Same settings of `WebSocket` event handlers as **WebSocket Client**.
This WebSocket is server-side, so data is sent from server to client.
If you want to function as echo server that returns a data to client as it is received,
// Echo
ws.Send(e_.Data);
If you want to function as chat server that returns a data to all clients,
// Chat
wssv.Send(e_.Data);
##### WebSocketServer.OnError event #####
`WebSocketServer.OnError` event is emitted when some error is occurred.
wssv.OnError += (sender, e) =>
{
...
};
Error message is stored in `e.Message` (`WebSocketSharp.ErrorEventArgs.Message`, its type is `string`), so you operate it.
#### Step 4 ####
Starting server.
wssv.Start();
#### Step 5 ####
Sending data to all clients.
wssv.Send(data);
`WebSocketServer.Send` method is overloaded.
`data` types are `string` and `byte[]`.
#### Step 6 ####
Stopping server.
wssv.Stop();
## Examples ##
Examples of using **websocket-sharp**.
@ -130,6 +222,10 @@ Examples of using **websocket-sharp**.
[Example1] uses [Json.NET].
### Example2 ###
[Example2] starts WebSocket server.
## Supported WebSocket Protocol ##
**websocket-sharp** supports **[RFC 6455]**.
@ -160,6 +256,7 @@ Licensed under the **[MIT License]**.
[Echo server]: http://www.websocket.org/echo.html
[Example]: https://github.com/sta/websocket-sharp/tree/master/Example
[Example1]: https://github.com/sta/websocket-sharp/tree/master/Example1
[Example2]: https://github.com/sta/websocket-sharp/tree/master/Example2
[hixie-75]: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75
[hybi-00]: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00
[Json.NET]: http://james.newtonking.com/projects/json-net.aspx

View File

@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example", "Example\Example.
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example1", "Example1\Example1.csproj", "{390E2568-57B7-4D17-91E5-C29336368CCF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example2", "Example2\Example2.csproj", "{B81A24C8-25BB-42B2-AF99-1E1EACCE74C7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -39,6 +41,14 @@ Global
{B357BAC7-529E-4D81-A0D2-71041B19C8DE}.Release_Ubuntu|Any CPU.Build.0 = Release_Ubuntu|Any CPU
{B357BAC7-529E-4D81-A0D2-71041B19C8DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B357BAC7-529E-4D81-A0D2-71041B19C8DE}.Release|Any CPU.Build.0 = Release|Any CPU
{B81A24C8-25BB-42B2-AF99-1E1EACCE74C7}.Debug_Ubuntu|Any CPU.ActiveCfg = Debug_Ubuntu|Any CPU
{B81A24C8-25BB-42B2-AF99-1E1EACCE74C7}.Debug_Ubuntu|Any CPU.Build.0 = Debug_Ubuntu|Any CPU
{B81A24C8-25BB-42B2-AF99-1E1EACCE74C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B81A24C8-25BB-42B2-AF99-1E1EACCE74C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B81A24C8-25BB-42B2-AF99-1E1EACCE74C7}.Release_Ubuntu|Any CPU.ActiveCfg = Release_Ubuntu|Any CPU
{B81A24C8-25BB-42B2-AF99-1E1EACCE74C7}.Release_Ubuntu|Any CPU.Build.0 = Release_Ubuntu|Any CPU
{B81A24C8-25BB-42B2-AF99-1E1EACCE74C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B81A24C8-25BB-42B2-AF99-1E1EACCE74C7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
StartupItem = websocket-sharp\websocket-sharp.csproj

View File

@ -2,7 +2,7 @@
<MonoDevelop.Ide.Workspace ActiveConfiguration="Debug" />
<MonoDevelop.Ide.Workbench ActiveDocument="websocket-sharp/WebSocket.cs">
<Files>
<File FileName="websocket-sharp/WebSocket.cs" Line="220" Column="8" />
<File FileName="websocket-sharp/WebSocket.cs" Line="476" Column="38" />
</Files>
</MonoDevelop.Ide.Workbench>
<MonoDevelop.Ide.DebuggingService.Breakpoints>

View File

@ -0,0 +1,42 @@
#region MIT License
/**
* ConnectionEventArgs.cs
*
* The MIT License
*
* Copyright (c) 2012 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
using System;
namespace WebSocketSharp
{
public class ConnectionEventArgs : EventArgs
{
public WebSocket Socket { get; private set; }
public ConnectionEventArgs(WebSocket webSocket)
{
Socket = webSocket;
}
}
}

View File

@ -0,0 +1,42 @@
#region MIT License
/**
* ErrorEventArgs.cs
*
* The MIT License
*
* Copyright (c) 2012 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
using System;
namespace WebSocketSharp
{
public class ErrorEventArgs : EventArgs
{
public string Message { get; private set; }
public ErrorEventArgs(string message)
{
Message = message;
}
}
}

View File

@ -32,6 +32,7 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Diagnostics;
using System.IO;
using System.Linq;
@ -60,12 +61,14 @@ namespace WebSocketSharp
#region Private Fields
private AutoResetEvent _autoEvent;
private string _base64key;
private string _binaryType;
private string _extensions;
private Object _forClose;
private Object _forSend;
private int _fragmentLen;
private bool _isClient;
private Thread _msgThread;
private NetworkStream _netStream;
private string _protocol;
@ -125,7 +128,7 @@ namespace WebSocketSharp
switch (value)
{
case WsState.OPEN:
messageThreadStart();
startMessageThread();
OnOpen.Emit(this, EventArgs.Empty);
break;
case WsState.CLOSING:
@ -153,7 +156,7 @@ namespace WebSocketSharp
public event EventHandler OnOpen;
public event EventHandler<MessageEventArgs> OnMessage;
public event EventHandler<MessageEventArgs> OnError;
public event EventHandler<ErrorEventArgs> OnError;
public event EventHandler<CloseEventArgs> OnClose;
#endregion
@ -174,27 +177,43 @@ namespace WebSocketSharp
#endregion
#region Internal Constructors
internal WebSocket(string url, TcpClient tcpClient)
: this()
{
_uri = new Uri(url);
if (!isValidScheme(_uri))
{
throw new ArgumentException("Unsupported WebSocket URI scheme: " + _uri.Scheme);
}
_tcpClient = tcpClient;
_isClient = false;
}
#endregion
#region Public Constructors
public WebSocket(string url, params string[] protocols)
: this()
: this()
{
_uri = new Uri(url);
string scheme = _uri.Scheme;
if (scheme != "ws" && scheme != "wss")
_uri = new Uri(url);
if (!isValidScheme(_uri))
{
throw new ArgumentException("Unsupported WebSocket URI scheme: " + scheme);
throw new ArgumentException("Unsupported WebSocket URI scheme: " + _uri.Scheme);
}
_protocols = protocols.ToString(", ");
_protocols = protocols.ToString(", ");
_isClient = true;
}
public WebSocket(
string url,
EventHandler onOpen,
EventHandler<MessageEventArgs> onMessage,
EventHandler<MessageEventArgs> onError,
EventHandler<ErrorEventArgs> onError,
EventHandler<CloseEventArgs> onClose,
params string[] protocols)
: this(url, protocols)
@ -211,10 +230,37 @@ namespace WebSocketSharp
#region Private Methods
private void acceptHandshake()
{
string msg, response;
string[] request;
request = receiveOpeningHandshake();
#if DEBUG
Console.WriteLine("\nWS: Info@acceptHandshake: Opening handshake from client:\n");
foreach (string s in request)
{
Console.WriteLine("{0}", s);
}
#endif
if (!isValidRequest(request, out msg))
{
throw new InvalidOperationException(msg);
}
response = createResponseHandshake();
#if DEBUG
Console.WriteLine("\nWS: Info@acceptHandshake: Opening handshake from server:\n{0}", response);
#endif
sendResponseHandshake(response);
ReadyState = WsState.OPEN;
}
private void close(PayloadData data)
{
#if DEBUG
Console.WriteLine("WS: Info@close: Current thread IsBackground?: {0}", Thread.CurrentThread.IsBackground);
Console.WriteLine("\nWS: Info@close: Current thread IsBackground?: {0}", Thread.CurrentThread.IsBackground);
#endif
lock(_forClose)
{
@ -291,13 +337,20 @@ namespace WebSocketSharp
if (!Thread.CurrentThread.IsBackground)
{
_msgThread.Join(5000);
if (_isClient)
{
_msgThread.Join(5000);
}
else
{
_autoEvent.WaitOne();
}
}
ReadyState = WsState.CLOSED;
}
private void createConnection()
private void createClientStream()
{
string scheme = _uri.Scheme;
string host = _uri.DnsSafeHost;
@ -400,6 +453,45 @@ namespace WebSocketSharp
crlf;
}
private string createResponseHandshake()
{
string crlf = "\r\n";
string resStatus = "HTTP/1.1 101 Switching Protocols" + crlf;
string resUpgrade = "Upgrade: websocket" + crlf;
string resConnection = "Connection: Upgrade" + crlf;
string secWsAccept = String.Format("Sec-WebSocket-Accept: {0}{1}", createExpectedKey(), crlf);
//string secWsProtocol = "Sec-WebSocket-Protocol: chat" + crlf;
string secWsVersion = String.Format("Sec-WebSocket-Version: {0}{1}", _version, crlf);
return resStatus +
resUpgrade +
resConnection +
secWsAccept +
//secWsProtocol +
secWsVersion +
crlf;
}
private void createServerStream()
{
_netStream = _tcpClient.GetStream();
if (_uri.Scheme == "wss")
{
_sslStream = new SslStream(_netStream);
string certPath = ConfigurationManager.AppSettings["ServerCertPath"];
_sslStream.AuthenticateAsServer(new X509Certificate(certPath));
_wsStream = new WsStream<SslStream>(_sslStream);
}
else
{
_wsStream = new WsStream<NetworkStream>(_netStream);
}
}
private void doHandshake()
{
string msg, request;
@ -407,11 +499,11 @@ namespace WebSocketSharp
request = createOpeningHandshake();
#if DEBUG
Console.WriteLine("WS: Info@doHandshake: Opening handshake from client:\n{0}", request);
Console.WriteLine("\nWS: Info@doHandshake: Opening handshake from client:\n{0}", request);
#endif
response = sendOpeningHandshake(request);
#if DEBUG
Console.WriteLine("WS: Info@doHandshake: Opening handshake from server:");
Console.WriteLine("\nWS: Info@doHandshake: Opening handshake from server:\n");
foreach (string s in response)
{
Console.WriteLine("{0}", s);
@ -432,18 +524,123 @@ namespace WebSocketSharp
var caller = callerFrame.GetMethod();
Console.WriteLine("WS: Error@{0}: {1}", caller.Name, message);
#endif
OnError.Emit(this, new MessageEventArgs(message));
OnError.Emit(this, new ErrorEventArgs(message));
}
private bool isValidRequest(string[] request, out string message)
{
string reqConnection, reqHost, reqUpgrade, secWsVersion;
string[] reqRequest;
List<string> extensionList = new List<string>();
Func<string, Func<string, string, string>> func = s =>
{
return (e, a) =>
{
return String.Format("Invalid request {0} value: {1}(expected: {2})", s, a, e);
};
};
string expectedHost = _uri.DnsSafeHost;
int port = ((IPEndPoint)_tcpClient.Client.LocalEndPoint).Port;
if (port != 80)
{
expectedHost += ":" + port;
}
reqRequest = request[0].Split(' ');
if ("GET".NotEqualsDo(reqRequest[0], func("HTTP Method"), out message, false))
{
return false;
}
if ("HTTP/1.1".NotEqualsDo(reqRequest[2], func("HTTP Version"), out message, false))
{
return false;
}
for (int i = 1; i < request.Length; i++)
{
if (request[i].Contains("Connection:"))
{
reqConnection = request[i].GetHeaderValue(":");
if ("Upgrade".NotEqualsDo(reqConnection, func("Connection"), out message, true))
{
return false;
}
}
else if (request[i].Contains("Host:"))
{
reqHost = request[i].GetHeaderValue(":");
if (expectedHost.NotEqualsDo(reqHost, func("Host"), out message, true))
{
return false;
}
}
else if (request[i].Contains("Origin:"))
{
continue;
}
else if (request[i].Contains("Upgrade:"))
{
reqUpgrade = request[i].GetHeaderValue(":");
if ("websocket".NotEqualsDo(reqUpgrade, func("Upgrade"), out message, true))
{
return false;
}
}
else if (request[i].Contains("Sec-WebSocket-Extensions:"))
{
extensionList.Add(request[i].GetHeaderValue(":"));
}
else if (request[i].Contains("Sec-WebSocket-Key:"))
{
_base64key = request[i].GetHeaderValue(":");
}
else if (request[i].Contains("Sec-WebSocket-Protocol:"))
{
_protocols = request[i].GetHeaderValue(":");
#if DEBUG
Console.WriteLine("WS: Info@isValidRequest: Sub protocol: {0}", _protocols);
#endif
}
else if (request[i].Contains("Sec-WebSocket-Version:"))
{
secWsVersion = request[i].GetHeaderValue(":");
if (_version.NotEqualsDo(secWsVersion, func("Sec-WebSocket-Version"), out message, true))
{
return false;
}
}
else
{
Console.WriteLine("WS: Info@isValidRequest: Unsupported request header line: {0}", request[i]);
}
}
if (String.IsNullOrEmpty(_base64key))
{
message = "Sec-WebSocket-Key header field does not exist or the value isn't set.";
return false;
}
#if DEBUG
foreach (string s in extensionList)
{
Console.WriteLine("WS: Info@isValidRequest: Extensions: {0}", s);
}
#endif
message = String.Empty;
return true;
}
private bool isValidResponse(string[] response, out string message)
{
Func<string, Func<string, string, string>> func;
string resUpgrade, resConnection;
string secWsAccept, secWsVersion;
string resUpgrade, resConnection, secWsAccept, secWsVersion;
string[] resStatus;
List<string> extensionList = new List<string>();
func = s =>
Func<string, Func<string, string, string>> func = s =>
{
return (e, a) =>
{
@ -501,9 +698,10 @@ namespace WebSocketSharp
else if (response[i].Contains("Sec-WebSocket-Version:"))
{
secWsVersion = response[i].GetHeaderValue(":");
#if DEBUG
Console.WriteLine("WS: Info@isValidResponse: Version: {0}", secWsVersion);
#endif
if (_version.NotEqualsDo(secWsVersion, func("Sec-WebSocket-Version"), out message, true))
{
return false;
}
}
else
{
@ -520,38 +718,81 @@ namespace WebSocketSharp
return true;
}
private bool isValidScheme(Uri uri)
{
string scheme = uri.Scheme;
if (scheme == "ws" || scheme == "wss")
{
return true;
}
return false;
}
private void message()
{
#if DEBUG
Console.WriteLine("WS: Info@message: Current thread IsBackground?: {0}", Thread.CurrentThread.IsBackground);
#endif
MessageEventArgs eventArgs;
while (_readyState == WsState.OPEN)
try
{
try
{
eventArgs = receive();
MessageEventArgs eventArgs = receive();
if (eventArgs != null)
{
OnMessage.Emit(this, eventArgs);
}
}
catch (WsReceivedTooBigMessageException ex)
if (eventArgs != null)
{
close(CloseStatusCode.TOO_BIG, ex.Message);
OnMessage.Emit(this, eventArgs);
}
}
catch (WsReceivedTooBigMessageException ex)
{
close(CloseStatusCode.TOO_BIG, ex.Message);
}
}
private void messageLoop()
{
#if DEBUG
Console.WriteLine("WS: Info@message: Exit message method.");
Console.WriteLine("\nWS: Info@messageLoop: Current thread IsBackground?: {0}", Thread.CurrentThread.IsBackground);
#endif
while (_readyState == WsState.OPEN)
{
message();
}
#if DEBUG
Console.WriteLine("WS: Info@messageLoop: Exit messageLoop method.");
#endif
}
private void messageThreadStart()
private void startMessageThread()
{
_msgThread = new Thread(new ThreadStart(message));
_msgThread.IsBackground = true;
_msgThread.Start();
if (_isClient)
{
_msgThread = new Thread(new ThreadStart(messageLoop));
_msgThread.IsBackground = true;
_msgThread.Start();
}
else
{
_autoEvent = new AutoResetEvent(false);
Action act = () =>
{
if (_readyState == WsState.OPEN)
{
message();
}
};
AsyncCallback callback = (ar) =>
{
act.EndInvoke(ar);
if (_readyState == WsState.OPEN)
{
act.BeginInvoke(callback, null);
}
else
{
_autoEvent.Set();
}
};
act.BeginInvoke(callback, null);
}
}
private MessageEventArgs receive()
@ -671,6 +912,26 @@ namespace WebSocketSharp
pong(payloadData);
}
private string[] receiveOpeningHandshake()
{
var readData = new List<byte>();
while (true)
{
if (_wsStream.ReadByte().EqualsAndSaveTo('\r', readData) &&
_wsStream.ReadByte().EqualsAndSaveTo('\n', readData) &&
_wsStream.ReadByte().EqualsAndSaveTo('\r', readData) &&
_wsStream.ReadByte().EqualsAndSaveTo('\n', readData))
{
break;
}
}
return Encoding.UTF8.GetString(readData.ToArray())
.Replace("\r\n", "\n").Replace("\n\n", "\n").TrimEnd('\n')
.Split('\n');
}
private bool send(WsFrame frame)
{
if (_readyState != WsState.OPEN)
@ -832,6 +1093,12 @@ namespace WebSocketSharp
.Split('\n');
}
private void sendResponseHandshake(string value)
{
var buffer = Encoding.UTF8.GetBytes(value);
_wsStream.Write(buffer, 0, buffer.Length);
}
#endregion
#region Public Methods
@ -840,14 +1107,22 @@ namespace WebSocketSharp
{
if (_readyState == WsState.OPEN)
{
Console.WriteLine("WS: Info@Connect: Connection is already established.");
Console.WriteLine("\nWS: Info@Connect: Connection is already established.");
return;
}
try
{
createConnection();
doHandshake();
{
if (_isClient)
{
createClientStream();
doHandshake();
}
else
{
createServerStream();
acceptHandshake();
}
}
catch (Exception ex)
{

View File

@ -0,0 +1,237 @@
#region MIT License
/**
* WebSocketServer.cs
*
* A C# implementation of a WebSocket protocol server.
*
* The MIT License
*
* Copyright (c) 2012 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using WebSocketSharp.Frame;
namespace WebSocketSharp
{
public class WebSocketServer
{
#region Private Fields
private TcpListener _tcpListener;
private Uri _uri;
private SynchronizedCollection<WebSocket> _webSockets;
#endregion
#region Properties
public IPAddress Address
{
get { return Endpoint.Address; }
}
public IPEndPoint Endpoint
{
get { return (IPEndPoint)_tcpListener.LocalEndpoint; }
}
public int Port
{
get { return Endpoint.Port; }
}
public string Url
{
get { return _uri.ToString(); }
}
#endregion
#region Events
public event EventHandler<ConnectionEventArgs> OnConnection;
public event EventHandler<ErrorEventArgs> OnError;
#endregion
#region Public Constructor
public WebSocketServer(string url)
{
_uri = new Uri(url);
if (!isValidScheme(_uri))
{
throw new ArgumentException("Unsupported WebSocket URI scheme: " + _uri.Scheme);
}
string scheme = _uri.Scheme;
int port = _uri.Port;
if (port <= 0)
{
if (scheme == "wss")
{
port = 443;
}
else
{
port = 80;
}
}
_tcpListener = new TcpListener(IPAddress.Any, port);
_webSockets = new SynchronizedCollection<WebSocket>();
}
#endregion
#region Private Methods
private void acceptClient(IAsyncResult ar)
{
TcpListener listener = (TcpListener)ar.AsyncState;
if (listener.Server == null || !listener.Server.IsBound)
{
return;
}
try
{
TcpClient client = listener.EndAcceptTcpClient(ar);
WebSocket ws = new WebSocket(_uri.ToString(), client);
OnConnection.Emit(this, new ConnectionEventArgs(ws));
_webSockets.Add(ws);
ws.Connect();
}
catch (ObjectDisposedException)
{
// TcpListener has been stopped.
return;
}
catch (Exception ex)
{
error(ex.Message);
}
listener.BeginAcceptTcpClient(acceptClient, listener);
}
private void error(string message)
{
#if DEBUG
var callerFrame = new StackFrame(1);
var caller = callerFrame.GetMethod();
Console.WriteLine("WSSV: Error@{0}: {1}", caller.Name, message);
#endif
OnError.Emit(this, new ErrorEventArgs(message));
}
private bool isValidScheme(Uri uri)
{
string scheme = uri.Scheme;
if (scheme == "ws" || scheme == "wss")
{
return true;
}
return false;
}
#endregion
#region Public Methods
public void Close(CloseStatusCode code, string reason)
{
lock (_webSockets.SyncRoot)
{
foreach (WebSocket ws in _webSockets)
{
if (ws.ReadyState == WsState.OPEN)
{
ws.Close(code, reason);
}
}
}
}
public void Send(byte[] data)
{
WaitCallback broadcast = (state) =>
{
lock (_webSockets.SyncRoot)
{
foreach (WebSocket ws in _webSockets)
{
if (ws.ReadyState == WsState.OPEN)
{
ws.Send(data);
}
}
}
};
ThreadPool.QueueUserWorkItem(broadcast);
}
public void Send(string data)
{
WaitCallback broadcast = (state) =>
{
lock (_webSockets.SyncRoot)
{
foreach (WebSocket ws in _webSockets)
{
if (ws.ReadyState == WsState.OPEN)
{
ws.Send(data);
}
}
}
};
ThreadPool.QueueUserWorkItem(broadcast);
}
public void Start()
{
_tcpListener.Start();
_tcpListener.BeginAcceptTcpClient(acceptClient, _tcpListener);
}
public void Stop()
{
_tcpListener.Stop();
Close(CloseStatusCode.NORMAL, String.Empty);
}
#endregion
}
}

View File

@ -53,11 +53,11 @@
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.ServiceModel" />
<Reference Include="System.Configuration" />
</ItemGroup>
<ItemGroup>
<Compile Include="AssemblyInfo.cs" />
<Compile Include="Ext.cs" />
<Compile Include="WebSocket.cs" />
<Compile Include="WsState.cs" />
<Compile Include="MessageEventArgs.cs" />
<Compile Include="CloseEventArgs.cs" />
@ -72,6 +72,10 @@
<Compile Include="Frame\Opcode.cs" />
<Compile Include="Frame\PayloadData.cs" />
<Compile Include="Frame\Rsv.cs" />
<Compile Include="ConnectionEventArgs.cs" />
<Compile Include="ErrorEventArgs.cs" />
<Compile Include="WebSocketServer.cs" />
<Compile Include="WebSocket.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>

Binary file not shown.