最近业务需要,需要重写一些C# 的 TCP通讯相关的东西,为了混点分下载资料,发上来供大家参考,有问题请留言指教,理性探讨,相互学习。
情景如下:公司业务需要在云服务器上实时采集很多智能传感器/PLC等之类的终端设备数据,有实时性/安全性/容错性的实际要求,采用最常见的CS架构。基本功能是很好实现的。但是中间很多坑在这里和大家分享。
先说点教训与心得:
1.这种多接入对象一定要采用异步监听。
2.TCP链路最好采用短链接进行单次业务流程,即上下行1次数据,长连接不好维护。
3.实际部署在云端后,网络延迟等因素,网络流字节到达时间有延迟,所以不要想着 Sleep 一下,再去读。老老实实设计通讯规约,设计好帧头,起始符,校验方式,容错机制。
4.要做到较高实时性,一定不要故弄玄虚,规约设计的内容存在即有用,非传输必须的功能一定不要在传输过程中处理。
5.容错机制设计时,不要想到云服务器应该不会怎样,而要考虑成出现这种异常要怎样。
6.接收与解析异步处理。
然后是代码,需要使用的同学详看,下面已经过初步测试
示例链接 示例点击下载
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
namespace CS_TEST
{
* 该部分包括:
* 1.站点到服务器
* 2.客户端到服务器
*/
public class FCS_Protocols
{
public class FCSNetSteam
{
public FCSNetSteam(NetworkStream ns)
{
State = RCErrors.NORMAL;
nstem = ns;
}
public int State;
public NetworkStream nstem;
}
/// <summary>
/// 收发消息
/// </summary>
public struct RCErrors
{
public const int NORMAL = 0;//正常
public const int NS_WRITE_LOCKED = 1;//当前流锁定中,禁止写操作
public const int PACKAGE_LEN_ERR = 2;//接收到的包长度不正确。
public const int PACKAGE_INDEX_ERR = 3;//当前分包序列号错误
public const int PACKAGE_READ_ERR = 4;//读取分包内容失败
public const int NS_NORESPONS = 5;//网络流读取无响应,包括 读取无字节 和 未读取到标识符两种情况。
public const int CRC16_ERR = 6;//CRC校验失败
}
//======================字段==========================
/// <summary>
/// AESKey
/// </summary>
public static byte[] AesKeys = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 };
/// <summary>
/// 流起始标识
/// </summary>
private byte[] HeadBegin = Encoding.UTF8.GetBytes("FCCP");
/// <summary>
/// 发送报文字节计数,不包含Header 20字节
/// </summary>
public long SendCtr;
/// <summary>
/// 接收报文字节计数,不包含Header 20字节
/// </summary>
public long ReceiveCtr;
/// <summary>
/// 消息事件
/// </summary>
/// <param name="ERRID">消息ID</param>
/// <param name="ErrDesc">消息补充描述</param>
public delegate void RC_MessageHandle(int ERRID, string ErrDesc);
public event RC_MessageHandle RC_MessageEvent;
/// <summary>
/// 帧流水号
/// </summary>
public int PackagesCtr = 0;
//======================工具方法==========================
/// <summary>
/// 检查流是否争用
/// </summary>
/// <param name="ns">网络流实例</param>
/// <returns></returns>
private int CheckFCSNsTimeOut(FCSNetSteam ns)
{
int delay = 0;
while (ns.State == RCErrors.NS_WRITE_LOCKED)
{
if (delay >= 1000)
{
RC_MessageEvent?.Invoke(RCErrors.NS_WRITE_LOCKED, "流写操作未结束");
return RCErrors.NS_WRITE_LOCKED;
}
delay++;
Thread.Sleep(1);
}
return RCErrors.NORMAL;
}
/// <summary>
/// CRC16校验码计算
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public byte[] AddCrc16(byte[] data)
{
//常数
int Const = 0xa001;
//设置CRC寄存器初始值
int crc = 0xffff;
for (int i = 0; i < data.Length; i++)
{
//CRC与命令字节异或
crc ^= data[i];
for (int j = 0; j < 8; j++)
{
if ((int)(crc & 0x1) == 1) //二进制末尾是1,进行右移和常数异或运算
{
crc >>= 1;
crc ^= Const;
}
else //二进制末尾为0,仅仅进行右移运算
{
crc >>= 1;
}
}
}
byte[] result = new byte[data.Length + 2];
for (int i = 0; i < data.Length; i++)
{
result[i] = data[i];
}
result[result.Length - 2] = (byte)(crc & 0xFF);
result[result.Length - 1] = (byte)(crc >> 8);
return result;
}
/// <summary>
/// 解密
/// </summary>
/// <param name="buffer"></param>
/// <returns></returns>
private byte[] Decrypt(byte[] buffer)
{
byte[] buffer_ = AESTools.Decrypt(AesKeys, buffer);
return buffer_;
}
/// <summary>
/// 加密
/// </summary>
/// <param name="buffer"></param>
/// <returns></returns>
private byte[] Encrypt(byte[] buffer)
{
byte[] buffer_ = AESTools.Encrypt(AesKeys, buffer);
return buffer_;
}
//---------------------------------------收发方法---------------------------
/// <summary>
/// 字符串发送
/// </summary>
/// <param name="ns">指定流</param>
/// <param name="bufferStr">报文</param>
public int SendFCSBuffer(FCSNetSteam ns, string bufferStr, int PackageSize)
{
int err = CheckFCSNsTimeOut(ns);
if (err == RCErrors.NS_WRITE_LOCKED)
{
return RCErrors.NS_WRITE_LOCKED;
}
ns.State = RCErrors.NS_WRITE_LOCKED;
byte[] bs0 = Encoding.UTF8.GetBytes(bufferStr);
bs0 = Encrypt(bs0);
SendBufferPackage(ns, bs0, PackageSize);
ns.State = RCErrors.NORMAL;
PackagesCtr++;
return RCErrors.NORMAL;
}
/// <summary>
/// 字节序列发送,用于文件类传输,文件的读存,用内存缓存后,与收发异步完成
/// </summary>
/// <param name="ns"></param>
/// <param name="Bs"></param>
/// <param name="PackageSize"></param>
/// <returns></returns>
public int SendFCSBs(FCSNetSteam ns, byte[] Bs, int PackageSize)
{
int err = CheckFCSNsTimeOut(ns);
if (err == RCErrors.NS_WRITE_LOCKED)
{
return RCErrors.NS_WRITE_LOCKED;
}
ns.State = RCErrors.NS_WRITE_LOCKED;
SendBufferPackage(ns, Bs, PackageSize);
ns.State = RCErrors.NORMAL;
PackagesCtr++;
return RCErrors.NORMAL;
}
/// <summary>
/// 发送分包
/// </summary>
/// <param name="ns">指定流实例</param>
/// <param name="bs">内容字节</param>
/// <param name="PLen">分包长度</param>
private void SendBufferPackage(FCSNetSteam ns, byte[] bs, int PLen)
{
double r = Math.IEEERemainder(bs.Length, PLen);
byte[] bsTotalLen = BitConverter.GetBytes(bs.Length);
byte Pctr = 0;
if (r != 0)
{
Pctr = (byte)(bs.Length / PLen + 1);
}
else
{
Pctr = (byte)(bs.Length / PLen);
}
if (Pctr != 0)
{
for (byte i = 0; i < Pctr; i++)
{
//从高位到低位:1,2,3,4字节为 帧流水号;5字节为分包序号;6字节为分包总数;7,8,9,10字节为当前包长度;11,12,13,14 为总字节长度;15,16为前14位CRC校验码
byte[] IDs = BitConverter.GetBytes(PackagesCtr);
byte Index = (byte)(i + 1);
byte PTotal = Pctr;
byte[] bs0;
byte[] bsw;
byte[] bsLen;
byte[] Header = new byte[14];
if (i == Pctr - 1)
{
int len = bs.Length - PLen * i;
bs0 = new byte[len];
Array.Copy(bs, PLen * i, bs0, 0, len);
bsLen = BitConverter.GetBytes(len);
}
else
{
bs0 = new byte[PLen];
Array.Copy(bs, PLen * i, bs0, 0, PLen);
bsLen = BitConverter.GetBytes(PLen);
}
bsw = new byte[bs0.Length + Header.Length+2];
//-组装帧头:
//1,2,3,4字节为 帧流水号;5字节为分包序号;6字节为分包总数;7,8,9,10字节为当前包长度;11,12,13,14 为总字节长度;15,16为前14位CRC校验码
Array.Copy(IDs, Header, IDs.Length);//ID
Header[4] = Index;//Index
Header[5] = PTotal;//Total
Array.Copy(bsLen, 0, Header, 6, bsLen.Length);//Len
Array.Copy(bsTotalLen, 0, Header, 10, bsTotalLen.Length);//TotalLen
Header = AddCrc16(Header);//Crc16
Array.Copy(Header, bsw, Header.Length);
Array.Copy(bs0, 0, bsw, Header.Length, bs0.Length) ;
byte[] bs_Final = new byte[bsw.Length + HeadBegin.Length];//起始符
Array.Copy(HeadBegin, bs_Final, HeadBegin.Length);
Array.Copy(bsw, 0,bs_Final,HeadBegin.Length,bsw.Length);
ns.nstem.Write(bs_Final, 0, bs_Final.Length);
SendCtr += bs0.Length;
}
}
}
/// <summary>
/// 从网络流里诊断起始符位置,防止云服务器莫名其妙粘连字节
/// </summary>
/// <param name="ns">指定流实例</param>
/// <returns></returns>
private bool CheckHeaderBegin(FCSNetSteam ns)
{
int k = 0;
byte[] bs = new byte[1];
int b = ns.nstem.Read(bs, 0, bs.Length);
while (b != -1)
{
if (bs[0] != HeadBegin[k])
{
}
else
{
k++;
if (k == HeadBegin.Length)
{
return true;
}
}
b = ns.nstem.Read(bs, 0, bs.Length);
}
return false;
}
/// <summary>
/// 接收报文
/// </summary>
/// <param name="ns">指定流</param>
public string ReceiveFCSBuffer(FCSNetSteam ns, int BufferSize)
{
byte[] bsR = new byte[0];
int offset = 0;
byte[] FirsPackage = ReceiveBufferPackage(ns);
if (FirsPackage.Length!=0)
{
byte[] IDs = new byte[4];
Array.Copy(FirsPackage, IDs, 4);
byte Index = FirsPackage[4];
byte PTotal = FirsPackage[5];
int PLen = BitConverter.ToInt32(FirsPackage, 6);
int PTotalLen = BitConverter.ToInt32(FirsPackage, 10);
bsR = new byte[PTotalLen];
Array.Copy(FirsPackage,14, bsR,0,PLen);
offset = PLen;
while (Index != PTotal)
{
Index++;
byte[] Package = ReceiveBufferPackage(ns);
if (Package.Length == 0)
{
RC_MessageEvent?.Invoke(RCErrors.PACKAGE_READ_ERR, "包读取失败: " + Index + "/" + PTotal);
return null;
}
int _Index = Package[4];
if (_Index != Index)
{
RC_MessageEvent?.Invoke(RCErrors.PACKAGE_INDEX_ERR, "包序列错误,与预期值不一致: " + Index + "?" + _Index);
return null;
}
int _PTotal = Package[5];
if (_PTotal != PTotal)
{
RC_MessageEvent?.Invoke(RCErrors.PACKAGE_LEN_ERR, "数据总长不一致: " + PTotal + "?" + _PTotal);
return null;
}
PLen = BitConverter.ToInt32(Package, 6);
Array.Copy(Package,14,bsR, offset, PLen);
offset += PLen;
}
}
if (bsR.Length != 0)
{
bsR = Decrypt(bsR);//解密
string s = UTF8Encoding.UTF8.GetString(bsR);
return s;
}
return null;
}
/// <summary>
/// 字节序列接收,用于文件类传输,文件的读存,用内存缓存后,与收发异步完成
/// </summary>
/// <param name="ns"></param>
/// <param name="BufferSize"></param>
/// <returns></returns>
public byte[] ReceiveFCSBs(FCSNetSteam ns, int BufferSize)
{
byte[] bsR = new byte[0];
int offset = 0;
byte[] FirsPackage = ReceiveBufferPackage(ns);
if (FirsPackage.Length != 0)
{
byte[] IDs = new byte[4];
Array.Copy(FirsPackage, IDs, 4);
byte Index = FirsPackage[4];
byte PTotal = FirsPackage[5];
int PLen = BitConverter.ToInt32(FirsPackage, 6);
int PTotalLen = BitConverter.ToInt32(FirsPackage, 10);
bsR = new byte[PTotalLen];
Array.Copy(FirsPackage, 14, bsR, 0, PLen);
offset = PLen;
while (Index != PTotal)
{
Index++;
byte[] Package = ReceiveBufferPackage(ns);
if (Package.Length == 0)
{
RC_MessageEvent?.Invoke(RCErrors.PACKAGE_READ_ERR, "包读取失败: " + Index + "/" + PTotal);
return null;
}
int _Index = Package[4];
if (_Index != Index)
{
RC_MessageEvent?.Invoke(RCErrors.PACKAGE_INDEX_ERR, "包序列错误,与预期值不一致: " + Index + "?" + _Index);
return null;
}
int _PTotal = Package[5];
if (_PTotal != PTotal)
{
RC_MessageEvent?.Invoke(RCErrors.PACKAGE_LEN_ERR, "数据总长不一致: " + PTotal + "?" + _PTotal);
return null;
}
PLen = BitConverter.ToInt32(Package, 6);
Array.Copy(Package, 14, bsR, offset, PLen);
offset += PLen;
}
}
if (bsR.Length != 0)
{
return bsR;
}
return null;
}
/// <summary>
/// 从网络流里提取包
/// </summary>
/// <param name="ns"></param>
/// <returns>返回包,长度为0时 存在ERR,可通过消息事件获取ERR信息</returns>
private byte[] ReceiveBufferPackage(FCSNetSteam ns)
{
byte[] bsR = new byte[0];
if (!CheckHeaderBegin(ns))
{
return bsR;
}
byte[] bsHeader = new byte[16];
int len = ns.nstem.Read(bsHeader, 0, bsHeader.Length);
if (len == bsHeader.Length)
{
byte[] IDs = new byte[4];
Array.Copy(bsHeader, IDs, 4);
byte Index = bsHeader[4];
byte PTotal = bsHeader[5];
if (Index>PTotal)
{
RC_MessageEvent?.Invoke(RCErrors.PACKAGE_INDEX_ERR, "包序列错误,大于总包数: " + Index + ">?" + PTotal);
return bsR;
}
int PLen = BitConverter.ToInt32(bsHeader, 6);
int PTotalLen = BitConverter.ToInt32(bsHeader, 10);
byte CRC_H = bsHeader[14];
byte CRC_L = bsHeader[15];
if (!CheckCRC(bsHeader))
{
RC_MessageEvent?.Invoke(RCErrors.CRC16_ERR, "CRC16校验失败: " + CRC_H + "-" + CRC_L + " 包序列 : " + Index + "/" + PTotal);
return bsR;
}
bsR = new byte[16 + PLen];
byte[] bs = new byte[PLen];
ns.nstem.Read(bs, 0, PLen);
Array.Copy(bsHeader, bsR, 16);
Array.Copy(bs,0, bsR,14, bs.Length);
ReceiveCtr += bs.Length;
}
return bsR;
}
/// <summary>
/// CRC校验
/// </summary>
/// <param name="bs">被校验的字节组,既得的校验码在最后两位H->L</param>
/// <returns></returns>
private bool CheckCRC(byte[] bs)
{
byte[] bs0 = new byte[bs.Length - 2];
Array.Copy(bs, bs0, bs0.Length);
bs0 = AddCrc16(bs0);
if (bs0[15]==bs[15]& bs0[14] == bs[14])//为提升效率 长度写为常数,帧头部分发生变化时须手动修改。
{
return true;
}
return false;
}
}
}