使用C#實現的TCP客戶端,可檢測斷線(包括網線斷開或拔掉),支持斷線重連。客戶端內部有循環緩沖區異步接收數據,對客戶端的讀操作會立即返回,根據方法返回值來判斷讀取數據的字節數。
對外提供3個事件分別是:
收到數據事件(網絡收到數據觸發此事件)
通訊空閑事件(長時間無數據收發,時間可設)
重連失敗事件(默認最大支持3次重連,若3次重連仍然失敗則觸發事件)
使用方式:
private Thread _socketThread;
private TcpClient _client;
_socketThread = new Thread(_client.ReadFormSocket);
_socketThread.IsBackground = false;
_socketThread.Start();
//接收數據操作,除此之外還可以注冊接收事件,有數據來時讀取
byte [] recv = new byte[100];
_client.CommReceive(recv,100);
//發送數據
byte [] send = new byte[10];
for (int i = 0; i < 10; i++)
send[i] = i;
_client.CommSend(send,10);
————————————————
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
using System.Timers;
namespace Communication
{
//通訊接口事件代理
internal delegate void CommRxEventHandler();
internal delegate void CommIdleEventHandler();
//通訊接口
internal interface ICommunication
{
event CommRxEventHandler CommRxEvent; //收到數據事件
event CommIdleEventHandler CommIdleEvent; //通訊空閑事件
int CommSend(byte[] buffer, int size); //發送數據到通訊口
int CommReceive(CommBuffer buffer); //從通訊口接收數據
int CommReceive(byte[] buffer, int size); //從通訊口接收數據
}
//TCP客戶端實現
internal class TcpClient : ICommunication
{
private const int MAX_RECONNECT_TIMES = 3; //斷線重連嘗試次數
private const int COMM_IDLE_TIMES = 3; //通訊空閑觸發間隔
//soket對象及參數
private Socket _socket;
private string _host;
private int _port;
private bool _reconnect;
private int ConnecteFailedCount { get; set; }
public int ReconnectStatistics { get; private set; }
private static uint _keepAliveTime = 5000; //無數據交互持續時間(ms)
private static uint _keepAliveInterval = 500; //發送探測包間隔(ms)
//定時器,用于觸發通訊空閑
private Timer _timer;
private int _commIdleCount;
//實現接口的兩個事件
public event CommRxEventHandler CommRxEvent;
public event CommIdleEventHandler CommIdleEvent;
//重連失敗事件
public event EventHandler ReconnectionFailedEvent;
//數據接收緩存
private CommBuffer recvBuffer;
//構造函數
public TcpClient(string host,int port)
{
_host = host;
_port = port;
_reconnect = false;
ConnecteFailedCount = 0;
recvBuffer = new CommBuffer(20480);
}
//連接
public void Connect()
{
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream,
ProtocolType.Tcp);
_socket.Connect(_host, _port);
ConnecteFailedCount = MAX_RECONNECT_TIMES;
//設置KeepAlive
_socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
byte[] optionValue = new byte[12];
BitConverter.GetBytes(1).CopyTo(optionValue, 0);
BitConverter.GetBytes(_keepAliveTime).CopyTo(optionValue, 4);
BitConverter.GetBytes(_keepAliveInterval).CopyTo(optionValue, 8);
_socket.IOControl(IOControlCode.KeepAliveValues, optionValue, null);
}
//重連
public bool Reconnect()
{
ReconnectStatistics++;
_reconnect = false;
Close();
try
{
Connect();
}
catch (SocketException e)
{
ConnecteFailedCount--;
if (ConnecteFailedCount > 0)
{
//Console.WriteLine("重試次數剩余{0}",ConnecteFailedCount);
_reconnect = true;
return true;
}
else
{
//重連失敗事件
if (ReconnectionFailedEvent != null)
ReconnectionFailedEvent(this, new EventArgs());
return false;
}
}
return true;
}
//釋放資源
public void Close()
{
_socket.Close();
}
//啟動空閑事件觸發
public void StartCommIdle()
{
//定時器配置
_timer = new Timer(500);
_timer.AutoReset = true;
_timer.Elapsed += TimerElapsed;
_timer.Start();
_commIdleCount = 0;
}
//停止空閑事件觸發
public void StopCommIdle()
{
_timer.Elapsed -= TimerElapsed;
_timer.Close();
}
//發送接收數據事件
public void SendRecvEvent()
{
if (CommRxEvent != null)
CommRxEvent();
}
//發送超時事件
public void TimerElapsed(object sender, ElapsedEventArgs e)
{
if (_commIdleCount++ >= COMM_IDLE_TIMES)
{
if (CommIdleEvent != null)
CommIdleEvent();
_commIdleCount = 0;
}
}
//發送數據接收實現,斷線重連
public int CommSend(byte[] buffer, int size)
{
int sendSize = 0;
try
{
sendSize = _socket.Send(buffer, size, SocketFlags.None);
_commIdleCount = 0;
}
catch (SocketException e)
{
ReconnectStatistics++;
_reconnect = true;
}
return sendSize;
}
//接收數據接口實現
public int CommReceive(byte[] buffer, int size)
{
return recvBuffer.Read(buffer, size);
}
//接收數據接口實現
public int CommReceive(CommBuffer buffer)
{
return recvBuffer.CopyTo(buffer);
}
//接收數據線程,使用阻塞方式接收數據
public void ReadFormSocket()
{
int recvNum;
byte[] recv = new byte[2048];
while(true)
{
try
{
recvNum = _socket.Receive(recv, SocketFlags.None);
}
catch(SocketException e)
{
recvNum = 0;
}
//網絡斷開Receive返回0
if (recvNum == 0 || _reconnect == true)
{
//重連次數用盡,退出
if (Reconnect() == false)
break;
}
else
{
recvBuffer.Write(recv, recvNum);
SendRecvEvent();
}
_commIdleCount = 0;
}
}
}
//通訊緩沖結構類
internal class CommBuffer
{
public uint capacity; //緩沖區大小
public int readPtr; //讀指針
public int writePtr; //寫指針
public byte[] pBuf; //緩沖區
//構造函數
public CommBuffer(uint capacity)
{
this.capacity = capacity;
this.readPtr = 0;
this.writePtr = 0;
this.pBuf = new byte[capacity];
}
//從緩沖區中讀取數據到byte數組
public int Read(byte[] buff, int size)
{
int readSize;
for (readSize = 0; readSize < size; readSize++)
{
if (IsEmpty())
break;
buff[readSize] = pBuf[readPtr++];
if (readPtr >= capacity)
readPtr = 0;
}
return readSize;
}
//將byte數組寫入緩沖區
public int Write(byte[] buff, int size)
{
int writeSize, wp;
for (writeSize = 0; writeSize < size; writeSize++)
{
wp = writePtr + 1;
if (wp >= capacity)
wp = 0;
if (wp == readPtr)
break;
pBuf[writePtr] = buff[writeSize];
writePtr = wp;
}
return writeSize;
}
//拷貝buffer緩沖區數據到本緩沖區內,會引起拷貝源有效數據為空
public int Copy(CommBuffer buffer, int len)
{
if (len == 0)
return 0;
byte[] data = new byte[len];
len = buffer.Read(data, len);
return this.Write(data, len);
}
//拷貝本緩沖區中的數據到buffer中,會引起拷貝源有效數據為空
public int CopyTo(CommBuffer buffer)
{
int dataLen = this.DataLength();
if (dataLen == 0)
return 0;
byte[] data = new byte[dataLen];
dataLen = this.Read(data, dataLen);
return buffer.Write(data, dataLen);
}
//索引器實現
public byte this[int index]
{
get
{
if (index >= DataLength())
return 0;
int rp = readPtr + index;
if (rp >= capacity)
rp = rp-(int)capacity;
return pBuf[rp];
}
}
//忽略len長度的數據
public void Skip(int len)
{
while (len-- > 0) {
if (readPtr == writePtr)
break;
readPtr++;
if (readPtr >= capacity)
readPtr = 0;
}
}
//判斷是否有數據
public bool IsEmpty()
{
return writePtr == readPtr;
}
//獲取有效數據長度
public int DataLength()
{
int len = writePtr-readPtr;
if (len < 0)
len = len + (int)capacity;
return len;
}
//清空緩沖區
public void Clear()
{
writePtr = readPtr = 0;
}
//緩沖區整理,將數據移動到0位置,方便后續數據的處理
public void Neaten()
{
uint i, j;
if (readPtr == 0)
{
return; //讀指針已經為0
}
if (readPtr >= writePtr)
{
readPtr = writePtr = 0;
return;
}
if (writePtr >= capacity)
{
readPtr = 0;
writePtr = 0;
return;
}
i = 0;
j = (uint)readPtr;
while (j < writePtr)
{
pBuf[i++] = pBuf[j++];
}
readPtr = 0;
writePtr = (int)i;
}
//顯示緩存數據
public override string ToString()
{
int dataLen = this.DataLength();
string str = string.Format("數據長度{0}: ", this.DataLength());
for (int i = 0; i < dataLen; i++)
{
str += string.Format("{0:X} ",this[i]);
}
return str;
}
}
}
————————————————
ps:通訊接口ICommunication用于將各種通訊方式(網絡,232串口,485總線等)的接口統一,如此上層應用調用統一接口即可,無需關心實際的通訊介質。對于單獨的TCP客戶端而言,此處可以跳過,并無實際意義,不喜歡的小伙伴可自行去掉。