这是我多年来修改过的TCP异步客户端代码。 它能够检测连接丢失(感谢使用保持活动值)。
但现在,我需要在检测到任何连接丢失(任何通信错误或Disconnect()
电话)后自动将其重新连接到服务器。
当我停止监听或断开客户端与它们的连接时,它可以与一些简单的SW TCP服务器一起使用。但是,当我连接到一些真实的设备并开始模拟可能的错误时,问题就开始了。
例如,我断开客户端PC与网络的连接,然后经过一段时间后重新连接",应用程序进入连接状态循环并挂起,特别是方法OnDataReceived
抛出SocketException: ConnectionReset
iRx = stateObject.Socket.EndReceive(asyn);
致电。
由于我不擅长异步代码,我相信我做的事情很糟糕
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace SharpLib.Tcp
{
public enum ConnectionState
{
Connected, Disconnected
};
public delegate void ConnectionStateChangedEventHandler(object sender, ConnectionStateEventArgs args);
public class ConnectionStateEventArgs : EventArgs
{
public ConnectionState ConnectionState { get; set; }
public ConnectionStateEventArgs(ConnectionState state)
{
this.ConnectionState = state;
}
}
/// <summary>
/// Structure of received data
/// </summary>
public class StateObject
{
public byte[] DataBuffer { get; set; }
public Socket Socket { get; set; }
public StateObject(Socket socket)
{
this.DataBuffer = new byte[128];
this.Socket = socket;
}
}
/// <summary>
/// TCP client with asynchronous connecting and data receiving
/// </summary>
public class TcpAsyncClient
{
protected string address;
protected int port;
private Socket socket;
private int keepAliveTime;
private int keepAliveInterval;
private int connectTimeout;
private bool autoReconnect;
private AsyncCallback callback;
private static ManualResetEvent connectDone = new ManualResetEvent(false);
public event MessageEventHandler DataReceived = delegate { };
public event ExceptionEventHandler ExceptionCaught = delegate { };
public event ConnectionStateChangedEventHandler ConnectionStateChanged = delegate { };
public bool Connected
{
get
{
if (socket == null)
return false;
return socket.Connected;
}
}
public TcpAsyncClient(string address, int port, int keepAliveTime = 1000, int keepAliveInterval = 1000, int connectTimeout = 1000, bool autoReconnect = false)
{
this.address = address;
this.port = port;
this.keepAliveInterval = keepAliveInterval;
this.keepAliveTime = keepAliveTime;
this.connectTimeout = connectTimeout;
this.autoReconnect = autoReconnect;
ConnectionStateChanged(this, new ConnectionStateEventArgs(ConnectionState.Disconnected));
}
/// <summary>
/// Connect to tcp server - async
/// </summary>
public void Connect()
{
try
{
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint ipEnd = FindIpEndPoint(address, port);
socket.BeginConnect(ipEnd, new AsyncCallback(ConnectCallback), new StateObject(socket));
connectDone.WaitOne(500);
}
catch (SocketException ex)
{
OnError(ex);
}
}
/// <summary>
/// Connect done callback
/// </summary>
/// <param name="ar"></param>
private void ConnectCallback(IAsyncResult ar)
{
try
{
// Complete the connection.
((StateObject)ar.AsyncState).Socket.EndConnect(ar);
// Signal that the connection has been made.
connectDone.Set();
WaitForData();
SetKeepAlive(true, Convert.ToUInt32(keepAliveTime), Convert.ToUInt32(keepAliveInterval));
ConnectionStateChanged(this, new ConnectionStateEventArgs(ConnectionState.Connected));
}
catch (SocketException ex)
{
OnError(ex);
}
}
/// <summary>
/// Disconnect from tcp server
/// </summary>
public void Disconnect()
{
try
{
// MSDN recommends to Shutdown() before Disconnect()
socket.Shutdown(SocketShutdown.Both);
socket.Disconnect(true);
}
catch { }
ConnectionStateChanged(this, new ConnectionStateEventArgs(ConnectionState.Disconnected));
if (autoReconnect)
{
Connect();
}
}
/// <summary>
/// Send string message to tcp server
/// </summary>
/// <param name="message"></param>
public void Send(string message)
{
// because of this, we can Send from client imidiately after Connect() call
DateTime start = DateTime.Now;
if (!Connected)
{
ConnectionStateChanged(this, new ConnectionStateEventArgs(ConnectionState.Disconnected));
return;
}
// make return on the end of line
message += "\r";
int sent = 0; // how many bytes is already sent
do
{
try
{
sent += socket.Send(System.Text.Encoding.UTF8.GetBytes(message), sent, message.Length - sent, SocketFlags.None);
}
catch (SocketException ex)
{
if (ex.SocketErrorCode == SocketError.WouldBlock ||
ex.SocketErrorCode == SocketError.IOPending ||
ex.SocketErrorCode == SocketError.NoBufferSpaceAvailable)
{
// socket buffer is probably full, wait and try again
Thread.Sleep(30);
}
else
{
OnError(ex);
break;
}
}
}
while (sent < message.Length);
}
/// <summary>
/// Start receiving data from tcp server
/// </summary>
public void WaitForData()
{
try
{
StateObject stateObject = new StateObject(socket);
IAsyncResult result = socket.BeginReceive(stateObject.DataBuffer, 0, 128, SocketFlags.None, new AsyncCallback(OnDataReceived), stateObject);
}
catch (SocketException ex)
{
OnError(ex);
}
}
/// <summary>
/// Data received callback
/// </summary>
/// <param name="asyn"></param>
public void OnDataReceived(IAsyncResult asyn)
{
try
{
StateObject stateObject = (StateObject)asyn.AsyncState;
if (!stateObject.Socket.Connected)
return;
int iRx = stateObject.Socket.EndReceive(asyn);
// Server probably stopped listening
if (iRx == 0)
{
Disconnect();
ConnectionStateChanged(this, new ConnectionStateEventArgs(ConnectionState.Disconnected));
return;
}
char[] chars = new char[iRx];
System.Text.Decoder d = System.Text.Encoding.UTF8.GetDecoder();
int charLen = d.GetChars(stateObject.DataBuffer, 0, iRx, chars, 0);
string szData = new string(chars);
DataReceived(this, new MessageEventArgs(szData));
WaitForData();
}
catch (SocketException ex)
{
OnError(ex);
}
}
/// <summary>
/// Socket exception during connecting or communication with server
/// </summary>
/// <param name="ex"></param>
private void OnError(Exception ex)
{
ExceptionCaught(this, new ExceptionEventArgs(ex));
Disconnect();
}
/// <summary>
/// Set KeepAlive timer for socket
/// </summary>
/// <param name="on"></param>
/// <param name="time"></param>
/// <param name="interval"></param>
private void SetKeepAlive(bool on, uint time, uint interval)
{
int size = Marshal.SizeOf(new uint());
var inOptionValues = new byte[size * 3];
BitConverter.GetBytes((uint)(on ? 1 : 0)).CopyTo(inOptionValues, 0);
BitConverter.GetBytes((uint)time).CopyTo(inOptionValues, size);
BitConverter.GetBytes((uint)interval).CopyTo(inOptionValues, size * 2);
socket.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null);
}
/// <summary>
/// Create ip address from known host name
/// </summary>
/// <param name="hostName"></param>
/// <param name="port"></param>
/// <returns></returns>
private IPEndPoint FindIpEndPoint(string hostName, int port)
{
var addresses = System.Net.Dns.GetHostAddresses(hostName);
if (addresses.Length == 0)
{
throw new ArgumentException(
"Unable to retrieve address from specified host name.",
"hostName"
);
}
return new IPEndPoint(addresses[0], port);
}
}
}