///
/// Event driven TCP client wrapper
///
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Text;
public class TCP_Client : IDisposable
{
#region Consts/Default values
const int DEFAULTTIMEOUT = 5000; //Default to 5 seconds on all timeouts
const int RECONNECTINTERVAL = 2000; //Default to 2 seconds reconnect attempt rate
#endregion
#region Components, Events, Delegates, and CTOR
//Timer used to detect receive timeouts
private System.Timers.Timer tmrReceiveTimeout = new System.Timers.Timer();
private System.Timers.Timer tmrSendTimeout = new System.Timers.Timer();
private System.Timers.Timer tmrConnectTimeout = new System.Timers.Timer();
public delegate void delDataReceived(TCP_Client sender, object data);
public event delDataReceived DataReceived;
public delegate void delConnectionStatusChanged(TCP_Client sender, ConnectionStatus status);
public event delConnectionStatusChanged ConnectionStatusChanged;
public enum ConnectionStatus
{
NeverConnected,
Connecting,
Connected,
AutoReconnecting,
DisconnectedByUser,
DisconnectedByHost,
ConnectFail_Timeout,
ReceiveFail_Timeout,
SendFail_Timeout,
SendFail_NotConnected,
Error
}
public TCP_Client(IPAddress ip, int port, bool autoreconnect = true)
{
this._IP = ip;
this._Port = port;
this._AutoReconnect = autoreconnect;
this._client = new TcpClient(AddressFamily.InterNetwork);
this._client.NoDelay = true; //Disable the nagel algorithm for simplicity
ReceiveTimeout = DEFAULTTIMEOUT;
SendTimeout = DEFAULTTIMEOUT;
ConnectTimeout = DEFAULTTIMEOUT;
ReconnectInterval = RECONNECTINTERVAL;
tmrReceiveTimeout.AutoReset = false;
tmrReceiveTimeout.Elapsed += new System.Timers.ElapsedEventHandler(tmrReceiveTimeout_Elapsed);
tmrConnectTimeout.AutoReset = false;
tmrConnectTimeout.Elapsed += new System.Timers.ElapsedEventHandler(tmrConnectTimeout_Elapsed);
tmrSendTimeout.AutoReset = false;
tmrSendTimeout.Elapsed += new System.Timers.ElapsedEventHandler(tmrSendTimeout_Elapsed);
ConnectionState = ConnectionStatus.NeverConnected;
}
#endregion
#region Private methods/Event Handlers
void tmrSendTimeout_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
this.ConnectionState = ConnectionStatus.SendFail_Timeout;
DisconnectByHost();
}
void tmrReceiveTimeout_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
this.ConnectionState = ConnectionStatus.ReceiveFail_Timeout;
DisconnectByHost();
}
void tmrConnectTimeout_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
ConnectionState = ConnectionStatus.ConnectFail_Timeout;
DisconnectByHost();
}
private void DisconnectByHost()
{
this.ConnectionState = ConnectionStatus.DisconnectedByHost;
tmrReceiveTimeout.Stop();
if (AutoReconnect)
Reconnect();
}
private void Reconnect()
{
if (this.ConnectionState == ConnectionStatus.Connected)
return;
this.ConnectionState = ConnectionStatus.AutoReconnecting;
try
{
this._client.Client.BeginDisconnect(true, new AsyncCallback(cbDisconnectByHostComplete), this._client.Client);
}
catch { }
}
#endregion
#region Public Methods
///
/// Try connecting to the remote host
///
public void Connect()
{
if (this.ConnectionState == ConnectionStatus.Connected)
return;
this.ConnectionState = ConnectionStatus.Connecting;
tmrConnectTimeout.Start();
this._client.BeginConnect(this._IP, this._Port, new AsyncCallback(cbConnect), this._client.Client);
}
///
/// Try disconnecting from the remote host
///
public void Disconnect()
{
if (this.ConnectionState != ConnectionStatus.Connected)
return;
this._client.Client.BeginDisconnect(true, new AsyncCallback(cbDisconnectComplete), this._client.Client);
}
///
/// Try sending a string to the remote host
///
/// The data to send
public void Send(string data)
{
if (this.ConnectionState != ConnectionStatus.Connected)
{
this.ConnectionState = ConnectionStatus.SendFail_NotConnected;
return;
}
var bytes = _encode.GetBytes(data);
SocketError err = new SocketError();
tmrSendTimeout.Start();
this._client.Client.BeginSend(bytes, 0, bytes.Length, SocketFlags.None, out err, new AsyncCallback(cbSendComplete), this._client.Client);
if (err != SocketError.Success)
{
Action doDCHost = new Action(DisconnectByHost);
doDCHost.Invoke();
}
}
///
/// Try sending byte data to the remote host
///
/// The data to send
public void Send(byte[] data)
{
if (this.ConnectionState != ConnectionStatus.Connected)
throw new InvalidOperationException("Cannot send data, socket is not connected");
SocketError err = new SocketError();
this._client.Client.BeginSend(data, 0, data.Length, SocketFlags.None, out err, new AsyncCallback(cbSendComplete), this._client.Client);
if (err != SocketError.Success)
{
Action doDCHost = new Action(DisconnectByHost);
doDCHost.Invoke();
}
}
public void Dispose()
{
this._client.Close();
this._client.Client.Dispose();
}
#endregion
#region Callbacks
private void cbConnectComplete()
{
if (_client.Connected == true)
{
tmrConnectTimeout.Stop();
ConnectionState = ConnectionStatus.Connected;
this._client.Client.BeginReceive(this.dataBuffer, 0, this.dataBuffer.Length, SocketFlags.None, new AsyncCallback(cbDataReceived), this._client.Client);
}
else
{
ConnectionState = ConnectionStatus.Error;
}
}
private void cbDisconnectByHostComplete(IAsyncResult result)
{
var r = result.AsyncState as Socket;
if (r == null)
throw new InvalidOperationException("Invalid IAsyncResult - Could not interpret as a socket object");
r.EndDisconnect(result);
if (this.AutoReconnect)
{
Action doConnect = new Action(Connect);
doConnect.Invoke();
return;
}
}
private void cbDisconnectComplete(IAsyncResult result)
{
var r = result.AsyncState as Socket;
if (r == null)
throw new InvalidOperationException("Invalid IAsyncResult - Could not interpret as a socket object");
r.EndDisconnect(result);
this.ConnectionState = ConnectionStatus.DisconnectedByUser;
}
private void cbConnect(IAsyncResult result)
{
var sock = result.AsyncState as Socket;
if (result == null)
throw new InvalidOperationException("Invalid IAsyncResult - Could not interpret as a socket object");
if (!sock.Connected)
{
if (AutoReconnect)
{
System.Threading.Thread.Sleep(ReconnectInterval);
Action reconnect = new Action(Connect);
reconnect.Invoke();
return;
}
else
return;
}
sock.EndConnect(result);
var callBack = new Action(cbConnectComplete);
callBack.Invoke();
}
private void cbSendComplete(IAsyncResult result)
{
var r = result.AsyncState as Socket;
if (r == null)
throw new InvalidOperationException("Invalid IAsyncResult - Could not interpret as a socket object");
SocketError err = new SocketError();
r.EndSend(result, out err);
if (err != SocketError.Success)
{
Action doDCHost = new Action(DisconnectByHost);
doDCHost.Invoke();
}
else
{
lock (SyncLock)
{
tmrSendTimeout.Stop();
}
}
}
private void cbChangeConnectionStateComplete(IAsyncResult result)
{
var r = result.AsyncState as TCP_Client;
if (r == null)
throw new InvalidOperationException("Invalid IAsyncResult - Could not interpret as a EDTC object");
r.ConnectionStatusChanged.EndInvoke(result);
}
private void cbDataReceived(IAsyncResult result)
{
var sock = result.AsyncState as Socket;
if (sock == null)
throw new InvalidOperationException("Invalid IASyncResult - Could not interpret as a socket");
SocketError err = new SocketError();
int bytes = sock.EndReceive(result, out err);
if (bytes == 0 || err != SocketError.Success)
{
lock (SyncLock)
{
tmrReceiveTimeout.Start();
return;
}
}
else
{
lock (SyncLock)
{
tmrReceiveTimeout.Stop();
}
}
if (DataReceived != null)
DataReceived.BeginInvoke(this, dataBuffer, new AsyncCallback(cbDataRecievedCallbackComplete), this);
//DataReceived.BeginInvoke(this, _encode.GetString(dataBuffer, 0, bytes), new AsyncCallback(cbDataRecievedCallbackComplete), this);
}
private void cbDataRecievedCallbackComplete(IAsyncResult result)
{
var r = result.AsyncState as TCP_Client;
if (r == null)
throw new InvalidOperationException("Invalid IAsyncResult - Could not interpret as EDTC object");
r.DataReceived.EndInvoke(result);
SocketError err = new SocketError();
this._client.Client.BeginReceive(this.dataBuffer, 0, this.dataBuffer.Length, SocketFlags.None, out err, new AsyncCallback(cbDataReceived), this._client.Client);
if (err != SocketError.Success)
{
Action doDCHost = new Action(DisconnectByHost);
doDCHost.Invoke();
}
}
#endregion
#region Properties and members
private IPAddress _IP = IPAddress.None;
private ConnectionStatus _ConStat;
private TcpClient _client;
private byte[] dataBuffer = new byte[5000];
private bool _AutoReconnect = false;
private int _Port = 0;
private Encoding _encode = Encoding.Default;
object _SyncLock = new object();
///
/// Syncronizing object for asyncronous operations
///
public object SyncLock
{
get
{
return _SyncLock;
}
}
///
/// Encoding to use for sending and receiving
///
public Encoding DataEncoding
{
get
{
return _encode;
}
set
{
_encode = value;
}
}
///
/// Current state that the connection is in
///
public ConnectionStatus ConnectionState
{
get
{
return _ConStat;
}
private set
{
bool raiseEvent = value != _ConStat;
_ConStat = value;
if (ConnectionStatusChanged != null && raiseEvent)
ConnectionStatusChanged.BeginInvoke(this, _ConStat, new AsyncCallback(cbChangeConnectionStateComplete), this);
}
}
///
/// True to autoreconnect at the given reconnection interval after a remote host closes the connection
///
public bool AutoReconnect
{
get
{
return _AutoReconnect;
}
set
{
_AutoReconnect = value;
}
}
public int ReconnectInterval { get; set; }
///
/// IP of the remote host
///
public IPAddress IP
{
get
{
return _IP;
}
}
///
/// Port to connect to on the remote host
///
public int Port
{
get
{
return _Port;
}
}
///
/// Time to wait after a receive operation is attempted before a timeout event occurs
///
public int ReceiveTimeout
{
get
{
return (int)tmrReceiveTimeout.Interval;
}
set
{
tmrReceiveTimeout.Interval = (double)value;
}
}
///
/// Time to wait after a send operation is attempted before a timeout event occurs
///
public int SendTimeout
{
get
{
return (int)tmrSendTimeout.Interval;
}
set
{
tmrSendTimeout.Interval = (double)value;
}
}
///
/// Time to wait after a connection is attempted before a timeout event occurs
///
public int ConnectTimeout
{
get
{
return (int)tmrConnectTimeout.Interval;
}
set
{
tmrConnectTimeout.Interval = (double)value;
}
}
#endregion
}