使用C#實現(xiàn)的TCP客戶端,可檢測斷線(包括網(wǎng)線斷開或拔掉),支持?jǐn)嗑重連。客戶端內(nèi)部有循環(huán)緩沖區(qū)異步接收數(shù)據(jù),對客戶端的讀操作會立即返回,根據(jù)方法返回值來判斷讀取數(shù)據(jù)的字節(jié)數(shù)。
對外提供3個事件分別是:
收到數(shù)據(jù)事件(網(wǎng)絡(luò)收到數(shù)據(jù)觸發(fā)此事件)
通訊空閑事件(長時間無數(shù)據(jù)收發(fā),時間可設(shè))
重連失敗事件(默認(rèn)最大支持3次重連,若3次重連仍然失敗則觸發(fā)事件)
使用方式:
private Thread _socketThread;
private TcpClient _client;
_socketThread = new Thread(_client.ReadFormSocket);
_socketThread.IsBackground = false;
_socketThread.Start();
//接收數(shù)據(jù)操作,除此之外還可以注冊接收事件,有數(shù)據(jù)來時讀取
byte [] recv = new byte[100];
_client.CommReceive(recv,100);
//發(fā)送數(shù)據(jù)
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; //收到數(shù)據(jù)事件
event CommIdleEventHandler CommIdleEvent; //通訊空閑事件
int CommSend(byte[] buffer, int size); //發(fā)送數(shù)據(jù)到通訊口
int CommReceive(CommBuffer buffer); //從通訊口接收數(shù)據(jù)
int CommReceive(byte[] buffer, int size); //從通訊口接收數(shù)據(jù)
}
//TCP客戶端實現(xiàn)
internal class TcpClient : ICommunication
{
private const int MAX_RECONNECT_TIMES = 3; //斷線重連嘗試次數(shù)
private const int COMM_IDLE_TIMES = 3; //通訊空閑觸發(fā)間隔
//soket對象及參數(shù)
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; //無數(shù)據(jù)交互持續(xù)時間(ms)
private static uint _keepAliveInterval = 500; //發(fā)送探測包間隔(ms)
//定時器,用于觸發(fā)通訊空閑
private Timer _timer;
private int _commIdleCount;
//實現(xiàn)接口的兩個事件
public event CommRxEventHandler CommRxEvent;
public event CommIdleEventHandler CommIdleEvent;
//重連失敗事件
public event EventHandler ReconnectionFailedEvent;
//數(shù)據(jù)接收緩存
private CommBuffer recvBuffer;
//構(gòu)造函數(shù)
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;
//設(shè)置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("重試次數(shù)剩余{0}",ConnecteFailedCount);
_reconnect = true;
return true;
}
else
{
//重連失敗事件
if (ReconnectionFailedEvent != null)
ReconnectionFailedEvent(this, new EventArgs());
return false;
}
}
return true;
}
//釋放資源
public void Close()
{
_socket.Close();
}
//啟動空閑事件觸發(fā)
public void StartCommIdle()
{
//定時器配置
_timer = new Timer(500);
_timer.AutoReset = true;
_timer.Elapsed += TimerElapsed;
_timer.Start();
_commIdleCount = 0;
}
//停止空閑事件觸發(fā)
public void StopCommIdle()
{
_timer.Elapsed -= TimerElapsed;
_timer.Close();
}
//發(fā)送接收數(shù)據(jù)事件
public void SendRecvEvent()
{
if (CommRxEvent != null)
CommRxEvent();
}
//發(fā)送超時事件
public void TimerElapsed(object sender, ElapsedEventArgs e)
{
if (_commIdleCount++ >= COMM_IDLE_TIMES)
{
if (CommIdleEvent != null)
CommIdleEvent();
_commIdleCount = 0;
}
}
//發(fā)送數(shù)據(jù)接收實現(xiàn),斷線重連
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;
}
//接收數(shù)據(jù)接口實現(xiàn)
public int CommReceive(byte[] buffer, int size)
{
return recvBuffer.Read(buffer, size);
}
//接收數(shù)據(jù)接口實現(xiàn)
public int CommReceive(CommBuffer buffer)
{
return recvBuffer.CopyTo(buffer);
}
//接收數(shù)據(jù)線程,使用阻塞方式接收數(shù)據(jù)
public void ReadFormSocket()
{
int recvNum;
byte[] recv = new byte[2048];
while(true)
{
try
{
recvNum = _socket.Receive(recv, SocketFlags.None);
}
catch(SocketException e)
{
recvNum = 0;
}
//網(wǎng)絡(luò)斷開Receive返回0
if (recvNum == 0 || _reconnect == true)
{
//重連次數(shù)用盡,退出
if (Reconnect() == false)
break;
}
else
{
recvBuffer.Write(recv, recvNum);
SendRecvEvent();
}
_commIdleCount = 0;
}
}
}
//通訊緩沖結(jié)構(gòu)類
internal class CommBuffer
{
public uint capacity; //緩沖區(qū)大小
public int readPtr; //讀指針
public int writePtr; //寫指針
public byte[] pBuf; //緩沖區(qū)
//構(gòu)造函數(shù)
public CommBuffer(uint capacity)
{
this.capacity = capacity;
this.readPtr = 0;
this.writePtr = 0;
this.pBuf = new byte[capacity];
}
//從緩沖區(qū)中讀取數(shù)據(jù)到byte數(shù)組
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數(shù)組寫入緩沖區(qū)
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緩沖區(qū)數(shù)據(jù)到本緩沖區(qū)內(nèi),會引起拷貝源有效數(shù)據(jù)為空
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);
}
//拷貝本緩沖區(qū)中的數(shù)據(jù)到buffer中,會引起拷貝源有效數(shù)據(jù)為空
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);
}
//索引器實現(xiàn)
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長度的數(shù)據(jù)
public void Skip(int len)
{
while (len-- > 0) {
if (readPtr == writePtr)
break;
readPtr++;
if (readPtr >= capacity)
readPtr = 0;
}
}
//判斷是否有數(shù)據(jù)
public bool IsEmpty()
{
return writePtr == readPtr;
}
//獲取有效數(shù)據(jù)長度
public int DataLength()
{
int len = writePtr-readPtr;
if (len < 0)
len = len + (int)capacity;
return len;
}
//清空緩沖區(qū)
public void Clear()
{
writePtr = readPtr = 0;
}
//緩沖區(qū)整理,將數(shù)據(jù)移動到0位置,方便后續(xù)數(shù)據(jù)的處理
public void Neaten()
{
uint i, j;
if (readPtr == 0)
{
return; //讀指針已經(jīng)為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;
}
//顯示緩存數(shù)據(jù)
public override string ToString()
{
int dataLen = this.DataLength();
string str = string.Format("數(shù)據(jù)長度{0}: ", this.DataLength());
for (int i = 0; i < dataLen; i++)
{
str += string.Format("{0:X} ",this[i]);
}
return str;
}
}
}
————————————————
ps:通訊接口ICommunication用于將各種通訊方式(網(wǎng)絡(luò),232串口,485總線等)的接口統(tǒng)一,如此上層應(yīng)用調(diào)用統(tǒng)一接口即可,無需關(guān)心實際的通訊介質(zhì)。對于單獨的TCP客戶端而言,此處可以跳過,并無實際意義,不喜歡的小伙伴可自行去掉。