/*
* Copyright (C) 2004-2005 Jonathan Bindel
* Copyright (C) 2006-2007 Eskil Bylund
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
using System;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Timers;
using DCSharp.Backend.Protocols;
namespace DCSharp.Backend.Connections{
public enum ConnectionState
{
Disconnected,
Connecting,
Connected
}
public enum ConnectionError
{
Dns,
Timeout,
General
}
public enum ConnectionMode
{
Message,
Data
}
public class ConnectionEventArgs : EventArgs
{
public ConnectionEventArgs(Connection connection)
{
this.connection = connection;
}
public Connection Connection
{
get { return connection; }
}
private Connection connection;
}
public class ConnectionErrorEventArgs : EventArgs
{
public ConnectionErrorEventArgs(ConnectionError error)
{
this.error = error;
}
public ConnectionError Error
{
get { return error; }
}
private ConnectionError error;
}
public abstract class Connection : IDisposable
{
public event EventHandler StateChanged;
public event EventHandler<ConnectionErrorEventArgs> Error;
protected const int BufferSize = 16384;
private Timer connectionTimer;
private byte[] buffer;
private StringBuilder messageBuffer;
private byte messageSeparator;
private AsyncCallback receivedData;
private bool disposed;
#region Constructors
protected Connection(IProtocol protocol)
{
if (protocol == null)
{
throw new ArgumentNullException("protocol");
}
Protocol = protocol;
state = ConnectionState.Disconnected;
mode = ConnectionMode.Message;
connectionTimer = new Timer();
connectionTimer.Elapsed += new ElapsedEventHandler(OnTimerElapsed);
connectionTimer.Interval = 10000;
buffer = new byte[BufferSize];
messageBuffer = new StringBuilder();
receivedData = new AsyncCallback(OnReceivedData);
if (client == null)
{
client = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
}
}
#endregion
#region Properties
public ConnectionState State
{
get { return state; }
protected set
{
if (state != value)
{
state = value;
OnStateChanged();
}
}
}
private ConnectionState state;
public ConnectionMode Mode
{
get { return mode; }
protected set { mode = value; }
}
private ConnectionMode mode;
protected Socket Client
{
get { return client; }
set { client = value; }
}
private Socket client;
protected IPEndPoint EndPoint
{
get { return endPoint; }
set { endPoint = value; }
}
private IPEndPoint endPoint;
protected IProtocol Protocol
{
get { return protocol; }
set
{
protocol = value;
messageSeparator = protocol.Encoding.GetBytes(protocol.MessageSeparator)[0];
}
}
private IProtocol protocol;
public bool Incoming
{
get { return incoming; }
}
private bool incoming;
#endregion
#region Methods
#region Connect
public virtual void Connect()
{
CheckDisposed();
State = ConnectionState.Connecting;
// Is this an incoming or outgoing connection?
if (!client.Connected)
{
connectionTimer.Start();
// An outgoing connection
try
{
client.BeginConnect(endPoint,
new AsyncCallback(SocketConnected), client);
}
catch (SocketException e)
{
OnError(ConnectionError.General, e);
Disconnect();
}
}
else
{
// An incoming connection
incoming = true;
State = ConnectionState.Connected;
protocol.Initialize();
Receive();
}
}
private void SocketConnected(IAsyncResult ar)
{
Socket client = (Socket)ar.AsyncState;
try
{
client.EndConnect(ar);
connectionTimer.Stop();
State = ConnectionState.Connected;
protocol.Initialize();
Receive();
}
catch (ObjectDisposedException)
{
}
catch (Exception e)
{
OnError(ConnectionError.General, e);
Disconnect();
}
}
public virtual void Disconnect()
{
Dispose();
}
private void OnTimerElapsed(object obj, ElapsedEventArgs args)
{
if (State == ConnectionState.Connecting)
{
OnError(ConnectionError.Timeout, null);
Disconnect();
}
}
#endregion
#region Send
internal virtual void Send(string message)
{
Send(message, protocol.Encoding);
}
internal virtual void Send(string message, Encoding encoding)
{
if (encoding == null)
{
throw new ArgumentNullException("encoding");
}
byte[] bytes = encoding.GetBytes(message);
client.Send(bytes);
}
internal virtual void Send(byte[] data)
{
Send(data, 0, data.Length);
}
internal virtual void Send(byte[] data, int offset, int count)
{
client.Send(data, offset, count, SocketFlags.None);
}
internal virtual void ConnectionSend(string message)
{
if (message == null)
{
throw new ArgumentNullException("message");
}
byte[] bytes = new byte[message.Length];
for (int i = 0; i < message.Length; i++)
{
bytes[i] = Convert.ToByte(message[i]);
}
client.Send(bytes);
}
#endregion
#region Receive
protected virtual void Receive()
{
try
{
client.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None,
receivedData, client);
}
catch (Exception e)
{
OnError(ConnectionError.General, e);
Disconnect();
}
}
private void OnReceivedData(IAsyncResult result)
{
int length = 0;
Socket client = (Socket)result.AsyncState;
try
{
length = client.EndReceive(result);
if (length > 0)
{
ParseData(length);
Receive();
}
else
{
Debug.WriteLine("No more data.");
Disconnect();
}
}
catch (ObjectDisposedException)
{
}
catch (Exception e)
{
OnError(ConnectionError.General, e);
Disconnect();
}
}
private void ParseData(int length)
{
int pos = 0;
do
{
switch (mode)
{
case ConnectionMode.Message:
int end = Array.IndexOf(buffer, messageSeparator, pos,
length - pos);
if (end >= 0)
{
string message = protocol.Encoding.GetString(buffer, pos, end - pos);
if (messageBuffer.Length > 0)
{
messageBuffer.Append(message);
message = messageBuffer.ToString();
messageBuffer.Remove(0, messageBuffer.Length);
}
if (message.Length > 0)
{
HandleMessage(message);
}
pos = end + 1;
}
else
{
messageBuffer.Append(protocol.Encoding.GetString(buffer, pos, length - pos));
pos = length;
}
break;
case ConnectionMode.Data:
int handled = HandleData(buffer, pos, length - pos);
pos += handled;
break;
}
}
while (pos < length && !disposed);
}
protected virtual void HandleMessage(string message)
{
if (message == null)
{
throw new ArgumentNullException("message");
}
Protocol.Handle(message);
}
protected virtual int HandleData(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
#endregion
protected void CheckDisposed()
{
if (disposed)
{
throw new ObjectDisposedException(GetType().FullName);
}
}
protected virtual void OnError(ConnectionError error, Exception e)
{
if (Error != null)
{
Error(this, new ConnectionErrorEventArgs(error));
}
}
protected virtual void OnStateChanged()
{
if (StateChanged != null)
{
StateChanged(this, EventArgs.Empty);
}
}
#region IDisposable Members
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposed)
{
return;
}
disposed = true;
if (disposing)
{
State = ConnectionState.Disconnected;
client.Close();
connectionTimer.Dispose();
}
}
#endregion
#endregion
}
}
|