使用C#实现的TCP客户端,可检测断线(包括网线断开或拔掉),支持断线重连。客户端内部有循环缓冲区异步接收数据,对客户端的读操作会立即返回,根据方法返回值来判断读取数据的字节数。
ps:通讯接口ICommunication用于将各种通讯方式(网络,232串口,485总线等)的接口统一,如此上层应用调用统一接口即可,无需关心实际的通讯介质。对于单独的TCP客户端而言,此处可以跳过,并无实际意义,不喜欢的小伙伴可自行去掉。
对外提供3个事件分别是:
- 收到数据事件(网络收到数据触发此事件)
- 通讯空闲事件(长时间无数据收发,时间可设)
- 重连失败事件(默认最大支持3次重连,若3次重连仍然失败则触发事件)
使用方式:
//将ReadFromSocket作为线程运行
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;
}
}
}