Modified closing and ping

This commit is contained in:
sta 2013-09-07 12:35:29 +09:00
parent b7313955c9
commit 80afaef7c9
7 changed files with 126 additions and 229 deletions

View File

@ -167,7 +167,7 @@ namespace WebSocketSharp
internal static string CheckIfValidCloseData (this byte [] data) internal static string CheckIfValidCloseData (this byte [] data)
{ {
return data.Length > 125 return data.Length > 125
? "The payload length of a Close frame must be 125 bytes or less." ? "'reason' length must be less."
: null; : null;
} }
@ -178,10 +178,10 @@ namespace WebSocketSharp
: null; : null;
} }
internal static string CheckIfValidPingMessage (this string message) internal static string CheckIfValidPingData (this byte [] data)
{ {
return message != null && message.Length > 0 && Encoding.UTF8.GetBytes (message).Length > 125 return data.Length > 125
? "The payload length of a Ping frame must be 125 bytes or less." ? "'message' length must be less."
: null; : null;
} }

View File

@ -89,17 +89,16 @@ namespace WebSocketSharp.Server
Dictionary<string, bool> Broadping (); Dictionary<string, bool> Broadping ();
/// <summary> /// <summary>
/// Sends Pings with the specified <paramref name="message"/> to all clients of /// Sends Pings with the specified <paramref name="data"/> to all clients of the WebSocket service host.
/// the WebSocket service host.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// A Dictionary&lt;string, bool&gt; that contains the collection of pairs of session ID and value /// A Dictionary&lt;string, bool&gt; that contains the collection of pairs of session ID and value
/// indicating whether the WebSocket service host received a Pong from each client in a time. /// indicating whether the WebSocket service host received a Pong from each client in a time.
/// </returns> /// </returns>
/// <param name="message"> /// <param name="data">
/// A <see cref="string"/> that contains a message to send. /// An array of <see cref="byte"/> that contains a message data to send.
/// </param> /// </param>
Dictionary<string, bool> Broadping (string message); Dictionary<string, bool> Broadping (byte [] data);
/// <summary> /// <summary>
/// Close the WebSocket session with the specified <paramref name="id"/>. /// Close the WebSocket session with the specified <paramref name="id"/>.

View File

@ -201,6 +201,11 @@ namespace WebSocketSharp.Server
IsBound = true; IsBound = true;
} }
internal bool Ping (byte [] data)
{
return _websocket.Ping (data);
}
internal void SendAsync (byte [] data, Action completed) internal void SendAsync (byte [] data, Action completed)
{ {
_websocket.SendAsync (data, completed); _websocket.SendAsync (data, completed);
@ -279,7 +284,7 @@ namespace WebSocketSharp.Server
protected virtual Dictionary<string, bool> Broadping () protected virtual Dictionary<string, bool> Broadping ()
{ {
return IsBound return IsBound
? _sessions.Broadping () ? _sessions.Broadping (new byte [] {})
: null; : null;
} }
@ -299,7 +304,11 @@ namespace WebSocketSharp.Server
if (!IsBound) if (!IsBound)
return null; return null;
var msg = message.CheckIfValidPingMessage (); if (message == null || message.Length == 0)
return _sessions.Broadping (new byte [] {});
var data = Encoding.UTF8.GetBytes (message);
var msg = data.CheckIfValidPingData ();
if (msg != null) if (msg != null)
{ {
Log.Error (msg); Log.Error (msg);
@ -308,7 +317,7 @@ namespace WebSocketSharp.Server
return null; return null;
} }
return _sessions.Broadping (message); return _sessions.Broadping (data);
} }
/// <summary> /// <summary>

View File

@ -360,7 +360,7 @@ namespace WebSocketSharp.Server
/// </returns> /// </returns>
public Dictionary<string, bool> Broadping () public Dictionary<string, bool> Broadping ()
{ {
return _sessions.Broadping (); return _sessions.Broadping (new byte [] {});
} }
/// <summary> /// <summary>
@ -375,14 +375,18 @@ namespace WebSocketSharp.Server
/// </param> /// </param>
public Dictionary<string, bool> Broadping (string message) public Dictionary<string, bool> Broadping (string message)
{ {
var msg = message.CheckIfValidPingMessage (); if (message == null || message.Length == 0)
return _sessions.Broadping (new byte [] {});
var data = Encoding.UTF8.GetBytes (message);
var msg = data.CheckIfValidPingData ();
if (msg != null) if (msg != null)
{ {
Log.Error (msg); Log.Error (msg);
return null; return null;
} }
return _sessions.Broadping (message); return _sessions.Broadping (data);
} }
/// <summary> /// <summary>
@ -635,133 +639,18 @@ namespace WebSocketSharp.Server
} }
/// <summary> /// <summary>
/// Sends Pings with the specified <paramref name="message"/> to all clients. /// Sends Pings with the specified <paramref name="data"/> to all clients.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// A Dictionary&lt;string, bool&gt; that contains the collection of pairs of session ID and value /// A Dictionary&lt;string, bool&gt; that contains the collection of pairs of session ID and value
/// indicating whether the service host received the Pong from each client in a time. /// indicating whether the WebSocket service host received a Pong from each client in a time.
/// </returns>
/// <param name="message">
/// A <see cref="string"/> that contains a message to send.
/// </param>
Dictionary<string, bool> IWebSocketServiceHost.Broadping (string message)
{
return _sessions.Broadping (message);
}
/// <summary>
/// Close the WebSocket session with the specified <paramref name="id"/>.
/// </summary>
/// <param name="id">
/// A <see cref="string"/> that contains a session ID to find.
/// </param>
void IWebSocketServiceHost.CloseSession (string id)
{
_sessions.StopServiceInstance (id);
}
/// <summary>
/// Close the WebSocket session with the specified <paramref name="code"/>, <paramref name="reason"/>
/// and <paramref name="id"/>.
/// </summary>
/// <param name="code">
/// A <see cref="ushort"/> that contains a status code indicating the reason for closure.
/// </param>
/// <param name="reason">
/// A <see cref="string"/> that contains the reason for closure.
/// </param>
/// <param name="id">
/// A <see cref="string"/> that contains a session ID to find.
/// </param>
void IWebSocketServiceHost.CloseSession (ushort code, string reason, string id)
{
_sessions.StopServiceInstance (code, reason, id);
}
/// <summary>
/// Close the WebSocket session with the specified <paramref name="code"/>, <paramref name="reason"/>
/// and <paramref name="id"/>.
/// </summary>
/// <param name="code">
/// A <see cref="CloseStatusCode"/> that contains a status code indicating the reason for closure.
/// </param>
/// <param name="reason">
/// A <see cref="string"/> that contains the reason for closure.
/// </param>
/// <param name="id">
/// A <see cref="string"/> that contains a session ID to find.
/// </param>
void IWebSocketServiceHost.CloseSession (CloseStatusCode code, string reason, string id)
{
_sessions.StopServiceInstance (code, reason, id);
}
/// <summary>
/// Sends a Ping to the client associated with the specified <paramref name="id"/>.
/// </summary>
/// <returns>
/// <c>true</c> if the WebSocket service host receives a Pong from the client in a time;
/// otherwise, <c>false</c>.
/// </returns>
/// <param name="id">
/// A <see cref="string"/> that contains a session ID that represents the destination for the Ping.
/// </param>
bool IWebSocketServiceHost.PingTo (string id)
{
return _sessions.PingTo (id);
}
/// <summary>
/// Sends a Ping with the specified <paramref name="message"/> to the client associated with
/// the specified <paramref name="id"/>.
/// </summary>
/// <returns>
/// <c>true</c> if the WebSocket service host receives a Pong from the client in a time;
/// otherwise, <c>false</c>.
/// </returns>
/// <param name="message">
/// A <see cref="string"/> that contains a message to send.
/// </param>
/// <param name="id">
/// A <see cref="string"/> that contains a session ID that represents the destination for the Ping.
/// </param>
bool IWebSocketServiceHost.PingTo (string message, string id)
{
return _sessions.PingTo (message, id);
}
/// <summary>
/// Sends a binary data to the client associated with the specified <paramref name="id"/>.
/// </summary>
/// <returns>
/// <c>true</c> if <paramref name="data"/> is successfully sent; otherwise, <c>false</c>.
/// </returns> /// </returns>
/// <param name="data"> /// <param name="data">
/// An array of <see cref="byte"/> that contains a binary data to send. /// An array of <see cref="byte"/> that contains a message data to send.
/// </param> /// </param>
/// <param name="id"> Dictionary<string, bool> IWebSocketServiceHost.Broadping (byte [] data)
/// A <see cref="string"/> that contains a session ID that represents the destination for the data.
/// </param>
bool IWebSocketServiceHost.SendTo (byte [] data, string id)
{ {
return _sessions.SendTo (data, id); return _sessions.Broadping (data);
}
/// <summary>
/// Sends a text data to the client associated with the specified <paramref name="id"/>.
/// </summary>
/// <returns>
/// <c>true</c> if <paramref name="data"/> is successfully sent; otherwise, <c>false</c>.
/// </returns>
/// <param name="data">
/// A <see cref="string"/> that contains a text data to send.
/// </param>
/// <param name="id">
/// A <see cref="string"/> that contains a session ID that represents the destination for the data.
/// </param>
bool IWebSocketServiceHost.SendTo (string data, string id)
{
return _sessions.SendTo (data, id);
} }
/// <summary> /// <summary>

View File

@ -176,6 +176,15 @@ namespace WebSocketSharp.Server
} }
} }
internal Dictionary<string, Dictionary<string, bool>> Broadping (byte [] data)
{
var result = new Dictionary<string, Dictionary<string, bool>> ();
foreach (var service in copy ())
result.Add (service.Key, service.Value.Broadping (data));
return result;
}
internal bool Remove (string servicePath) internal bool Remove (string servicePath)
{ {
servicePath = HttpUtility.UrlDecode (servicePath).TrimEndSlash (); servicePath = HttpUtility.UrlDecode (servicePath).TrimEndSlash ();
@ -348,11 +357,7 @@ namespace WebSocketSharp.Server
/// </returns> /// </returns>
public Dictionary<string, Dictionary<string, bool>> Broadping () public Dictionary<string, Dictionary<string, bool>> Broadping ()
{ {
var result = new Dictionary<string, Dictionary<string, bool>> (); return Broadping (new byte [] {});
foreach (var service in copy ())
result.Add (service.Key, service.Value.Broadping ());
return result;
} }
/// <summary> /// <summary>
@ -369,18 +374,18 @@ namespace WebSocketSharp.Server
/// </param> /// </param>
public Dictionary<string, Dictionary<string, bool>> Broadping (string message) public Dictionary<string, Dictionary<string, bool>> Broadping (string message)
{ {
var msg = message.CheckIfValidPingMessage (); if (message == null || message.Length == 0)
return Broadping (new byte [] {});
var data = Encoding.UTF8.GetBytes (message);
var msg = data.CheckIfValidPingData ();
if (msg != null) if (msg != null)
{ {
_logger.Error (msg); _logger.Error (msg);
return null; return null;
} }
var result = new Dictionary<string, Dictionary<string, bool>> (); return Broadping (data);
foreach (var service in copy ())
result.Add (service.Key, service.Value.Broadping (message));
return result;
} }
/// <summary> /// <summary>
@ -410,7 +415,7 @@ namespace WebSocketSharp.Server
return null; return null;
} }
return host.Broadping (); return host.Broadping (new byte [] {});
} }
/// <summary> /// <summary>
@ -430,7 +435,11 @@ namespace WebSocketSharp.Server
/// </param> /// </param>
public Dictionary<string, bool> BroadpingTo (string message, string servicePath) public Dictionary<string, bool> BroadpingTo (string message, string servicePath)
{ {
var msg = message.CheckIfValidPingMessage () ?? servicePath.CheckIfValidServicePath (); if (message == null || message.Length == 0)
return BroadpingTo (servicePath);
var data = Encoding.UTF8.GetBytes (message);
var msg = data.CheckIfValidPingData () ?? servicePath.CheckIfValidServicePath ();
if (msg != null) if (msg != null)
{ {
_logger.Error (msg); _logger.Error (msg);
@ -444,7 +453,7 @@ namespace WebSocketSharp.Server
return null; return null;
} }
return host.Broadping (message); return host.Broadping (data);
} }
/// <summary> /// <summary>
@ -459,7 +468,7 @@ namespace WebSocketSharp.Server
/// </param> /// </param>
public void CloseSession (string id, string servicePath) public void CloseSession (string id, string servicePath)
{ {
var msg = id.CheckIfValidSessionID () ?? servicePath.CheckIfValidServicePath (); var msg = servicePath.CheckIfValidServicePath ();
if (msg != null) if (msg != null)
{ {
_logger.Error (msg); _logger.Error (msg);
@ -494,7 +503,7 @@ namespace WebSocketSharp.Server
/// </param> /// </param>
public void CloseSession (ushort code, string reason, string id, string servicePath) public void CloseSession (ushort code, string reason, string id, string servicePath)
{ {
var msg = id.CheckIfValidSessionID () ?? servicePath.CheckIfValidServicePath (); var msg = servicePath.CheckIfValidServicePath ();
if (msg != null) if (msg != null)
{ {
_logger.Error (msg); _logger.Error (msg);
@ -529,7 +538,7 @@ namespace WebSocketSharp.Server
/// </param> /// </param>
public void CloseSession (CloseStatusCode code, string reason, string id, string servicePath) public void CloseSession (CloseStatusCode code, string reason, string id, string servicePath)
{ {
var msg = id.CheckIfValidSessionID () ?? servicePath.CheckIfValidServicePath (); var msg = servicePath.CheckIfValidServicePath ();
if (msg != null) if (msg != null)
{ {
_logger.Error (msg); _logger.Error (msg);
@ -591,7 +600,7 @@ namespace WebSocketSharp.Server
/// </param> /// </param>
public bool PingTo (string id, string servicePath) public bool PingTo (string id, string servicePath)
{ {
var msg = id.CheckIfValidSessionID () ?? servicePath.CheckIfValidServicePath (); var msg = servicePath.CheckIfValidServicePath ();
if (msg != null) if (msg != null)
{ {
_logger.Error (msg); _logger.Error (msg);
@ -627,9 +636,7 @@ namespace WebSocketSharp.Server
/// </param> /// </param>
public bool PingTo (string message, string id, string servicePath) public bool PingTo (string message, string id, string servicePath)
{ {
var msg = (message.CheckIfValidPingMessage () ?? id.CheckIfValidSessionID ()) ?? var msg = servicePath.CheckIfValidServicePath ();
servicePath.CheckIfValidServicePath ();
if (msg != null) if (msg != null)
{ {
_logger.Error (msg); _logger.Error (msg);
@ -664,9 +671,7 @@ namespace WebSocketSharp.Server
/// </param> /// </param>
public bool SendTo (byte [] data, string id, string servicePath) public bool SendTo (byte [] data, string id, string servicePath)
{ {
var msg = (data.CheckIfValidSendData () ?? id.CheckIfValidSessionID ()) ?? var msg = servicePath.CheckIfValidServicePath ();
servicePath.CheckIfValidServicePath ();
if (msg != null) if (msg != null)
{ {
_logger.Error (msg); _logger.Error (msg);
@ -701,9 +706,7 @@ namespace WebSocketSharp.Server
/// </param> /// </param>
public bool SendTo (string data, string id, string servicePath) public bool SendTo (string data, string id, string servicePath)
{ {
var msg = (data.CheckIfValidSendData () ?? id.CheckIfValidSessionID ()) ?? var msg = servicePath.CheckIfValidServicePath ();
servicePath.CheckIfValidServicePath ();
if (msg != null) if (msg != null)
{ {
_logger.Error (msg); _logger.Error (msg);

View File

@ -84,7 +84,7 @@ namespace WebSocketSharp.Server
/// </value> /// </value>
public IEnumerable<string> ActiveIDs { public IEnumerable<string> ActiveIDs {
get { get {
return from result in Broadping () return from result in Broadping (new byte [] {})
where result.Value where result.Value
select result.Key; select result.Key;
} }
@ -134,7 +134,7 @@ namespace WebSocketSharp.Server
/// </value> /// </value>
public IEnumerable<string> InactiveIDs { public IEnumerable<string> InactiveIDs {
get { get {
return from result in Broadping () return from result in Broadping (new byte [] {})
where !result.Value where !result.Value
select result.Key; select result.Key;
} }
@ -345,38 +345,21 @@ namespace WebSocketSharp.Server
} }
/// <summary> /// <summary>
/// Sends Pings to the clients of every <see cref="WebSocketService"/> instances managed by /// Sends Pings with the specified <paramref name="data"/> to the clients of every <see cref="WebSocketService"/>
/// the <see cref="WebSocketServiceManager"/>.
/// </summary>
/// <returns>
/// A Dictionary&lt;string, bool&gt; that contains the collection of pairs of ID and value indicating
/// whether each <see cref="WebSocketService"/> instance received a Pong from the client in a time.
/// </returns>
internal Dictionary<string, bool> Broadping ()
{
var result = new Dictionary<string, bool> ();
foreach (var session in copy ())
result.Add (session.Key, session.Value.Ping ());
return result;
}
/// <summary>
/// Sends Pings with the specified <paramref name="message"/> to the clients of every <see cref="WebSocketService"/>
/// instances managed by the <see cref="WebSocketServiceManager"/>. /// instances managed by the <see cref="WebSocketServiceManager"/>.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// A Dictionary&lt;string, bool&gt; that contains the collection of pairs of ID and value indicating /// A Dictionary&lt;string, bool&gt; that contains the collection of pairs of ID and value indicating
/// whether each <see cref="WebSocketService"/> instance received a Pong from the client in a time. /// whether each <see cref="WebSocketService"/> instance received a Pong from the client in a time.
/// </returns> /// </returns>
/// <param name="message"> /// <param name="data">
/// A <see cref="string"/> that contains a message to send. /// An array of <see cref="byte"/> that contains a message data to send.
/// </param> /// </param>
internal Dictionary<string, bool> Broadping (string message) internal Dictionary<string, bool> Broadping (byte [] data)
{ {
var result = new Dictionary<string, bool> (); var result = new Dictionary<string, bool> ();
foreach (var session in copy ()) foreach (var session in copy ())
result.Add (session.Key, session.Value.Ping (message)); result.Add (session.Key, session.Value.Ping (data));
return result; return result;
} }

View File

@ -320,7 +320,7 @@ namespace WebSocketSharp
public bool IsAlive { public bool IsAlive {
get { get {
return _readyState == WebSocketState.OPEN return _readyState == WebSocketState.OPEN
? ping (new byte [] {}) ? Ping (new byte [] {})
: false; : false;
} }
} }
@ -531,32 +531,21 @@ namespace WebSocketSharp
return send (createHandshakeResponse ()); return send (createHandshakeResponse ());
} }
private void close (CloseEventArgs eventArgs)
{
if (!Thread.CurrentThread.IsBackground && _exitReceiving != null)
if (!_exitReceiving.WaitOne (5 * 1000))
eventArgs.WasClean = false;
if (!closeResources ())
eventArgs.WasClean = false;
_readyState = WebSocketState.CLOSED;
OnClose.Emit (this, eventArgs);
}
private void close (PayloadData data) private void close (PayloadData data)
{ {
_logger.Debug ("Is this thread background?: " + Thread.CurrentThread.IsBackground); _logger.Debug ("Is this thread background?: " + Thread.CurrentThread.IsBackground);
CloseEventArgs args = null; CloseEventArgs args = null;
lock (_forClose) lock (_forClose)
{ {
if (_readyState == WebSocketState.CLOSING || _readyState == WebSocketState.CLOSED) if (_readyState == WebSocketState.CLOSING || _readyState == WebSocketState.CLOSED)
return; return;
var state = _readyState; var current = _readyState;
_readyState = WebSocketState.CLOSING; _readyState = WebSocketState.CLOSING;
args = new CloseEventArgs (data); args = new CloseEventArgs (data);
if (state == WebSocketState.CONNECTING) if (current == WebSocketState.CONNECTING)
{ {
if (!_client) if (!_client)
{ {
@ -571,7 +560,17 @@ namespace WebSocketSharp
} }
} }
close (args); if (!Thread.CurrentThread.IsBackground &&
_exitReceiving != null &&
!_exitReceiving.WaitOne (5 * 1000))
args.WasClean = false;
if (!closeResources ())
args.WasClean = false;
_readyState = WebSocketState.CLOSED;
OnClose.Emit (this, args);
_logger.Trace ("Exit close method."); _logger.Trace ("Exit close method.");
} }
@ -579,7 +578,12 @@ namespace WebSocketSharp
private void close (HttpStatusCode code) private void close (HttpStatusCode code)
{ {
send (createHandshakeResponse (code)); send (createHandshakeResponse (code));
closeResources (); try {
closeServerResources ();
}
catch {
}
_readyState = WebSocketState.CLOSED; _readyState = WebSocketState.CLOSED;
} }
@ -635,12 +639,11 @@ namespace WebSocketSharp
// As server // As server
private void closeServerResources () private void closeServerResources ()
{ {
if (_context != null && _closeContext != null) if (_closeContext != null)
{
_closeContext (); _closeContext ();
_stream = null;
_context = null; _stream = null;
} _context = null;
} }
private bool concatenateFragments (Stream dest) private bool concatenateFragments (Stream dest)
@ -864,16 +867,6 @@ namespace WebSocketSharp
OnOpen.Emit (this, EventArgs.Empty); OnOpen.Emit (this, EventArgs.Empty);
} }
private bool ping (byte [] data)
{
var frame = createControlFrame (Opcode.PING, new PayloadData (data), _client);
var timeOut = _client ? 5000 : 1000;
return send (frame)
? _receivePong.WaitOne (timeOut)
: false;
}
private void pong (PayloadData data) private void pong (PayloadData data)
{ {
var frame = createControlFrame (Opcode.PONG, data, _client); var frame = createControlFrame (Opcode.PONG, data, _client);
@ -1327,6 +1320,16 @@ namespace WebSocketSharp
close (code); close (code);
} }
internal bool Ping (byte [] data)
{
var frame = createControlFrame (Opcode.PING, new PayloadData (data), _client);
var timeOut = _client ? 5000 : 1000;
return send (frame)
? _receivePong.WaitOne (timeOut)
: false;
}
#endregion #endregion
#region Public Methods #region Public Methods
@ -1352,7 +1355,16 @@ namespace WebSocketSharp
/// </param> /// </param>
public void Close (ushort code) public void Close (ushort code)
{ {
Close (code, ""); var msg = code.CheckIfValidCloseStatusCode ();
if (msg != null)
{
_logger.Error (String.Format ("{0}\ncode: {1}", msg, code));
error (msg);
return;
}
close (new PayloadData (code.ToByteArray (ByteOrder.BIG)));
} }
/// <summary> /// <summary>
@ -1451,38 +1463,40 @@ namespace WebSocketSharp
/// Sends a Ping using the WebSocket connection. /// Sends a Ping using the WebSocket connection.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// <c>true</c> if a <see cref="WebSocket"/> instance receives a Pong in a time; otherwise, <c>false</c>. /// <c>true</c> if the <see cref="WebSocket"/> instance receives a Pong in a time;
/// otherwise, <c>false</c>.
/// </returns> /// </returns>
public bool Ping () public bool Ping ()
{ {
return ping (new byte [] {}); return Ping (new byte [] {});
} }
/// <summary> /// <summary>
/// Sends a Ping with the specified <paramref name="message"/> using the WebSocket connection. /// Sends a Ping with the specified <paramref name="message"/> using the WebSocket connection.
/// </summary> /// </summary>
/// <param name="message"> /// <param name="message">
/// A <see cref="string"/> that contains a message to send with a Ping. /// A <see cref="string"/> that contains a message to send.
/// </param> /// </param>
/// <returns> /// <returns>
/// <c>true</c> if a <see cref="WebSocket"/> instance receives a Pong in a time; otherwise, <c>false</c>. /// <c>true</c> if the <see cref="WebSocket"/> instance receives a Pong in a time;
/// otherwise, <c>false</c>.
/// </returns> /// </returns>
public bool Ping (string message) public bool Ping (string message)
{ {
if (message.IsNullOrEmpty ()) if (message == null || message.Length == 0)
return ping (new byte [] {}); return Ping (new byte [] {});
var data = Encoding.UTF8.GetBytes (message); var data = Encoding.UTF8.GetBytes (message);
if (data.Length > 125) var msg = data.CheckIfValidPingData ();
if (msg != null)
{ {
var msg = "The payload length of a Ping frame must be 125 bytes or less.";
_logger.Error (msg); _logger.Error (msg);
error (msg); error (msg);
return false; return false;
} }
return ping (data); return Ping (data);
} }
/// <summary> /// <summary>