using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Security.Authentication;
using System.Text;
using System.Threading;
using SuperSocket.Common;
using SuperSocket.SocketBase.Command;
using SuperSocket.SocketBase.Config;
using SuperSocket.SocketBase.Logging;
using SuperSocket.SocketBase.Protocol;
namespace SuperSocket.SocketBase
{
///
/// AppSession base class
///
/// The type of the app session.
/// The type of the request info.
public abstract class AppSession : IAppSession, IAppSession
where TAppSession : AppSession, IAppSession, new()
where TRequestInfo : class, IRequestInfo
{
#region Properties
///
/// Gets the app server instance assosiated with the session.
///
public virtual AppServerBase AppServer { get; private set; }
///
/// Gets the app server instance assosiated with the session.
///
IAppServer IAppSession.AppServer
{
get { return this.AppServer; }
}
///
/// Gets or sets the charset which is used for transfering text message.
///
///
/// The charset.
///
public Encoding Charset { get; set; }
private IDictionary m_Items;
///
/// Gets the items dictionary, only support 10 items maximum
///
public IDictionary Items
{
get
{
if (m_Items == null)
m_Items = new Dictionary(10);
return m_Items;
}
}
private bool m_Connected = false;
///
/// Gets a value indicating whether this is connected.
///
///
/// true if connected; otherwise, false .
///
public bool Connected
{
get { return m_Connected; }
internal set { m_Connected = value; }
}
///
/// Gets or sets the previous command.
///
///
/// The prev command.
///
public string PrevCommand { get; set; }
///
/// Gets or sets the current executing command.
///
///
/// The current command.
///
public string CurrentCommand { get; set; }
///
/// Gets or sets the secure protocol of transportation layer.
///
///
/// The secure protocol.
///
public SslProtocols SecureProtocol
{
get { return SocketSession.SecureProtocol; }
set { SocketSession.SecureProtocol = value; }
}
///
/// Gets the local listening endpoint.
///
public IPEndPoint LocalEndPoint
{
get { return SocketSession.LocalEndPoint; }
}
///
/// Gets the remote endpoint of client.
///
public IPEndPoint RemoteEndPoint
{
get { return SocketSession.RemoteEndPoint; }
}
///
/// Gets the logger.
///
public ILog Logger
{
get { return AppServer.Logger; }
}
///
/// Gets or sets the last active time of the session.
///
///
/// The last active time.
///
public DateTime LastActiveTime { get; set; }
///
/// Gets the start time of the session.
///
public DateTime StartTime { get; private set; }
///
/// Gets the session ID.
///
public string SessionID { get; private set; }
///
/// Gets the socket session of the AppSession.
///
public ISocketSession SocketSession { get; private set; }
///
/// Gets the config of the server.
///
public IServerConfig Config
{
get { return AppServer.Config; }
}
IReceiveFilter m_ReceiveFilter;
#endregion
///
/// Initializes a new instance of the class.
///
public AppSession()
{
this.StartTime = DateTime.Now;
this.LastActiveTime = this.StartTime;
}
///
/// Initializes the specified app session by AppServer and SocketSession.
///
/// The app server.
/// The socket session.
public virtual void Initialize(IAppServer appServer, ISocketSession socketSession)
{
var castedAppServer = (AppServerBase)appServer;
AppServer = castedAppServer;
Charset = castedAppServer.TextEncoding;
SocketSession = socketSession;
SessionID = socketSession.SessionID;
m_Connected = true;
m_ReceiveFilter = castedAppServer.ReceiveFilterFactory.CreateFilter(appServer, this, socketSession.RemoteEndPoint);
var filterInitializer = m_ReceiveFilter as IReceiveFilterInitializer;
if (filterInitializer != null)
filterInitializer.Initialize(castedAppServer, this);
socketSession.Initialize(this);
OnInit();
}
///
/// Starts the session.
///
void IAppSession.StartSession()
{
OnSessionStarted();
}
///
/// Called when [init].
///
protected virtual void OnInit()
{
}
///
/// Called when [session started].
///
protected virtual void OnSessionStarted()
{
}
///
/// Called when [session closed].
///
/// The reason.
internal protected virtual void OnSessionClosed(CloseReason reason)
{
}
///
/// Handles the exceptional error, it only handles application error.
///
/// The exception.
protected virtual void HandleException(Exception e)
{
Logger.Error(this, e);
this.Close(CloseReason.ApplicationError);
}
///
/// Handles the unknown request.
///
/// The request info.
protected virtual void HandleUnknownRequest(TRequestInfo requestInfo)
{
}
internal void InternalHandleUnknownRequest(TRequestInfo requestInfo)
{
HandleUnknownRequest(requestInfo);
}
internal void InternalHandleExcetion(Exception e)
{
HandleException(e);
}
///
/// Closes the session by the specified reason.
///
/// The close reason.
public virtual void Close(CloseReason reason)
{
this.SocketSession.Close(reason);
}
///
/// Closes this session.
///
public virtual void Close()
{
Close(CloseReason.ServerClosing);
}
#region Sending processing
///
/// Try to send the message to client.
///
/// The message which will be sent.
/// Indicate whether the message was pushed into the sending queue
public virtual bool TrySend(string message)
{
var data = this.Charset.GetBytes(message);
return InternalTrySend(new ArraySegment(data, 0, data.Length));
}
///
/// Sends the message to client.
///
/// The message which will be sent.
public virtual void Send(string message)
{
var data = this.Charset.GetBytes(message);
Send(data, 0, data.Length);
}
///
/// Try to send the data to client.
///
/// The data which will be sent.
/// The offset.
/// The length.
/// Indicate whether the message was pushed into the sending queue
public virtual bool TrySend(byte[] data, int offset, int length)
{
return InternalTrySend(new ArraySegment(data, offset, length));
}
///
/// Sends the data to client.
///
/// The data which will be sent.
/// The offset.
/// The length.
public virtual void Send(byte[] data, int offset, int length)
{
InternalSend(new ArraySegment(data, offset, length));
}
private bool InternalTrySend(ArraySegment segment)
{
if (!SocketSession.TrySend(segment))
return false;
LastActiveTime = DateTime.Now;
return true;
}
///
/// Try to send the data segment to client.
///
/// The segment which will be sent.
/// Indicate whether the message was pushed into the sending queue
public virtual bool TrySend(ArraySegment segment)
{
if (!m_Connected)
return false;
return InternalTrySend(segment);
}
private void InternalSend(ArraySegment segment)
{
if (!m_Connected)
return;
if (InternalTrySend(segment))
return;
var sendTimeOut = Config.SendTimeOut;
//Don't retry, timeout directly
if (sendTimeOut < 0)
{
throw new TimeoutException("The sending attempt timed out");
}
var timeOutTime = sendTimeOut > 0 ? DateTime.Now.AddMilliseconds(sendTimeOut) : DateTime.Now;
var spinWait = new SpinWait();
while (m_Connected)
{
spinWait.SpinOnce();
if (InternalTrySend(segment))
return;
//If sendTimeOut = 0, don't have timeout check
if (sendTimeOut > 0 && DateTime.Now >= timeOutTime)
{
throw new TimeoutException("The sending attempt timed out");
}
}
}
///
/// Sends the data segment to client.
///
/// The segment which will be sent.
public virtual void Send(ArraySegment segment)
{
InternalSend(segment);
}
private bool InternalTrySend(IList> segments)
{
if (!SocketSession.TrySend(segments))
return false;
LastActiveTime = DateTime.Now;
return true;
}
///
/// Try to send the data segments to client.
///
/// The segments.
/// Indicate whether the message was pushed into the sending queue; if it returns false, the sending queue may be full or the socket is not connected
public virtual bool TrySend(IList> segments)
{
if (!m_Connected)
return false;
return InternalTrySend(segments);
}
private void InternalSend(IList> segments)
{
if (!m_Connected)
return;
if (InternalTrySend(segments))
return;
var sendTimeOut = Config.SendTimeOut;
//Don't retry, timeout directly
if (sendTimeOut < 0)
{
throw new TimeoutException("The sending attempt timed out");
}
var timeOutTime = sendTimeOut > 0 ? DateTime.Now.AddMilliseconds(sendTimeOut) : DateTime.Now;
var spinWait = new SpinWait();
while (m_Connected)
{
spinWait.SpinOnce();
if (InternalTrySend(segments))
return;
//If sendTimeOut = 0, don't have timeout check
if (sendTimeOut > 0 && DateTime.Now >= timeOutTime)
{
throw new TimeoutException("The sending attempt timed out");
}
}
}
///
/// Sends the data segments to client.
///
/// The segments.
public virtual void Send(IList> segments)
{
InternalSend(segments);
}
///
/// Sends the response.
///
/// The message which will be sent.
/// The parameter values.
public virtual void Send(string message, params object[] paramValues)
{
var data = this.Charset.GetBytes(string.Format(message, paramValues));
InternalSend(new ArraySegment(data, 0, data.Length));
}
#endregion
#region Receiving processing
///
/// Sets the next Receive filter which will be used when next data block received
///
/// The next receive filter.
protected void SetNextReceiveFilter(IReceiveFilter nextReceiveFilter)
{
m_ReceiveFilter = nextReceiveFilter;
}
///
/// Gets the maximum allowed length of the request.
///
///
protected virtual int GetMaxRequestLength()
{
return AppServer.Config.MaxRequestLength;
}
///
/// Filters the request.
///
/// The read buffer.
/// The offset.
/// The length.
/// if set to true [to be copied].
/// The rest, the size of the data which has not been processed
/// return offset delta of next receiving buffer.
///
TRequestInfo FilterRequest(byte[] readBuffer, int offset, int length, bool toBeCopied, out int rest, out int offsetDelta)
{
if (!AppServer.OnRawDataReceived(this, readBuffer, offset, length))
{
rest = 0;
offsetDelta = 0;
return null;
}
var currentRequestLength = m_ReceiveFilter.LeftBufferSize;
var requestInfo = m_ReceiveFilter.Filter(readBuffer, offset, length, toBeCopied, out rest);
if (m_ReceiveFilter.State == FilterState.Error)
{
rest = 0;
offsetDelta = 0;
Close(CloseReason.ProtocolError);
return null;
}
var offsetAdapter = m_ReceiveFilter as IOffsetAdapter;
offsetDelta = offsetAdapter != null ? offsetAdapter.OffsetDelta : 0;
if (requestInfo == null)
{
//current buffered length
currentRequestLength = m_ReceiveFilter.LeftBufferSize;
}
else
{
//current request length
currentRequestLength = currentRequestLength + length - rest;
}
var maxRequestLength = GetMaxRequestLength();
if (currentRequestLength >= maxRequestLength)
{
if (Logger.IsErrorEnabled)
Logger.Error(this, string.Format("Max request length: {0}, current processed length: {1}", maxRequestLength, currentRequestLength));
Close(CloseReason.ProtocolError);
return null;
}
//If next Receive filter wasn't set, still use current Receive filter in next round received data processing
if (m_ReceiveFilter.NextReceiveFilter != null)
m_ReceiveFilter = m_ReceiveFilter.NextReceiveFilter;
return requestInfo;
}
///
/// Processes the request data.
///
/// The read buffer.
/// The offset.
/// The length.
/// if set to true [to be copied].
///
/// return offset delta of next receiving buffer
///
int IAppSession.ProcessRequest(byte[] readBuffer, int offset, int length, bool toBeCopied)
{
int rest, offsetDelta;
while (true)
{
var requestInfo = FilterRequest(readBuffer, offset, length, toBeCopied, out rest, out offsetDelta);
if (requestInfo != null)
{
try
{
AppServer.ExecuteCommand(this, requestInfo);
}
catch (Exception e)
{
HandleException(e);
}
}
if (rest <= 0)
{
return offsetDelta;
}
//Still have data has not been processed
offset = offset + length - rest;
length = rest;
}
}
#endregion
}
///
/// AppServer basic class for whose request infoe type is StringRequestInfo
///
/// The type of the app session.
public abstract class AppSession : AppSession
where TAppSession : AppSession, IAppSession, new()
{
private bool m_AppendNewLineForResponse = false;
private static string m_NewLine = "\r\n";
///
/// Initializes a new instance of the class.
///
public AppSession()
: this(true)
{
}
///
/// Initializes a new instance of the class.
///
/// if set to true [append new line for response].
public AppSession(bool appendNewLineForResponse)
{
m_AppendNewLineForResponse = appendNewLineForResponse;
}
///
/// Handles the unknown request.
///
/// The request info.
protected override void HandleUnknownRequest(StringRequestInfo requestInfo)
{
Send("Unknown request: " + requestInfo.Key);
}
///
/// Processes the sending message.
///
/// The raw message.
///
protected virtual string ProcessSendingMessage(string rawMessage)
{
if (!m_AppendNewLineForResponse)
return rawMessage;
if (AppServer.Config.Mode == SocketMode.Udp)
return rawMessage;
if (string.IsNullOrEmpty(rawMessage) || !rawMessage.EndsWith(m_NewLine))
return rawMessage + m_NewLine;
else
return rawMessage;
}
///
/// Sends the specified message.
///
/// The message.
///
public override void Send(string message)
{
base.Send(ProcessSendingMessage(message));
}
///
/// Sends the response.
///
/// The message.
/// The param values.
/// Indicate whether the message was pushed into the sending queue
public override void Send(string message, params object[] paramValues)
{
base.Send(ProcessSendingMessage(message), paramValues);
}
}
///
/// AppServer basic class for whose request infoe type is StringRequestInfo
///
public class AppSession : AppSession
{
}
}