Refactored WebSocketServiceManager.cs

This commit is contained in:
sta 2014-09-18 20:52:38 +09:00
parent 0d5d6d2b4c
commit e84caebfa8
4 changed files with 116 additions and 124 deletions

View File

@ -491,7 +491,7 @@ namespace WebSocketSharp.Server
private void processWebSocketRequest (HttpListenerWebSocketContext context) private void processWebSocketRequest (HttpListenerWebSocketContext context)
{ {
WebSocketServiceHost host; WebSocketServiceHost host;
if (!_services.TryGetServiceHostInternally (context.RequestUri.AbsolutePath, out host)) { if (!_services.InternalTryGetServiceHost (context.RequestUri.AbsolutePath, out host)) {
context.Close (HttpStatusCode.NotImplemented); context.Close (HttpStatusCode.NotImplemented);
return; return;
} }
@ -615,11 +615,7 @@ namespace WebSocketSharp.Server
return; return;
} }
var host = new WebSocketServiceHost<TBehavior> (path, initializer, _logger); _services.Add<TBehavior> (path, initializer);
if (!KeepClean)
host.KeepClean = false;
_services.Add (host.Path, host);
} }
/// <summary> /// <summary>

View File

@ -594,7 +594,7 @@ namespace WebSocketSharp.Server
} }
WebSocketServiceHost host; WebSocketServiceHost host;
if (!_services.TryGetServiceHostInternally (uri.AbsolutePath, out host)) { if (!_services.InternalTryGetServiceHost (uri.AbsolutePath, out host)) {
context.Close (HttpStatusCode.NotImplemented); context.Close (HttpStatusCode.NotImplemented);
return; return;
} }
@ -732,11 +732,7 @@ namespace WebSocketSharp.Server
return; return;
} }
var host = new WebSocketServiceHost<TBehavior> (path, initializer, _logger); _services.Add<TBehavior> (path, initializer);
if (!KeepClean)
host.KeepClean = false;
_services.Add (host.Path, host);
} }
/// <summary> /// <summary>

View File

@ -134,7 +134,7 @@ namespace WebSocketSharp.Server
internal WebSocketServiceHost (string path, Func<TBehavior> initializer, Logger logger) internal WebSocketServiceHost (string path, Func<TBehavior> initializer, Logger logger)
{ {
_path = HttpUtility.UrlDecode (path).TrimEndSlash (); _path = path;
_initializer = initializer; _initializer = initializer;
_sessions = new WebSocketSessionManager (logger); _sessions = new WebSocketSessionManager (logger);
} }

View File

@ -27,6 +27,7 @@
#endregion #endregion
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text; using System.Text;
@ -43,8 +44,8 @@ namespace WebSocketSharp.Server
{ {
#region Private Fields #region Private Fields
private volatile bool _clean;
private Dictionary<string, WebSocketServiceHost> _hosts; private Dictionary<string, WebSocketServiceHost> _hosts;
private volatile bool _keepClean;
private Logger _logger; private Logger _logger;
private volatile ServerState _state; private volatile ServerState _state;
private object _sync; private object _sync;
@ -62,10 +63,10 @@ namespace WebSocketSharp.Server
{ {
_logger = logger; _logger = logger;
_clean = true;
_hosts = new Dictionary<string, WebSocketServiceHost> (); _hosts = new Dictionary<string, WebSocketServiceHost> ();
_keepClean = true;
_state = ServerState.Ready; _state = ServerState.Ready;
_sync = new object (); _sync = ((ICollection) _hosts).SyncRoot;
} }
#endregion #endregion
@ -73,45 +74,43 @@ namespace WebSocketSharp.Server
#region Public Properties #region Public Properties
/// <summary> /// <summary>
/// Gets the number of the WebSocket services provided by the server. /// Gets the number of the WebSocket services.
/// </summary> /// </summary>
/// <value> /// <value>
/// An <see cref="int"/> that represents the number of the WebSocket services. /// An <see cref="int"/> that represents the number of the services.
/// </value> /// </value>
public int Count { public int Count {
get { get {
lock (_sync) { lock (_sync)
return _hosts.Count; return _hosts.Count;
}
} }
} }
/// <summary> /// <summary>
/// Gets the collection of every information in the Websocket services provided by the server. /// Gets the host instances for the Websocket services.
/// </summary> /// </summary>
/// <value> /// <value>
/// An IEnumerable&lt;WebSocketServiceHost&gt; that contains the collection of every /// An <c>IEnumerable&lt;WebSocketServiceHost&gt;</c> instance that provides an enumerator
/// information in the Websocket services. /// which supports the iteration over the collection of the host instances for the services.
/// </value> /// </value>
public IEnumerable<WebSocketServiceHost> Hosts { public IEnumerable<WebSocketServiceHost> Hosts {
get { get {
lock (_sync) { lock (_sync)
return _hosts.Values.ToList (); return _hosts.Values.ToList ();
}
} }
} }
/// <summary> /// <summary>
/// Gets the information in a WebSocket service with the specified <paramref name="path"/>. /// Gets the WebSocket service host with the specified <paramref name="path"/>.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="WebSocketServiceHost"/> instance that provides the access to the WebSocket /// A <see cref="WebSocketServiceHost"/> instance that provides the access to
/// service if it's successfully found; otherwise, <see langword="null"/>. /// the information for the service, or <see langword="null"/> if it's not found.
/// </value> /// </value>
/// <param name="path"> /// <param name="path">
/// A <see cref="string"/> that represents the absolute path to the WebSocket service to find. /// A <see cref="string"/> that represents the absolute path to the service to find.
/// </param> /// </param>
public WebSocketServiceHost this [string path] { public WebSocketServiceHost this[string path] {
get { get {
WebSocketServiceHost host; WebSocketServiceHost host;
TryGetServiceHost (path, out host); TryGetServiceHost (path, out host);
@ -121,24 +120,24 @@ namespace WebSocketSharp.Server
} }
/// <summary> /// <summary>
/// Gets a value indicating whether the manager cleans up the inactive sessions in the /// Gets a value indicating whether the manager cleans up the inactive sessions
/// WebSocket services provided by the server periodically. /// in the WebSocket services periodically.
/// </summary> /// </summary>
/// <value> /// <value>
/// <c>true</c> if the manager cleans up the inactive sessions every 60 seconds; otherwise, /// <c>true</c> if the manager cleans up the inactive sessions every 60 seconds;
/// <c>false</c>. /// otherwise, <c>false</c>.
/// </value> /// </value>
public bool KeepClean { public bool KeepClean {
get { get {
return _keepClean; return _clean;
} }
internal set { internal set {
lock (_sync) { lock (_sync) {
if (!(value ^ _keepClean)) if (!(value ^ _clean))
return; return;
_keepClean = value; _clean = value;
foreach (var host in _hosts.Values) foreach (var host in _hosts.Values)
host.KeepClean = value; host.KeepClean = value;
} }
@ -146,37 +145,36 @@ namespace WebSocketSharp.Server
} }
/// <summary> /// <summary>
/// Gets the collection of every path to the WebSocket services provided by the server. /// Gets the paths for the WebSocket services.
/// </summary> /// </summary>
/// <value> /// <value>
/// An IEnumerable&lt;string&gt; that contains the collection of every path to the WebSocket /// An <c>IEnumerable&lt;string&gt;</c> instance that provides an enumerator which supports
/// services. /// the iteration over the collection of the paths for the services.
/// </value> /// </value>
public IEnumerable<string> Paths { public IEnumerable<string> Paths {
get { get {
lock (_sync) { lock (_sync)
return _hosts.Keys.ToList (); return _hosts.Keys.ToList ();
}
} }
} }
/// <summary> /// <summary>
/// Gets the number of the sessions in the WebSocket services provided by the server. /// Gets the total number of the sessions in the WebSocket services.
/// </summary> /// </summary>
/// <value> /// <value>
/// An <see cref="int"/> that represents the number of the sessions. /// An <see cref="int"/> that represents the total number of the sessions in the services.
/// </value> /// </value>
public int SessionCount { public int SessionCount {
get { get {
var count = 0; var cnt = 0;
foreach (var host in Hosts) { foreach (var host in Hosts) {
if (_state != ServerState.Start) if (_state != ServerState.Start)
break; break;
count += host.Sessions.Count; cnt += host.Sessions.Count;
} }
return count; return cnt;
} }
} }
@ -184,9 +182,9 @@ namespace WebSocketSharp.Server
#region Private Methods #region Private Methods
private void broadcast (Opcode opcode, byte [] data, Action completed) private void broadcast (Opcode opcode, byte[] data, Action completed)
{ {
var cache = new Dictionary<CompressionMethod, byte []> (); var cache = new Dictionary<CompressionMethod, byte[]> ();
try { try {
foreach (var host in Hosts) { foreach (var host in Hosts) {
if (_state != ServerState.Start) if (_state != ServerState.Start)
@ -231,47 +229,52 @@ namespace WebSocketSharp.Server
} }
} }
private void broadcastAsync (Opcode opcode, byte [] data, Action completed) private void broadcastAsync (Opcode opcode, byte[] data, Action completed)
{ {
ThreadPool.QueueUserWorkItem ( ThreadPool.QueueUserWorkItem (state => broadcast (opcode, data, completed));
state => broadcast (opcode, data, completed));
} }
private void broadcastAsync (Opcode opcode, Stream stream, Action completed) private void broadcastAsync (Opcode opcode, Stream stream, Action completed)
{ {
ThreadPool.QueueUserWorkItem ( ThreadPool.QueueUserWorkItem (state => broadcast (opcode, stream, completed));
state => broadcast (opcode, stream, completed));
} }
private Dictionary<string, Dictionary<string, bool>> broadping ( private Dictionary<string, Dictionary<string, bool>> broadping (
byte [] frame, int millisecondsTimeout) byte[] frameAsBytes, int millisecondsTimeout)
{ {
var result = new Dictionary<string, Dictionary<string, bool>> (); var res = new Dictionary<string, Dictionary<string, bool>> ();
foreach (var host in Hosts) { foreach (var host in Hosts) {
if (_state != ServerState.Start) if (_state != ServerState.Start)
break; break;
result.Add (host.Path, host.Sessions.Broadping (frame, millisecondsTimeout)); res.Add (host.Path, host.Sessions.Broadping (frameAsBytes, millisecondsTimeout));
} }
return result; return res;
} }
#endregion #endregion
#region Internal Methods #region Internal Methods
internal void Add (string path, WebSocketServiceHost host) internal void Add<TBehavior> (string path, Func<TBehavior> initializer)
where TBehavior : WebSocketBehavior
{ {
lock (_sync) { lock (_sync) {
WebSocketServiceHost find; path = HttpUtility.UrlDecode (path).TrimEndSlash ();
if (_hosts.TryGetValue (path, out find)) {
WebSocketServiceHost host;
if (_hosts.TryGetValue (path, out host)) {
_logger.Error ( _logger.Error (
"A WebSocket service with the specified path already exists.\npath: " + path); "A WebSocket service with the specified path already exists.\npath: " + path);
return; return;
} }
host = new WebSocketServiceHost<TBehavior> (path, initializer, _logger);
if (!_clean)
host.KeepClean = false;
if (_state == ServerState.Start) if (_state == ServerState.Start)
host.Sessions.Start (); host.Sessions.Start ();
@ -279,14 +282,27 @@ namespace WebSocketSharp.Server
} }
} }
internal bool InternalTryGetServiceHost (string path, out WebSocketServiceHost host)
{
bool res;
lock (_sync) {
path = HttpUtility.UrlDecode (path).TrimEndSlash ();
res = _hosts.TryGetValue (path, out host);
}
if (!res)
_logger.Error ("A WebSocket service with the specified path isn't found.\npath: " + path);
return res;
}
internal bool Remove (string path) internal bool Remove (string path)
{ {
path = HttpUtility.UrlDecode (path).TrimEndSlash ();
WebSocketServiceHost host; WebSocketServiceHost host;
lock (_sync) { lock (_sync) {
path = HttpUtility.UrlDecode (path).TrimEndSlash ();
if (!_hosts.TryGetValue (path, out host)) { if (!_hosts.TryGetValue (path, out host)) {
_logger.Error ("A WebSocket service with the specified path not found.\npath: " + path); _logger.Error ("A WebSocket service with the specified path isn't found.\npath: " + path);
return false; return false;
} }
@ -310,51 +326,36 @@ namespace WebSocketSharp.Server
} }
} }
internal void Stop (byte [] data, bool send) internal void Stop (byte[] data, bool send)
{ {
lock (_sync) { lock (_sync) {
_state = ServerState.ShuttingDown; _state = ServerState.ShuttingDown;
var payload = new PayloadData (data); var payload = new PayloadData (data);
var args = new CloseEventArgs (payload); var args = new CloseEventArgs (payload);
var frameAsBytes = var bytes = send
send ? WebSocketFrame.CreateCloseFrame (Mask.Unmask, payload).ToByteArray () : null; ? WebSocketFrame.CreateCloseFrame (Mask.Unmask, payload).ToByteArray ()
: null;
foreach (var host in _hosts.Values) foreach (var host in _hosts.Values)
host.Sessions.Stop (args, frameAsBytes); host.Sessions.Stop (args, bytes);
_hosts.Clear (); _hosts.Clear ();
_state = ServerState.Stop; _state = ServerState.Stop;
} }
} }
internal bool TryGetServiceHostInternally (string path, out WebSocketServiceHost host)
{
path = HttpUtility.UrlDecode (path).TrimEndSlash ();
bool result;
lock (_sync) {
result = _hosts.TryGetValue (path, out host);
}
if (!result)
_logger.Error ("A WebSocket service with the specified path not found.\npath: " + path);
return result;
}
#endregion #endregion
#region Public Methods #region Public Methods
/// <summary> /// <summary>
/// Broadcasts a binary <paramref name="data"/> to every client in the WebSocket services /// Broadcasts a binary <paramref name="data"/> to every client in the WebSocket services.
/// provided by the server.
/// </summary> /// </summary>
/// <param name="data"> /// <param name="data">
/// An array of <see cref="byte"/> that represents the binary data to broadcast. /// An array of <see cref="byte"/> that represents the binary data to broadcast.
/// </param> /// </param>
public void Broadcast (byte [] data) public void Broadcast (byte[] data)
{ {
var msg = _state.CheckIfStart () ?? data.CheckIfValidSendData (); var msg = _state.CheckIfStart () ?? data.CheckIfValidSendData ();
if (msg != null) { if (msg != null) {
@ -369,8 +370,7 @@ namespace WebSocketSharp.Server
} }
/// <summary> /// <summary>
/// Broadcasts a text <paramref name="data"/> to every client in the WebSocket services /// Broadcasts a text <paramref name="data"/> to every client in the WebSocket services.
/// provided by the server.
/// </summary> /// </summary>
/// <param name="data"> /// <param name="data">
/// A <see cref="string"/> that represents the text data to broadcast. /// A <see cref="string"/> that represents the text data to broadcast.
@ -391,8 +391,8 @@ namespace WebSocketSharp.Server
} }
/// <summary> /// <summary>
/// Broadcasts a binary <paramref name="data"/> asynchronously to every client in the WebSocket /// Broadcasts a binary <paramref name="data"/> asynchronously to every client
/// services provided by the server. /// in the WebSocket services.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This method doesn't wait for the broadcast to be complete. /// This method doesn't wait for the broadcast to be complete.
@ -401,10 +401,10 @@ namespace WebSocketSharp.Server
/// An array of <see cref="byte"/> that represents the binary data to broadcast. /// An array of <see cref="byte"/> that represents the binary data to broadcast.
/// </param> /// </param>
/// <param name="completed"> /// <param name="completed">
/// A <see cref="Action"/> delegate that references the method(s) called when the broadcast is /// A <see cref="Action"/> delegate that references the method(s) called when
/// complete. /// the broadcast is complete.
/// </param> /// </param>
public void BroadcastAsync (byte [] data, Action completed) public void BroadcastAsync (byte[] data, Action completed)
{ {
var msg = _state.CheckIfStart () ?? data.CheckIfValidSendData (); var msg = _state.CheckIfStart () ?? data.CheckIfValidSendData ();
if (msg != null) { if (msg != null) {
@ -419,8 +419,8 @@ namespace WebSocketSharp.Server
} }
/// <summary> /// <summary>
/// Broadcasts a text <paramref name="data"/> asynchronously to every client in the WebSocket /// Broadcasts a text <paramref name="data"/> asynchronously to every client
/// services provided by the server. /// in the WebSocket services.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This method doesn't wait for the broadcast to be complete. /// This method doesn't wait for the broadcast to be complete.
@ -429,8 +429,8 @@ namespace WebSocketSharp.Server
/// A <see cref="string"/> that represents the text data to broadcast. /// A <see cref="string"/> that represents the text data to broadcast.
/// </param> /// </param>
/// <param name="completed"> /// <param name="completed">
/// A <see cref="Action"/> delegate that references the method(s) called when the broadcast is /// A <see cref="Action"/> delegate that references the method(s) called when
/// complete. /// the broadcast is complete.
/// </param> /// </param>
public void BroadcastAsync (string data, Action completed) public void BroadcastAsync (string data, Action completed)
{ {
@ -448,8 +448,8 @@ namespace WebSocketSharp.Server
} }
/// <summary> /// <summary>
/// Broadcasts a binary data from the specified <see cref="Stream"/> asynchronously to every /// Broadcasts a binary data from the specified <see cref="Stream"/> asynchronously
/// client in the WebSocket services provided by the server. /// to every client in the WebSocket services.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This method doesn't wait for the broadcast to be complete. /// This method doesn't wait for the broadcast to be complete.
@ -461,14 +461,14 @@ namespace WebSocketSharp.Server
/// An <see cref="int"/> that represents the number of bytes to broadcast. /// An <see cref="int"/> that represents the number of bytes to broadcast.
/// </param> /// </param>
/// <param name="completed"> /// <param name="completed">
/// A <see cref="Action"/> delegate that references the method(s) called when the broadcast is /// A <see cref="Action"/> delegate that references the method(s) called when
/// complete. /// the broadcast is complete.
/// </param> /// </param>
public void BroadcastAsync (Stream stream, int length, Action completed) public void BroadcastAsync (Stream stream, int length, Action completed)
{ {
var msg = _state.CheckIfStart () ?? var msg = _state.CheckIfStart () ??
stream.CheckIfCanRead () ?? stream.CheckIfCanRead () ??
(length < 1 ? "'length' must be greater than 0." : null); (length < 1 ? "'length' is less than 1." : null);
if (msg != null) { if (msg != null) {
_logger.Error (msg); _logger.Error (msg);
@ -480,14 +480,14 @@ namespace WebSocketSharp.Server
data => { data => {
var len = data.Length; var len = data.Length;
if (len == 0) { if (len == 0) {
_logger.Error ("A data cannot be read from 'stream'."); _logger.Error ("The data cannot be read from 'stream'.");
return; return;
} }
if (len < length) if (len < length)
_logger.Warn ( _logger.Warn (
String.Format ( String.Format (
"A data with 'length' cannot be read from 'stream'.\nexpected: {0} actual: {1}", "The data with 'length' cannot be read from 'stream'.\nexpected: {0} actual: {1}",
length, length,
len)); len));
@ -500,13 +500,13 @@ namespace WebSocketSharp.Server
} }
/// <summary> /// <summary>
/// Sends a Ping to every client in the WebSocket services provided by the server. /// Sends a Ping to every client in the WebSocket services.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// A Dictionary&lt;string, Dictionary&lt;string, bool&gt;&gt; that contains the collection of /// A <c>Dictionary&lt;string, Dictionary&lt;string, bool&gt;&gt;</c> that contains
/// pairs of service path and collection of pairs of session ID and value indicating whether /// a collection of pairs of a service path and a collection of pairs of a session ID
/// the manager received a Pong from every client in a time. If this method isn't available, /// and a value indicating whether the manager received a Pong from each client in a time,
/// returns <see langword="null"/>. /// or <see langword="null"/> if this method isn't available.
/// </returns> /// </returns>
public Dictionary<string, Dictionary<string, bool>> Broadping () public Dictionary<string, Dictionary<string, bool>> Broadping ()
{ {
@ -520,14 +520,15 @@ namespace WebSocketSharp.Server
} }
/// <summary> /// <summary>
/// Sends a Ping with the specified <paramref name="message"/> to every client in the WebSocket /// Sends a Ping with the specified <paramref name="message"/> to every client
/// services provided by the server. /// in the WebSocket services.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// A Dictionary&lt;string, Dictionary&lt;string, bool&gt;&gt; that contains the collection of /// A <c>Dictionary&lt;string, Dictionary&lt;string, bool&gt;&gt;</c> that contains
/// pairs of service path and collection of pairs of session ID and value indicating whether /// a collection of pairs of a service path and a collection of pairs of a session ID
/// the manager received a Pong from every client in a time. If this method isn't available or /// and a value indicating whether the manager received a Pong from each client in a time,
/// <paramref name="message"/> is invalid, returns <see langword="null"/>. /// or <see langword="null"/> if this method isn't available or <paramref name="message"/>
/// is invalid.
/// </returns> /// </returns>
/// <param name="message"> /// <param name="message">
/// A <see cref="string"/> that represents the message to send. /// A <see cref="string"/> that represents the message to send.
@ -537,7 +538,7 @@ namespace WebSocketSharp.Server
if (message == null || message.Length == 0) if (message == null || message.Length == 0)
return Broadping (); return Broadping ();
byte [] data = null; byte[] data = null;
var msg = _state.CheckIfStart () ?? var msg = _state.CheckIfStart () ??
(data = Encoding.UTF8.GetBytes (message)).CheckIfValidControlData ("message"); (data = Encoding.UTF8.GetBytes (message)).CheckIfValidControlData ("message");
@ -550,19 +551,18 @@ namespace WebSocketSharp.Server
} }
/// <summary> /// <summary>
/// Tries to get the information in a WebSocket service with the specified /// Tries to get the WebSocket service host with the specified <paramref name="path"/>.
/// <paramref name="path"/>.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// <c>true</c> if the WebSocket service is successfully found; otherwise, <c>false</c>. /// <c>true</c> if the service is successfully found; otherwise, <c>false</c>.
/// </returns> /// </returns>
/// <param name="path"> /// <param name="path">
/// A <see cref="string"/> that represents the absolute path to the WebSocket service to find. /// A <see cref="string"/> that represents the absolute path to the service to find.
/// </param> /// </param>
/// <param name="host"> /// <param name="host">
/// When this method returns, a <see cref="WebSocketServiceHost"/> instance that /// When this method returns, a <see cref="WebSocketServiceHost"/> instance that provides
/// provides the access to the WebSocket service if it's successfully found; /// the access to the information for the service, or <see langword="null"/> if it's not found.
/// otherwise, <see langword="null"/>. This parameter is passed uninitialized. /// This parameter is passed uninitialized.
/// </param> /// </param>
public bool TryGetServiceHost (string path, out WebSocketServiceHost host) public bool TryGetServiceHost (string path, out WebSocketServiceHost host)
{ {
@ -574,7 +574,7 @@ namespace WebSocketSharp.Server
return false; return false;
} }
return TryGetServiceHostInternally (path, out host); return InternalTryGetServiceHost (path, out host);
} }
#endregion #endregion