客户端用SocketAsyncEventArgs 异步接受数据,有时会出现接受的数据不对 异步接受数据C#应答心跳有时解析错误粘包 解决方案 » 免费领取超大流量手机卡,每月29元包185G流量+100分钟通话, 中国电信官方发货 const int nRmainBufSize = 5120; const int nRcvBufSize = 10240; static byte[] byRemainBuf = new byte[nRmainBufSize]; static int nTotalRemainLen = 0; static object LockObj = new object(); private static void RecvDistribute(byte[] byRecv, int nLen) { lock (LockObj) { if (null == byRecv || 0 >= nLen) return; List<RspResult> byRspPacketList = new List<RspResult>(); ParseRecvData(byRecv, nLen, ref byRemainBuf, ref nTotalRemainLen, ref byRspPacketList); if (null == byRspPacketList || byRspPacketList.Count == 0) return; //处理 byRspPacketList,这个是解析到的单一包list,一次byRecv可能有多个包,还可能会粘包, byRemainBuf不足一个包的部分,和下面长度一起用作,包合并 nTotalRemainLen:不足那部分的长度 每次解析前合并上次收的 不足一个包的部分 }} RecvDistribute 就是ReceiveMSGEvent 的委托函数 信息不足,ParseRecvData没有具体代码,现猜测之:1、未被处理的剩余数据被保存到byRemainBuf(长度为nTotalRemainLen) ,这(两)个参数是静态的,多个Socket连接(句柄)收数据时都会写入数据到byRemainBuf2、接收数据失败断开连接重连没有重置byRemainBuf和nTotalRemainLen3、数据契约定义不够严谨,可能造成错误的解析4、nRcvBufSize 10K,未处理完毕的数据最多5K,新数据可能超过5K大小,可能造成数据被丢弃而为处理6、ParseRecvData等函数中可能存在解析错误7、代码中无法看出是TCP还是UDP 非常感谢,1.是一个socket连接的2.我收到的e.BytesTransferred经常是0,但连接还是好的,重连我确认没考虑3.不知道4.都改为10K还是同样问题5.ParseRecvData等函数中解析我查查,但特别说的是:ParseRecvData中解析到是心跳会socket:send一个应答,然后再解析,会不会是这个?6.TCP的 public static void ParseRecvData(byte[] byRevData, int nLen,byte[] byRemainBuf, int nTotalRemainLen,ref List<RspResult> byPacketList) { try { if (nLen <= 0) return; if (nLen < 4) { Array.Copy(byRevData, 0, byRemainBuf, nTotalRemainLen, nLen); nTotalRemainLen += nLen; return; } //每次合并不足的部分 int nAllLen = nTotalRemainLen + nLen; byte[] byAllData = new byte[nAllLen]; Array.Copy(byRemainBuf, 0, byAllData, 0, nTotalRemainLen); Array.Copy(byRevData, 0, byAllData, nTotalRemainLen, nLen); Array.Clear(byRemainBuf, 0, byRemainBuf.Length);//清空 nTotalRemainLen = 0;//清空 //用byAllData int nCurLen = 0; while (nCurLen >= 0 && nCurLen < nAllLen) { // FTDHeader byte[] byHeader = new byte[4]; FTDHeader Header = new FTDHeader(); Array.Copy(byAllData, nCurLen, byHeader, 0, 4); Header.Decode(byHeader); int nPacketLen = 4 + Header.extHeaderLen + Header.contentLen;//一个报文长 if (Header.type != 0x01)//自己协议规定的 { nCurLen += nPacketLen; ClientLoger.Print("Header.type != 0x01");//经常出现这个解析错误,此时nPacketLen也是错误的,会跳过一大截,丢几个包的! continue; } if (nPacketLen == 6) { byte[] byhb = new byte[2]; Array.Copy(byAllData, nCurLen + 4, byhb, 0, 2); if (byhb[0] == 0x04 && byhb[1] == 0 && Header.contentLen == 0) { IOCPSocket.SendReceive(BaseFTDPacketTool.CreateHeartBeatPacket()); ClientLoger.Print("收到心跳包---------"); nCurLen += nPacketLen; continue; } } if (Header.contentLen == 0) { ClientLoger.Print("Header.contentLen == 0"); nCurLen += nPacketLen; continue; } int nRemainLen = nAllLen - nCurLen; if (nRemainLen < nPacketLen)//剩下长度小于包长度 { if (nRemainLen > byRemainBuf.Length) { ClientLoger.Print("nRemainLen >= byRemainBuf.Length"); break; } Array.Copy(byAllData, nCurLen, byRemainBuf, nTotalRemainLen, nRemainLen); nTotalRemainLen += nRemainLen;//加到剩余字节总长 nCurLen += nRemainLen; continue; } // FTDExtHeader // FTDCHeader byte[] bRecvDCHeader = new byte[20]; FTDCHeader DCHeader = new FTDCHeader(); Array.Copy(byAllData, nCurLen + 4 + Header.extHeaderLen, bRecvDCHeader, 0, 20); DCHeader.Decode(bRecvDCHeader); if (DCHeader.version != 1) { ClientLoger.Print("DCHeader.version != 1"); nCurLen += nPacketLen; continue; } if (DCHeader.seqSeries != 0x0) { ClientLoger.Print("DCHeader.seqSeries != 0x0"); nCurLen += nPacketLen; continue; } if (DCHeader.fieldContentLen == 0) { ClientLoger.Print("DCHeader.fieldContentLen == 0"); nCurLen += nPacketLen; continue; } byte[] PacketData = new byte[nPacketLen]; Array.Copy(byAllData, nCurLen, PacketData, 0, nPacketLen); RspResult recvftd = new RspResult(); recvftd.nTid = (FTDType)DCHeader.TID; recvftd.byRecvData = PacketData; byPacketList.Add(recvftd); nCurLen += nPacketLen; } } catch (System.Exception ex) { ClientLoger.Print(" ex.Message + ex.StackTrace); } } 1、Array.Copy(byRevData, 0, byRemainBuf, nTotalRemainLen, nLen); 如果nTotalRemainLen + nLen > nRmainBufSize 会异常,因为外层处理了异常而允许继续运行会丢失数据2、if (Header.type != 0x01)//自己协议规定的 这里的处理是不明智的,假如数据已经乱了,那么这样处理可能出现丢失N多数据包,例如数据为:01 [01 02 00 00 04 00 ][01 02 14 00 ....],其中框框中的是正确的数据报,顺序为[type extlength contentlength低位 contentlength高为 数据段]你可以推测出这个数据里会丢失多少数据包。应该发现数据包不合法去判断下1字节开始的数据是否合法,只能每字节丢弃,不能根据不可靠的packetlen来丢弃3、if (nPacketLen == 6) 符合此条件但不是所要处理心跳包怎么办?代码中似乎往下执行,但执行到Array.Copy(byAllData, nCurLen + 4 + Header.extHeaderLen, bRecvDCHeader, 0, 20); 异常了,于是剩下的数据全部被丢弃了。4、同上条,Array.Copy(byAllData, nCurLen + 4 + Header.extHeaderLen, bRecvDCHeader, 0, 20); 当数据包的数据不足20字节(nPacketLen + < 20 + 4 + Header.extHeaderLen),产生异常,剩下的数据全部被丢弃5、建立处理顺序:A、检查数据包头,不是合法包头则后移一字节继续检查B、数据包头合法的情况下检查数据长度是否满足,若不满足还需要判断是否是数据乱了,若是数据乱了则需要找到下一个合法数据包头,若不是则确保数据包是准确的数据包C、取出校验合法的数据包进行业务处理D、协议契约定义建议:包头(若干固定字节如FF FF)、数据内容区、包尾(固定字节或者CRC校验) Quote: 引用 10 楼 guxingfeiyu 的回复: static void OnReceive(object sender, SocketAsyncEventArgs e) { try { int rec = e.BytesTransferred; if (e.SocketError == SocketError.Success && rec > 0) { ConnectInfo info = e.UserToken as ConnectInfo; if (info == null) { return; } Socket client = sender as Socket; if (client == null || !client.Connected) { return; } byte[] datas = e.Buffer; if (null != datas) { ReceiveMSGEvent(datas, e.BytesTransferred); client.ReceiveAsync(e); return; }}这是我接受的部分,您看下是不是有什么问题?,循环接受的 11楼暂时看不出问题,你先把ParseRecvData改好再看吧。 你的数据发送端,不管一个所谓包的数据是2k还是100k得数据,总之只能在一个 Send 命令(或者 BeginSend,或者 Write,或者 BeginWrite)中发送,不能分成两三个Send命令。可以用比较通用的消息分割方式,而不需要看什么“length”。例如通常可以用短链接方式(例如http下载方式)获取一个分割内容,例如“guid+服务名称+启动时间”产生的几十个字节的分隔符,然后每一个消息的结束位置都包括这个分割内容。你的接收到端每当收到数据包,判断结尾是否是它。如果是它才解析消息(可能有不止一条)。缓冲区不要太小。10k太小。500k也丝毫不算大。其它的都比较乱,都是自己代码写过度复杂了、产生的干扰。 对于tcp长连接来说,其实根本不需要心跳。实际上通常只有短链接才需要心跳。心跳是逻辑意义上的,不是指基础的通讯。不要滥用心跳。 楼主,没理解这个数据包缓冲区呀,还有你把一个包分成三个Send 出去,这三个之间最好要Thread.Sleep(1)的间隔,这个我试过在高并发量情况下。。你想按顺序发送,但有时接收的可能是第二个包或第三个包先到达会造成包的序序错乱。还有你面接收数据包方法太复杂了。看看这个之前给人写的/// <summary> /// 字节缓冲器 /// </summary> public class ByteQueue { private List<byte> m_buffer = new List<byte>(); public bool Find() { if (m_buffer.Count == 0) return false; int HeadIndex = m_buffer.FindIndex(o => o == 0xAA); if (HeadIndex == -1) { m_buffer.Clear(); return false; //没找到AA } else if (HeadIndex != 0) //不为开头移掉之前的字节 { if (HeadIndex > 1) m_buffer.RemoveRange(0, HeadIndex); } int length= GetLength(); if (m_buffer.Count <length) { return false; } int TailIndex = m_buffer.FindIndex(o => o == 0x55); //查找55的位置 if (TailIndex == -1) { //这一步为防止连发一个AA开头的包后,没发55,而又发了一个AA int head = m_buffer.FindLastIndex(o => o == 0xAA); if (head > -1) { m_buffer.RemoveRange(0, head); } return false; } else if (TailIndex + 1 != length) //计算包尾是否与包长度相等 { m_buffer.RemoveRange(0, TailIndex); return false; } return true; } /// <summary> /// 命令类型 /// </summary> /// <returns></returns> public byte Cmd() { if (m_buffer.Count >= 2) { return m_buffer[1]; } return 0; } /// <summary> /// 序号 /// </summary> /// <returns></returns> public byte Number() { if (m_buffer.Count >= 3) { return m_buffer[2]; } return 0; } /// <summary> /// 包长度 /// </summary> /// <returns></returns> public int GetLength() { int len = 5;//AA 命令类型 序号 校验和 55 if (m_buffer.Count >= 3) { switch (m_buffer[2]) //第三字节为序号 { case 0x00: //序号 return len + 16; case 0x01: //序号 return len + 10; case 0x02: //序号 return len + 12; } } return 0; } /// <summary> /// 提取数据 /// </summary> public void Dequeue(byte[] buffer, int offset,int size) { m_buffer.CopyTo(0,buffer,offset,size); m_buffer.RemoveRange(0, size); } /// <summary> /// 队列数据 /// </summary> /// <param name="buffer"></param> public void Enqueue(byte[] buffer) { m_buffer.AddRange(buffer); } }Demoprivate ByteQueue queue = new ByteQueue(); private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e) { int len = serialPort1.BytesToRead; if (len > 0) { byte[] temp = new byte[len]; serialPort1.Read(temp, 0, len); queue.Enqueue(temp); while (queue.Find()) //while可处理同时接收到多个AA ... 55 ,AA...55的包 { int length = queue.GetLength(); byte[] readBuffer = new byte[len]; queue.Dequeue(readBuffer, 0, length); OnReceiveData(readBuffer); //<这里自己写一个委托吧就OK了 } } }原地址http://bbs.csdn.net/topics/390316188 对于长连接,心跳的主要意义是检查SOCKET异常断开,能较快识别“死连接”并释放“采用基于连接的协议通讯的双方中的一方由于停电、当机、崩溃等原因没有进行或者完成断开连接的过程那么另一方就不知道连接是否断开了。对于基于连接的协议,一旦在应用层采用了长连接绝大多少情况下都需要心跳信号,除非程序不用长时间运行,不在乎死连接的性能损失”http://baike.baidu.com/link?url=eYNlpyyNooHFeT3LBKqL9mu4XzOTEibr76ywww0Bfera7zDIgyQohhHGz_nWAFpNXZG864U1yQV_jNbUJa9wNK对于短连接,心跳的主要意义是维护连接 有关视频图片融合问题, 如何用C#做截图,像QQ截图那样的 .cs 与 .resx的关系? 谁有.net 非接触IC卡应用开发代码? SQL Server不存在或访问被拒绝 A机器如何远程登录B机器,并更改B机器的登录密码? 问一个很菜的问题 如何解决调用Invalidate()时出现的界面闪烁问题? 关于类的用法,马上结帐 这个窗口怎麽弹不出来呢?? C#中如何动态定义变量 winform chart控件鼠标悬停显示Y值
const int nRcvBufSize = 10240;
static byte[] byRemainBuf = new byte[nRmainBufSize];
static int nTotalRemainLen = 0;
static object LockObj = new object();
private static void RecvDistribute(byte[] byRecv, int nLen)
{
lock (LockObj)
{
if (null == byRecv || 0 >= nLen)
return; List<RspResult> byRspPacketList = new List<RspResult>();
ParseRecvData(byRecv, nLen, ref byRemainBuf, ref nTotalRemainLen, ref byRspPacketList);
if (null == byRspPacketList || byRspPacketList.Count == 0)
return;
//处理
byRspPacketList,这个是解析到的单一包list,一次byRecv可能有多个包,还可能会粘包,
byRemainBuf不足一个包的部分,和下面长度一起用作,包合并
nTotalRemainLen:不足那部分的长度
每次解析前合并上次收的 不足一个包的部分
}
}
1、未被处理的剩余数据被保存到byRemainBuf(长度为nTotalRemainLen) ,这(两)个参数是静态的,多个Socket连接(句柄)收数据时都会写入数据到byRemainBuf
2、接收数据失败断开连接重连没有重置byRemainBuf和nTotalRemainLen
3、数据契约定义不够严谨,可能造成错误的解析
4、nRcvBufSize 10K,未处理完毕的数据最多5K,新数据可能超过5K大小,可能造成数据被丢弃而为处理
6、ParseRecvData等函数中可能存在解析错误
7、代码中无法看出是TCP还是UDP
非常感谢,
1.是一个socket连接的
2.我收到的e.BytesTransferred经常是0,但连接还是好的,重连我确认没考虑3.不知道
4.都改为10K还是同样问题
5.ParseRecvData等函数中解析我查查,但特别说的是:ParseRecvData中解析到是心跳会socket:send一个应答,然后再解析,会不会是这个?
6.TCP的
{
try
{
if (nLen <= 0)
return;
if (nLen < 4)
{
Array.Copy(byRevData, 0, byRemainBuf, nTotalRemainLen, nLen);
nTotalRemainLen += nLen;
return;
} //每次合并不足的部分
int nAllLen = nTotalRemainLen + nLen;
byte[] byAllData = new byte[nAllLen];
Array.Copy(byRemainBuf, 0, byAllData, 0, nTotalRemainLen);
Array.Copy(byRevData, 0, byAllData, nTotalRemainLen, nLen); Array.Clear(byRemainBuf, 0, byRemainBuf.Length);//清空
nTotalRemainLen = 0;//清空 //用byAllData
int nCurLen = 0;
while (nCurLen >= 0 && nCurLen < nAllLen)
{
// FTDHeader
byte[] byHeader = new byte[4];
FTDHeader Header = new FTDHeader();
Array.Copy(byAllData, nCurLen, byHeader, 0, 4);
Header.Decode(byHeader); int nPacketLen = 4 + Header.extHeaderLen + Header.contentLen;//一个报文长
if (Header.type != 0x01)//自己协议规定的
{
nCurLen += nPacketLen;
ClientLoger.Print("Header.type != 0x01");//经常出现这个解析错误,此时nPacketLen也是错误的,会跳过一大截,丢几个包的!
continue;
} if (nPacketLen == 6)
{
byte[] byhb = new byte[2];
Array.Copy(byAllData, nCurLen + 4, byhb, 0, 2);
if (byhb[0] == 0x04 && byhb[1] == 0 && Header.contentLen == 0)
{
IOCPSocket.SendReceive(BaseFTDPacketTool.CreateHeartBeatPacket());
ClientLoger.Print("收到心跳包---------");
nCurLen += nPacketLen;
continue;
}
} if (Header.contentLen == 0)
{
ClientLoger.Print("Header.contentLen == 0");
nCurLen += nPacketLen;
continue;
} int nRemainLen = nAllLen - nCurLen;
if (nRemainLen < nPacketLen)//剩下长度小于包长度
{
if (nRemainLen > byRemainBuf.Length)
{
ClientLoger.Print("nRemainLen >= byRemainBuf.Length");
break;
} Array.Copy(byAllData, nCurLen, byRemainBuf, nTotalRemainLen, nRemainLen);
nTotalRemainLen += nRemainLen;//加到剩余字节总长
nCurLen += nRemainLen;
continue;
}
// FTDExtHeader // FTDCHeader
byte[] bRecvDCHeader = new byte[20];
FTDCHeader DCHeader = new FTDCHeader();
Array.Copy(byAllData, nCurLen + 4 + Header.extHeaderLen, bRecvDCHeader, 0, 20);
DCHeader.Decode(bRecvDCHeader);
if (DCHeader.version != 1)
{
ClientLoger.Print("DCHeader.version != 1");
nCurLen += nPacketLen;
continue;
}
if (DCHeader.seqSeries != 0x0)
{
ClientLoger.Print("DCHeader.seqSeries != 0x0");
nCurLen += nPacketLen;
continue;
}
if (DCHeader.fieldContentLen == 0)
{
ClientLoger.Print("DCHeader.fieldContentLen == 0");
nCurLen += nPacketLen;
continue;
}
byte[] PacketData = new byte[nPacketLen];
Array.Copy(byAllData, nCurLen, PacketData, 0, nPacketLen); RspResult recvftd = new RspResult();
recvftd.nTid = (FTDType)DCHeader.TID;
recvftd.byRecvData = PacketData;
byPacketList.Add(recvftd); nCurLen += nPacketLen;
}
}
catch (System.Exception ex)
{
ClientLoger.Print(" ex.Message + ex.StackTrace);
}
}
如果nTotalRemainLen + nLen > nRmainBufSize 会异常,因为外层处理了异常而允许继续运行会丢失数据
2、if (Header.type != 0x01)//自己协议规定的
这里的处理是不明智的,假如数据已经乱了,那么这样处理可能出现丢失N多数据包,例如数据为:01 [01 02 00 00 04 00 ][01 02 14 00 ....],其中框框中的是正确的数据报,顺序为[type extlength contentlength低位 contentlength高为 数据段]你可以推测出这个数据里会丢失多少数据包。
应该发现数据包不合法去判断下1字节开始的数据是否合法,只能每字节丢弃,不能根据不可靠的packetlen来丢弃
3、if (nPacketLen == 6)
符合此条件但不是所要处理心跳包怎么办?代码中似乎往下执行,但执行到Array.Copy(byAllData, nCurLen + 4 + Header.extHeaderLen, bRecvDCHeader, 0, 20); 异常了,于是剩下的数据全部被丢弃了。
4、同上条,Array.Copy(byAllData, nCurLen + 4 + Header.extHeaderLen, bRecvDCHeader, 0, 20); 当数据包的数据不足20字节(nPacketLen + < 20 + 4 + Header.extHeaderLen),产生异常,剩下的数据全部被丢弃
5、建立处理顺序:
A、检查数据包头,不是合法包头则后移一字节继续检查
B、数据包头合法的情况下检查数据长度是否满足,若不满足还需要判断是否是数据乱了,若是数据乱了则需要找到下一个合法数据包头,若不是则确保数据包是准确的数据包
C、取出校验合法的数据包进行业务处理
D、协议契约定义建议:包头(若干固定字节如FF FF)、数据内容区、包尾(固定字节或者CRC校验)
楼主,没理解这个数据包缓冲区呀,还有你把一个包分成三个Send 出去,这三个之间最好要Thread.Sleep(1)的间隔,这个我试过
在高并发量情况下。。你想按顺序发送,但有时接收的可能是第二个包或第三个包先到达会造成包的序序错乱。还有你面接收数据包方法太复杂了。看看这个之前给人写的/// <summary>
/// 字节缓冲器
/// </summary>
public class ByteQueue
{
private List<byte> m_buffer = new List<byte>();
public bool Find()
{
if (m_buffer.Count == 0)
return false;
int HeadIndex = m_buffer.FindIndex(o => o == 0xAA);
if (HeadIndex == -1)
{
m_buffer.Clear();
return false; //没找到AA
}
else if (HeadIndex != 0) //不为开头移掉之前的字节
{
if (HeadIndex > 1)
m_buffer.RemoveRange(0, HeadIndex);
}
int length= GetLength();
if (m_buffer.Count <length)
{
return false;
}
int TailIndex = m_buffer.FindIndex(o => o == 0x55); //查找55的位置
if (TailIndex == -1)
{
//这一步为防止连发一个AA开头的包后,没发55,而又发了一个AA
int head = m_buffer.FindLastIndex(o => o == 0xAA);
if (head > -1)
{
m_buffer.RemoveRange(0, head);
}
return false;
}
else if (TailIndex + 1 != length) //计算包尾是否与包长度相等
{
m_buffer.RemoveRange(0, TailIndex);
return false;
}
return true;
}
/// <summary>
/// 命令类型
/// </summary>
/// <returns></returns>
public byte Cmd()
{
if (m_buffer.Count >= 2)
{
return m_buffer[1];
}
return 0;
}
/// <summary>
/// 序号
/// </summary>
/// <returns></returns>
public byte Number()
{
if (m_buffer.Count >= 3)
{
return m_buffer[2];
}
return 0;
}
/// <summary>
/// 包长度
/// </summary>
/// <returns></returns>
public int GetLength()
{
int len = 5;//AA 命令类型 序号 校验和 55
if (m_buffer.Count >= 3)
{
switch (m_buffer[2]) //第三字节为序号
{
case 0x00: //序号
return len + 16;
case 0x01: //序号
return len + 10;
case 0x02: //序号
return len + 12;
}
}
return 0;
}
/// <summary>
/// 提取数据
/// </summary>
public void Dequeue(byte[] buffer, int offset,int size)
{
m_buffer.CopyTo(0,buffer,offset,size);
m_buffer.RemoveRange(0, size);
}
/// <summary>
/// 队列数据
/// </summary>
/// <param name="buffer"></param>
public void Enqueue(byte[] buffer)
{
m_buffer.AddRange(buffer);
}
}Demoprivate ByteQueue queue = new ByteQueue();
private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
int len = serialPort1.BytesToRead;
if (len > 0)
{
byte[] temp = new byte[len];
serialPort1.Read(temp, 0, len);
queue.Enqueue(temp);
while (queue.Find()) //while可处理同时接收到多个AA ... 55 ,AA...55的包
{
int length = queue.GetLength();
byte[] readBuffer = new byte[len];
queue.Dequeue(readBuffer, 0, length);
OnReceiveData(readBuffer); //<这里自己写一个委托吧就OK了
}
}
}原地址
http://bbs.csdn.net/topics/390316188
“采用基于连接的协议通讯的双方中的一方由于停电、当机、崩溃等原因没有进行或者完成断开连接的过程那么另一方就不知道连接是否断开了。对于基于连接的协议,一旦在应用层采用了长连接绝大多少情况下都需要心跳信号,除非程序不用长时间运行,不在乎死连接的性能损失”
http://baike.baidu.com/link?url=eYNlpyyNooHFeT3LBKqL9mu4XzOTEibr76ywww0Bfera7zDIgyQohhHGz_nWAFpNXZG864U1yQV_jNbUJa9wNK对于短连接,心跳的主要意义是维护连接