搜索
bottom↓
回复: 88

100元话费+500莫元咨询TCP 黏包服务器怎么处理的问题

  [复制链接]

出0入8汤圆

发表于 2017-1-12 16:10:06 | 显示全部楼层 |阅读模式
本帖最后由 lindabell 于 2017-1-12 16:42 编辑

事情是这样的,我做了一台设备可以通过app控制,也可以上报温湿度等信息的;使用透传的WIFI模块。
设备从关机到开机会发生很多状态变化,都会上传这些状态;通过串口发送到WIFI模块,然后到服务器。
在服务器接收那边就会出现黏包的现象,由于黏包服务器处理起来非常耗时,应该是3~4s的数据到了数据库看居然花了26s左右。

另外我数据的格式是这样的 55AA+MAC+len+CRC8,黏包就是多包数据被TCP封成一个包了。

希望做个服务器 (要专业做服务器的,不是专业的意见不接受)的坛友,给个意见这样的黏包服务器能不能处理,怎样处理?

注:我是做单片机软件的对服务器一点不懂,但是我需要的是专业的回答,另外回答的不错的;可能还会付费咨询更加详细的,报酬方面可谈。


修改:增加到200元话费,高手希望提示一下

阿莫论坛20周年了!感谢大家的支持与爱护!!

月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!

出0入0汤圆

发表于 2017-1-12 16:20:46 | 显示全部楼层
你这种应用场合用 UDP 较好,简单,好用,不出问题

出0入8汤圆

 楼主| 发表于 2017-1-12 16:23:06 | 显示全部楼层
88mw300 发表于 2017-1-12 16:20
你这种应用场合用 UDP 较好,简单,好用,不出问题

这是物联设备要保证稳定性,用UPD各种收不到数据,不同步更难处理

出0入0汤圆

发表于 2017-1-12 16:26:27 | 显示全部楼层
非要一直连着干嘛,发送过就断开。

出0入0汤圆

发表于 2017-1-12 16:28:56 | 显示全部楼层
没太看懂你的意思,既然有包头“”55AA“”,那么上位机可以用55AA分割数据,这样就能得到一个数组,然后每个数组就是一条了;另外找到粘包的真正原因,是wifi模块的原因还是上位机没处理好....

出95入100汤圆

发表于 2017-1-12 16:32:23 | 显示全部楼层
每个数据包加几个字节做个编号,由编号来区分行不行?

出0入4汤圆

发表于 2017-1-12 16:35:05 | 显示全部楼层
指令格式一定要规范一些,加指令头指令尾,数据用ascii码,接收到的数据先判断指令头指令尾,不符合的反馈错误,丢掉重发

出0入8汤圆

 楼主| 发表于 2017-1-12 16:35:08 | 显示全部楼层
huangrui 发表于 2017-1-12 16:26
非要一直连着干嘛,发送过就断开。

应该目前物联的都是TCP 长连接;短连接会造成这样的问题app按开机,设备等了很久才响应。

出0入22汤圆

发表于 2017-1-12 16:36:26 来自手机 | 显示全部楼层
参考一下ping包内容,里面有个16bit的流水号。每个包不一样。

出0入8汤圆

 楼主| 发表于 2017-1-12 16:37:59 | 显示全部楼层
补充一下,我有写一个测试用的上位机可放在阿里云win平台当服务器,一点问题都没有;问题是那只是测试用的;现在是做服务器的人告诉我做不了。

出40入42汤圆

发表于 2017-1-12 16:44:23 | 显示全部楼层
有数据长度,有检验码,不能把黏包拆开吗?

出0入8汤圆

 楼主| 发表于 2017-1-12 16:45:26 | 显示全部楼层
zxq6 发表于 2017-1-12 16:36
参考一下ping包内容,里面有个16bit的流水号。每个包不一样。

加流水号就能解决黏包的问题吗?
你还没有明白我想问的问题

出0入0汤圆

发表于 2017-1-12 16:45:44 | 显示全部楼层
数据做成类似Modbus这样的协议,在服务器端进行数据拆分就好了

出0入228汤圆

发表于 2017-1-12 16:47:31 | 显示全部楼层
感觉应该是服务器端解析的问题。

有头,有尾,有长度,还要花26S,也是牛B了。

出0入8汤圆

 楼主| 发表于 2017-1-12 16:47:58 | 显示全部楼层
落叶知秋 发表于 2017-1-12 16:44
有数据长度,有检验码,不能把黏包拆开吗?

我也是觉得能够处理的,但是做服务器的人告诉我,他咨询了很多做服务器的人得到的答案是很难处理,有可能会导致服务器挂掉。

出0入8汤圆

 楼主| 发表于 2017-1-12 16:49:35 | 显示全部楼层
Pupil 发表于 2017-1-12 16:45
数据做成类似Modbus这样的协议,在服务器端进行数据拆分就好了

产品已经在试产阶段了,大改不可能

出0入0汤圆

发表于 2017-1-12 16:55:35 | 显示全部楼层
楼主,TCP是运输层,不经过处理,当发送速度较快时的确会发生连包情况。
可以从发送端或者协议上优化。
1、发送端,关闭Nagle;
2、针对协议,做拆包优化,服务器易于拆包

出0入0汤圆

发表于 2017-1-12 16:10:07 | 显示全部楼层
好办,数据包定义成TLV(Type Len Value)格式就行

Type 用1字节,表示数据包类型
Len 用2字节,表示数据包长度
Value 就是实际的数据包内容
CRC 放到Value后面

写一个函数专门用来解TLV数据包,每解出一个数据包就调一个回调,如果解析出错就断开连接

出0入0汤圆

发表于 2017-1-12 16:57:42 | 显示全部楼层
如果串口收到的数据有具体含义,可以打成json包

出0入0汤圆

发表于 2017-1-12 16:58:01 | 显示全部楼层
补充下,tcp数据是有校验的,所以可以去掉你的crc校验,加快速度

出0入0汤圆

发表于 2017-1-12 16:59:13 | 显示全部楼层
tcp本来就是提供可靠地数据流,没有包的概念,可靠地做法是从字节流里自己分包。

出0入0汤圆

发表于 2017-1-12 17:00:14 | 显示全部楼层
这个就一个字节一个字节的较验呗,不对就错一个字节,直到一整串到CRC8都OK,则认为这是一包

出0入0汤圆

发表于 2017-1-12 17:01:58 | 显示全部楼层
一直连着服务器能接入的客户端数量不会太多吧,你这又是APP 又是 客户端的,数量不多多可以采用长连接,数量多了你还是考虑换换思路吧。
对应你的这个问题,简单点,包后加0x0d,0x0a试试

出40入42汤圆

发表于 2017-1-12 17:02:30 | 显示全部楼层
lindabell 发表于 2017-1-12 16:47
我也是觉得能够处理的,但是做服务器的人告诉我,他咨询了很多做服务器的人得到的答案是很难处理,有可能 ...

黏包问题是TCP长连接传输中经常会碰到的问题啊,基本都是靠自定义的协议来解析数据进行拆包的。

楼主熟手的话,自个儿写个Demo把黏包问题解决了,给那做服务器的哥们儿参考参考。

出0入0汤圆

发表于 2017-1-12 17:11:10 | 显示全部楼层
MQTT解决你的问题,TCP的粘包是不可控。
即使你发出是分开的,中间的各级路由器也可能才你重新粘上或拆开。

出0入0汤圆

发表于 2017-1-12 17:17:53 来自手机 | 显示全部楼层
mqtt也会粘,不过人家帮你处理得方便点了

出300入477汤圆

发表于 2017-1-12 17:20:18 来自手机 | 显示全部楼层
88mw300 发表于 2017-1-12 16:20
你这种应用场合用 UDP 较好,简单,好用,不出问题

对。这也是我想说的。
我们的GRM200系列模块常年工作在一个来回要两秒钟的GPRS网络下,全靠UDP才能做到。
这种网络你就是要准备着它必然会丢包的,这是事实。
你必须自己对付丢包的情况。
TCP天生的那种处理一点也不好。你发3个包,丢了第一个,那么对UDP你照样能收到后两个,而TCP你一个也收不到了,直到它重传第一个包成功为止!

出0入8汤圆

 楼主| 发表于 2017-1-12 17:47:09 来自手机 | 显示全部楼层
aozima 发表于 2017-1-12 17:11
MQTT解决你的问题,TCP的粘包是不可控。
即使你发出是分开的,中间的各级路由器也可能才你重新粘上或拆开。 ...

后面考虑mqtt,目前我用WiFi透传根本无法控啊

出0入8汤圆

 楼主| 发表于 2017-1-12 18:18:30 来自手机 | 显示全部楼层
xaper 发表于 2017-1-12 16:58
补充下,tcp数据是有校验的,所以可以去掉你的crc校验,加快速度

现在问题并不是快不快的问题

出200入2554汤圆

发表于 2017-1-12 18:44:17 | 显示全部楼层
数据域编码避开包头编码,能降低接收端负担,代价就是浪费部分带宽。

比如你包头 55AAH 偏偏出现在了数据域(如果传RAW格式尤其容易发生),就会解码解的很乱。

最快的避开方式就是直接用掉 1bit 来区分是否是包头,或者其他控制字

出0入0汤圆

发表于 2017-1-12 18:45:55 | 显示全部楼层
粘包处理是通信处理的基本问题,做服务器端的人员没有相关经验。

出0入0汤圆

发表于 2017-1-12 18:51:32 | 显示全部楼层
以前碰到过,有办法解决。
由于粘包不可避免,所以可以从协议上下手,使用包头或者包尾来从粘包数据中区分每一包的数据。但是不做特殊处理,粘包后包头包尾可能会和数据混淆,所以可以按照下面的方法来做,将包头包尾从数据中区分开来。
比如发送[11,bb,cc]的数据过去,可以将11这个十六进制拆分成两个字节[31,31],在服务器端再进行解析,31也就是1的ascii码。
对于所有数据来说,都可以是十六进制的,十六进制的字符范围也就是0-9,a-f,比如f0,12,ff,00这些,都可以拆分成两个字节。拆分之后,他们的范围就是30-39(0-9),61-66(a-f),而不在这些范围之内的就可以用作包头包尾了。
那到了这里就简单了,只要看到特定的包头(例如60),那么就认为后面的是新一包的数据,直到再碰到下一个包头,服务端只要用这个规则对数据进行解析即可,例如 60,31,32,61,66(包头,12,af)。
当然,11不仅仅可以拆成[31,31],也可以拆成[01,01],fa也可以拆成[0f,0a],也就是不按照ascii码来拆,那么所有数据范围就是00-0f,除了这些以外的都可以做包头包尾。

出60入0汤圆

发表于 2017-1-12 18:52:57 来自手机 | 显示全部楼层
参考netty对wire protocol处理

出0入0汤圆

发表于 2017-1-12 19:20:40 来自手机 | 显示全部楼层
很简单,tcp数据包每一个条第一个字节就是后面的长度,完成第一个包,再循环即可,tcp可靠流式传输

出0入0汤圆

发表于 2017-1-12 19:22:50 来自手机 | 显示全部楼层
很简单,tcp数据包每一个条第一个字节就是后面的长度,完成第一个包,再循环即可,tcp可靠流式传输

出0入0汤圆

发表于 2017-1-12 19:56:02 | 显示全部楼层
本帖最后由 tribear 于 2017-1-12 19:58 编辑

TCP粘包一般是2个或者以上的TCP包粘结在一起,一般不会出现数据帧不完整的情况所以,可以用递归调用的方法处理TCP的粘包问题.
比如说你封装一个解析TCP数据帧的函数DecodeTCPFrame(uint8_t *TCPData, int TCPLength)  (TCPData[]为缓冲数组,TCPLength为TCP包的字节),
通过对比你自定义协议帧中的Len长度和TCP的包长度就能判断是不是有粘包的情况,
如果有粘包的情况就再递归调用一遍DecodeTCPFrame(&TCPData[第二帧起始字节下标],TCPLength - 上一个帧的完整长度).
一直对比到TCPLength 和数据帧Len符合时跳出DecodeTCPFrame函数.

把上面解析到的数据帧存到一个结构体数组里,搞定~

出0入0汤圆

发表于 2017-1-12 20:04:10 | 显示全部楼层
怀疑不是模块自动帮忙组包上传造成的,仔细看看模块手册是不是有包间隔时间之类的参数。

出0入8汤圆

 楼主| 发表于 2017-1-12 20:33:47 来自手机 | 显示全部楼层
tribear 发表于 2017-1-12 19:56
TCP粘包一般是2个或者以上的TCP包粘结在一起,一般不会出现数据帧不完整的情况所以,可以用递归调用的方法处 ...

你是做服务器的吗?是做过类似的吗?

出0入0汤圆

发表于 2017-1-12 20:37:54 | 显示全部楼层
用Qt是这样处理粘包的,自定义数据包,收到的数据都存入缓冲区中,从缓冲区起始处解析数据,先找到包头,然后找到包头中表示包体大小的字段,根据包体大小找到包体数据。

出0入0汤圆

发表于 2017-1-12 20:45:01 来自手机 | 显示全部楼层
黏包问题是TCP长连接经常遇到的,可以从协议的去分解,或者用netty去打包,或用MQTT,你们做服务器的开发太菜了,他们就知道http这些处理好的,他们根本不知道tcp 粘包的问题,楼主可以换人了

出0入89汤圆

发表于 2017-1-12 22:48:58 来自手机 | 显示全部楼层
根据协议完全可以处理!服务外包吧!你们做服务的太次了

出0入0汤圆

发表于 2017-1-12 23:04:52 | 显示全部楼层
我也是做GPRS这方面的,不过是硬件部分,这种TCP黏包问题,一般都是依赖协议来区分的,我这边的硬件socket接收部分,也经常出现黏包问题,都是按协议一包一包解析的,
你服务器端解析要20多s,我只能呵呵....

出0入0汤圆

发表于 2017-1-12 23:24:09 | 显示全部楼层
以下内容来源于网络:

二、TCP协议简介   
TCP是一个面向连接的传输层协议,虽然TCP不属于ISO制定的协议集,但由于其在商业界和工业界的成功应用,它已成为事实上的网络标准,广泛应用于各种网络主机间的通信。   
作为一个面向连接的传输层协议,TCP的目标是为用户提供可靠的端到端连接,保证信息有序无误的传输。它除了提供基本的数据传输功能外,还为保证可靠性采用了数据编号、校验和计算、数据确认等一系列措施。它对传送的每个数据字节都进行编号,并请求接收方回传确认信息(ACK)。发送方如果在规定的时间内没有收到数据确认,就重传该数据。数据编号使接收方能够处理数据的失序和重复问题。数据误码问题通过在每个传输的数据段中增加校验和予以解决,接收方在接收到数据后检查校验和,若校验和有误,则丢弃该有误码的数据段,并要求发送方重传。流量控制也是保证可靠性的一个重要措施,若无流控,可能会因接收缓冲区溢出而丢失大量数据,导致许多重传,造成网络拥塞恶性循环。TCP采用可变窗口进行流量控制,由接收方控制发送方发送的数据量。   
TCP为用户提供了高可靠性的网络传输服务,但可靠性保障措施也影响了传输效率。因此,在实际工程应用中,只有关键数据的传输才采用TCP,而普通数据的传输一般采用高效率的UDP。   

三、粘包问题分析
TCP粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。   
出现粘包现象的原因是多方面的,它既可能由发送方造成,也可能由接收方造成。发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据。若连续几次发送的数据都很少,通常TCP会根据优化算法把这些数据合成一包后一次发送出去,这样接收方就收到了粘包数据。接收方引起的粘包是由于接收方用户进程不及时接收数据,从而导致粘包现象。这是因为接收方先把收到的数据放在系统接收缓冲区,用户进程从该缓冲区取数据,若下一包数据到达时前一包数据尚未被用户进程取走,则下一包数据放到系统接收缓冲区时就接到前一包数据之后,而用户进程根据预先设定的缓冲区大小从系统接收缓冲区取数据,这样就一次取到了多包数据(图1所示)。   
粘包情况有两种,一种是粘在一起的包都是完整的数据包(图1、图2所示),另一种情况是粘在一起的包有不完整的包(图3所示),此处假设用户接收缓冲区长度为m个字节。   
不是所有的粘包现象都需要处理,若传输的数据为不带结构的连续流数据(如文件传输),则不必把粘连的包分开(简称分包)。但在实际工程应用中,传输的数据一般为带结构的数据,这时就需要做分包处理。   
在处理定长结构数据的粘包问题时,分包算法比较简单;在处理不定长结构数据的粘包问题时,分包算法就比较复杂。特别是如图3所示的粘包情况,由于一包数据内容被分在了两个连续的接收包中,处理起来难度较大。实际工程应用中应尽量避免出现粘包现象。   

二 .什么时候需要考虑粘包问题?
1:如果利用tcp每次发送数据,就与对方建立连接,然后双方发送完一段数据后,就关闭连接,这样就不会出现粘包问题(因为只有一种包结构,类似于http协议)。关闭连接主要要双方都发送close连接(参考tcp关闭协议)。如:A需要发送一段字符串给B,那么A与B建立连接,然后发送双方都默认好的协议字符如"hello give me sth abour yourself",然后B收到报文后,就将缓冲区数据接收,然后关闭连接,这样粘包问题不用考虑到,因为大家都知道是发送一段字符。
2:如果发送数据无结构,如文件传输,这样发送方只管发送,接收方只管接收存储就ok,也不用考虑粘包
3:如果双方建立连接,需要在连接后一段时间内发送不同结构数据,如连接后,有好几种结构:
1)"hello give me sth abour yourself"
2)"Don't give me sth abour yourself"
   那这样的话,如果发送方连续发送这个两个包出去,接收方一次接收可能会是"hello give me sth abour yourselfDon't give me sth abour yourself" 这样接收方就傻了,到底是要干嘛?不知道,因为协议没有规定这么诡异的字符串,所以要处理把它分包,怎么分也需要双方组织一个比较好的包结构,所以一般可能会在头加一个数据长度之类的包,以确保接收。

三 .粘包出现原因:在流传输中出现,UDP不会出现粘包,因为它有消息边界(参考Windows 网络编程)
1 发送端需要等缓冲区满才发送出去,造成粘包
2 接收方不及时接收缓冲区的包,造成多个包接收
解决办法:
为了避免粘包现象,可采取以下几种措施。一是对于发送方引起的粘包现象,用户可通过编程设置来避免,TCP提供了强制数据立即传送的操作指令push,TCP软件收到该操作指令后,就立即将本段数据发送出去,而不必等待发送缓冲区满;二是对于接收方引起的粘包,则可通过优化程序设计、精简接收进程工作量、提高接收进程优先级等措施,使其及时接收数据,从而尽量避免出现粘包现象;三是由接收方控制,将一包数据按结构字段,人为控制分多次接收,然后合并,通过这种手段来避免粘包。
以上提到的三种措施,都有其不足之处。第一种编程设置方法虽然可以避免发送方引起的粘包,但它关闭了优化算法,降低了网络发送效率,影响应用程序的性能,一般不建议使用。第二种方法只能减少出现粘包的可能性,但并不能完全避免粘包,当发送频率较高时,或由于网络突发可能使某个时间段数据包到达接收方较快,接收方还是有可能来不及接收,从而导致粘包。第三种方法虽然避免了粘包,但应用程序的效率较低,对实时应用的场合不适合。
载自:http://blog.csdn.net/binghuazh/archive/2009/05/28/4222516.aspx
====================================================================

出0入0汤圆

发表于 2017-1-12 23:38:53 | 显示全部楼层
续,以下内容来源于网络:
三.怎样封包和拆包.
   最初遇到"粘包"的问题时,我是通过在两次send之间调用sleep来休眠一小段时间来解决.这个解决方法的缺点是显而易见的,使传输效率大大降低,而且也并不可靠.后来就是通过应答的方式来解决,尽管在大多数时候是可行的,但是不能解决象B的那种情况,而且采用应答方式增加了通讯量,加重了网络负荷. 再后来就是对数据包进行封包和拆包的操作.
    封包:
封包就是给一段数据加上包头,这样一来数据包就分为包头和包体两部分内容了(以后讲过滤非法包时封包会加入"包尾"内容).包头其实上是个大小固定的结构体,其中有个结构体成员变量表示包体的长度,这是个很重要的变量,其他的结构体成员可根据需要自己定义.根据包头长度固定以及包头中含有包体长度的变量就能正确的拆分出一个完整的数据包.
    对于拆包目前我最常用的是以下两种方式.
    1.动态缓冲区暂存方式.之所以说缓冲区是动态的是因为当需要缓冲的数据长度超出缓冲区的长度时会增大缓冲区长度.
    大概过程描述如下:
    A,为每一个连接动态分配一个缓冲区,同时把此缓冲区和SOCKET关联,常用的是通过结构体关联.
    B,当接收到数据时首先把此段数据存放在缓冲区中.
    C,判断缓存区中的数据长度是否够一个包头的长度,如不够,则不进行拆包操作.
    D,根据包头数据解析出里面代表包体长度的变量.
    E,判断缓存区中除包头外的数据长度是否够一个包体的长度,如不够,则不进行拆包操作.
    F,取出整个数据包.这里的"取"的意思是不光从缓冲区中拷贝出数据包,而且要把此数据包从缓存区中删除掉.删除的办法就是把此包后面的数据移动到缓冲区的起始地址.
这种方法有两个缺点.1.为每个连接动态分配一个缓冲区增大了内存的使用.2.有三个地方需要拷贝数据,一个地方是把数据存放在缓冲区,一个地方是把完整的数据包从缓冲区取出来,一个地方是把数据包从缓冲区中删除.第二种拆包的方法会解决和完善这些缺点.
前面提到过这种方法的缺点.下面给出一个改进办法, 即采用环形缓冲.但是这种改进方法还是不能解决第一个缺点以及第一个数据拷贝,只能解决第三个地方的数据拷贝(这个地方是拷贝数据最多的地方).第2种拆包方式会解决这两个问题.
环形缓冲实现方案是定义两个指针,分别指向有效数据的头和尾.在存放数据和删除数据时只是进行头尾指针的移动.
2.利用底层的缓冲区来进行拆包
由于TCP也维护了一个缓冲区,所以我们完全可以利用TCP的缓冲区来缓存我们的数据,这样一来就不需要为每一个连接分配一个缓冲区了.另一方面我们知道recv或者wsarecv都有一个参数,用来表示我们要接收多长长度的数据.利用这两个条件我们就可以对第一种方法进行优化.

====================================================================
以下这一行是我写的,哈哈,仅此一行.
以上内容,对TCP更进一步的认知,故此共享
====================================================================

出0入0汤圆

发表于 2017-1-13 00:31:31 来自手机 | 显示全部楼层
在这个帖子里看到一些关于网络数据包处理的知识,有点用,

出0入0汤圆

发表于 2017-1-13 01:53:58 来自手机 | 显示全部楼层
只能说明做服务器程序的水平不行,数据帧有头有长度。

出0入92汤圆

发表于 2017-1-13 05:56:02 | 显示全部楼层
MCU发送数据到串口的最短时间间隔是多久,TCP长连接一般建议发送数据上一帧与下一帧之间的间隔时间大于200MS。

出0入50汤圆

发表于 2017-1-13 06:54:30 来自手机 | 显示全部楼层
你的协议没问题,要优化的就是服务器数据接收的部分

出0入17汤圆

发表于 2017-1-13 07:09:21 | 显示全部楼层
好多都是UDP的哦,自己软件保证可靠性

出0入0汤圆

发表于 2017-1-13 08:27:44 | 显示全部楼层
我很负责任的告诉你,能做的了。看看我的数据包格式
0xa5a5xxx; xxxx表示协议版本
packetLen ;数据包总长度。
aa
bb
cc
.. ..以上是数据包头,下面是附加数据
accachLen ;附加数据长度
data;附加数据数组

出0入0汤圆

发表于 2017-1-13 08:34:06 | 显示全部楼层
黏包的原因不必找,任何服务器都存在黏包的可能,这是TCP协议决定的。除了黏包,TCP还会分包,就是你接到的数据未必是一个整包,可能存在一包分多次才能接受完整的情况。处理的方法很简单,就是在服务器端对接收到的数据进行缓存,然后另外一个线程负责解析这些数据,典型的生产者消费者模式。你这个协议做的有问题,通常有两种方法,一是先发数据长度,服务器端根据长度来收后边的数据,收够了就认为是一包完整的数据,然后再收长度,再收数据,规定好数据长度的固定字节,比如说2字节一般。第二种方法就是指定一个帧开头和结尾,中间的数据全部做16进制变换,可以避免数据与帧开头结尾冲突,也可以采用转义字符的方式处理。你你这种格式很有可能碰到数据与开头相同的问题,黏包的时候就分不开了。

出0入57汤圆

发表于 2017-1-13 08:38:42 | 显示全部楼层
黏包了就拆包啊,收到数据后先摘出来一包的数据,如果还有没处理的字节就再摘出一包数据来,直至本次收到的数据全处理完成。

出0入0汤圆

发表于 2017-1-13 08:39:36 | 显示全部楼层
学习了,学习了。。

出0入0汤圆

发表于 2017-1-13 08:45:59 | 显示全部楼层


应用层把黏包解开  不就可以了吗?     我们都是这样做的


出0入0汤圆

发表于 2017-1-13 09:01:33 | 显示全部楼层
TCP是流式的,就像自来水水流过水管。 实际上并没有“包”的概念。  所谓的“黏包”是伪命题。

包,你可以自己封装。 每“一堆”数据加上头尾,做成一个所谓的“包”,服务器收到后根据头尾解析。在头中包含长度,这样更可靠。

不过服务器也要写得好才行。比如你一个包是20字节发了2个包。有可能服务器第一次收到了30字节,是第一个包以及第二个包的前前半部分。 然后过了一会儿才收到最后10字节。

服务器必须要能可靠的处理这种情况,才能长期稳定工作。

出5入8汤圆

发表于 2017-1-13 09:09:19 | 显示全部楼层
1)问题根源:发送端与wifi配合的问题(wifi接收串口数据未能分包),造成多包数据打入一个tcp包
2)解决:第一个方法-修正发送端打包的粘包问题;第二个方法-在接收的服务器端加入分包机制,收到的数据包放入缓存,然后摘出单数据帧.


只是不明白你的分包处理为什么会那么耗时?(26s-4s = 22s)   应该是处理机制有问题

出0入8汤圆

 楼主| 发表于 2017-1-13 09:18:32 来自手机 | 显示全部楼层
semonpic 发表于 2017-1-13 08:27
我很负责任的告诉你,能做的了。看看我的数据包格式
0xa5a5xxx; xxxx表示协议版本
packetLen ;数据包总长度 ...

能够告诉我你们服务器用的是什么库来做的吗?

出0入0汤圆

发表于 2017-1-13 09:37:28 | 显示全部楼层
有这个现象很正常,尤其是gprs网络早期,网络质量不好的时候,由于数据是分组交换传输,每组数据经过的路径都有可能不同,先发的不一定先到,因此需要服务器端具有适应这些情况的能力,花生壳内网版这个现象更突出,这个问题能够解决,重点是优化服务器数据接收部分的程序.

出0入0汤圆

发表于 2017-1-13 09:55:16 | 显示全部楼层
lindabell 发表于 2017-1-12 20:33
你是做服务器的吗?是做过类似的吗?

我也是做偏底层应用的,这种TCP粘包的问题无论是在上位机还是底层上都会出现,我们实际项目中就是这样处理的.

出0入0汤圆

发表于 2017-1-13 10:10:06 | 显示全部楼层
lindabell 发表于 2017-1-13 09:18
能够告诉我你们服务器用的是什么库来做的吗?

netty 自带粘包处理的库

出0入0汤圆

发表于 2017-1-13 10:34:02 | 显示全部楼层
直接操作以太网的话,写完之后用flush强制写出一下。
不过你用串口转wifi的透传,还要先看一下模块手册上关于它的分包处理是怎么搞的,自动分包发送间隔是多少之类的参数。
这个粘包还可能是模块本身的粘包,你连续两包间隔太小,模块认为它是一包数据,直到你超过它预设的包缓冲区大小才会对外发送。如果是这种情况,配置一下适当减小模块的包缓冲区大小。
另外发送的数据流可以加上帧头帧尾,数据包中针对特定字节进行转义处理。服务器端专门开个线程只负责接收tcp数据加到缓冲队列中,收到数据之后,就根据帧头帧尾来解码分包

出5入14汤圆

发表于 2017-1-13 10:52:17 | 显示全部楼层
就像上面很多兄弟说的,TCP是传输“数据流”的,本来不存在什么包的概念!不过以我接触到的软件人员来说,十个里面有九个都图省事而直接处理包的 ......

出0入0汤圆

发表于 2017-1-13 12:12:06 | 显示全部楼层
本帖最后由 chinaye2 于 2017-1-13 12:13 编辑

擦,这种基本问题还用问?!  你可以换掉 你们写服务端的 人了,这种 基本处理 并发连接 大数据量的框架多的 是 这小问题还解决不了?!


楼上各位 不要自定义协议好吧 ,往行业 标准协议上靠 啊

出0入12汤圆

发表于 2017-1-13 12:21:51 | 显示全部楼层
TCP 是基于流的,小包组大包是常见的,再服务程序端拆开就好了,所有把数据看作是流的都需要做这样的事情。服务器的 CPU 是啥,用什么做的 Sever?数据库是啥?

出0入0汤圆

发表于 2017-1-13 13:04:01 | 显示全部楼层
本帖最后由 rundream 于 2017-1-13 13:09 编辑

方法1: LEN+DATA 模式,需要双方通信层都做分包处理, 不仅仅考虑黏包,还要考虑断包。处理好了,非常稳定,我已经这么用了10年了。

方法2:用7E作为帧间隔符,内容层7E等字符 转义为7D 5E等 , ....,参照PPP协议。

出0入8汤圆

 楼主| 发表于 2017-1-13 15:07:24 | 显示全部楼层
打听到使用的是mina框架,这个框架处理不了这类数据吗?

出0入4汤圆

发表于 2017-1-13 15:18:08 | 显示全部楼层
问题出在做服务器人那边,他们应该只做业务端,可能都没接触过socket数据流类通讯解析。
这种架构下,最好加一级中间件,你可以用c语言写,基本分3个线程,第一个就是接收并丢到内存数据池(保证不丢tcp包是这个线程主要任务);第二个就是拆包,将数据池内容再拆成一个一个数据包,第三个对接业务服务器,可以数据包直接扔过去,或者按照服务器要求转出其他格式等等。
这一级基本可以解耦数据流跟业务,这一级最好做硬件的一起做掉

出0入0汤圆

发表于 2017-1-13 15:51:25 | 显示全部楼层
lindabell 发表于 2017-1-12 16:49
产品已经在试产阶段了,大改不可能

看下26秒程序都在做什么了,是不是处理逻辑有问题。 另外需要评估下最终会有多少个设备连接到服务器,服务器能不能顶住负载。

出0入8汤圆

 楼主| 发表于 2017-1-13 16:00:07 | 显示全部楼层
kelp 发表于 2017-1-13 15:51
看下26秒程序都在做什么了,是不是处理逻辑有问题。 另外需要评估下最终会有多少个设备连接到服务器,服 ...

不是我做,也看不懂啊。
我做有一个上位机做测试就像常用那种串口一样只是改成TCP而已,丢到阿里云运行的棒棒的。

出0入0汤圆

发表于 2017-1-13 16:18:09 | 显示全部楼层
Modbus over TCP文档是这样指导的:
It is recommended to force the TCP-NODELAY option that disables the "NAGLE algorithm" on client and server connections.

出100入101汤圆

发表于 2017-1-13 16:21:31 | 显示全部楼层
用UDP,加确认、重传机制

出0入0汤圆

发表于 2017-1-13 16:27:00 | 显示全部楼层
不是服务器处理不了,是服务器端的程序员处理不了或不想处理。数据包拆分不算是什么问题,是基本功能。

出0入8汤圆

 楼主| 发表于 2017-1-13 16:30:49 | 显示全部楼层
在隔壁找到一篇使用mina+自定义格式(格式比较像我目前这个);看了看和C++差不多啊;感觉我学一下java就能写出来。
http://blog.csdn.net/fendou4533/article/details/9083611

出0入0汤圆

发表于 2017-1-13 16:34:33 | 显示全部楼层
我们做的是7e开始 7ej结束   遇到7e转义  7e==>7d 5e    7d==>7d 5d   两个7e间就是数据

出0入8汤圆

 楼主| 发表于 2017-1-13 16:39:52 | 显示全部楼层
STT 发表于 2017-1-13 16:34
我们做的是7e开始 7ej结束   遇到7e转义  7e==>7d 5e    7d==>7d 5d   两个7e间就是数据 ...

这种协议我也写过,汽车行业里的一个国标协议。
服务器人能力不行,这个他肯定也搞不出来;最好搞个json给他就满意了。

出0入8汤圆

 楼主| 发表于 2017-1-13 16:41:41 | 显示全部楼层
styleno1 发表于 2017-1-13 16:18
Modbus over TCP文档是这样指导的:

印象中modbus是靠时间间隔来区分帧的,所以会这样写。

出0入0汤圆

发表于 2017-1-13 19:00:29 | 显示全部楼层
55AA+MAC+len+CRC8
随便写个函数截断就行了
其实不你只是要解决粘包,还要解决一个数据包分两次收到

出40入42汤圆

发表于 2017-1-13 21:09:20 | 显示全部楼层
chinaye2 发表于 2017-1-13 12:12
擦,这种基本问题还用问?!  你可以换掉 你们写服务端的 人了,这种 基本处理 并发连接 大数据量的框架多 ...

请教,“行业标准协议”有哪些?

出0入4汤圆

发表于 2017-1-13 21:31:11 | 显示全部楼层
本帖最后由 huchunlei 于 2017-1-13 21:39 编辑

楼主最近做的事跟我做的类似,我也是做单片机的,但是服务器没人写,我只有自己写了。  我用的是 UDP,设备端用的是 透传的 GPRS DTU, 设备传过来的数据 会发生, 一包分成2包, 或者多包和并在一起。  所以,我这个数据处理也有你这个问题。  不过花了几天时间,问题基本上解决了。
大体思路如下:

1、接收的数据进行初步的判断,同一个IP地址发过来的数据进行连接。
2、将连接后的数据进行识别,也就是最笨的办法,从前往后遍历,遇到包头,就按照协议规则去计算验证码,验证码正确,就把这部分数据提取出来。 然后前面多出来的数据是无效数据, 而后面多出来的数据要留着,需要跟新接收过来的数据包进行连接。
3、对提取出来的数据进行处理。

我的C# 代码如下, 供你参考:(声明一下,我是做单片机的,上位机我非专业人事,所以代码风格很烂,仅供参考)

1、数据处理代码:(处理代码是单独一个线程运行的,所以里面是个大的死循环)

  1. private void ServiceProcess()
  2.         {
  3.             byte[] PendingDataBuffer = null;    //待处理流数据缓存
  4.             IPEndPoint PendingRemote = null;    //待处理流数据的来源地址

  5.             while (true)
  6.             {
  7.                 //读取并缓存原始数据
  8.                 List<UdpReceiveDataStruct> ReceiveOrgList = ServiceUdpClient.GetReceiveData();
  9.                 while (ReceiveOrgList.Count > 0)
  10.                 {
  11.                     UdpReceiveDataStruct ReceiveOrg = ReceiveOrgList[0];
  12.                     lock (UdpReceiveOrgListLocker)
  13.                     {
  14.                         UdpReceiveOrgList.Add(ReceiveOrg);
  15.                         ReceiveOrgList.RemoveAt(0);
  16.                     }
  17.                 }

  18.                 //由于原始数据可能存在错误的分包、组包,需要对数据进行识别并缓存有效数据
  19.                 while (UdpReceiveOrgList.Count > 0)
  20.                 {
  21.                     UdpReceiveDataStruct ReceiveOrg;

  22.                     //通信总数+1
  23.                     lock (CommunicatorStateLocker)
  24.                     {
  25.                         CommunicatorState.CommunicationTotalCount++;
  26.                     }

  27.                     //取出一条原始数据
  28.                     lock (UdpReceiveOrgListLocker)
  29.                     {
  30.                         ReceiveOrg = UdpReceiveOrgList[0];
  31.                         UdpReceiveOrgList.RemoveAt(0);
  32.                     }

  33.                     //初步判断数据是否有效,并合并
  34.                     if (PendingDataBuffer == null)   //待处理流数据为空
  35.                     {
  36.                         PendingDataBuffer = (byte[])ReceiveOrg.DataContent.Clone();
  37.                         PendingRemote = ReceiveOrg.Remote;
  38.                     }
  39.                     else
  40.                     {
  41.                         if (PendingRemote.Address == ReceiveOrg.Remote.Address)   //判断数据来源是否相同,如果相同,连接数据
  42.                         {
  43.                             byte[] newBuffer = (byte[])PendingDataBuffer.Clone();
  44.                             PendingDataBuffer = newBuffer.Concat(ReceiveOrg.DataContent).ToArray();
  45.                             PendingRemote = ReceiveOrg.Remote;   //为防止来源地址其他属性发生变化,进行更新
  46.                         }
  47.                         else     //如果不同,丢弃原来的数据,并把丢弃的数据通过Log输出
  48.                         {
  49.                             //输出Log
  50.                             string str = "【" + ProjectSetting.ProjectCode + "】" + "NWGY接收数据出现无效数据段:" + PendingRemote.Address.ToString() + ":" + PendingRemote.Port.ToString() + ",";
  51.                             foreach (byte d in PendingDataBuffer)
  52.                             {
  53.                                 str += d.ToString("X2") + " ";
  54.                             }
  55.                             Log.WriteLine(str);

  56.                             //通信错误数+1
  57.                             lock (CommunicatorStateLocker)
  58.                             {
  59.                                 CommunicatorState.CommunicationErrorCount++;
  60.                             }

  61.                             PendingDataBuffer = (byte[])ReceiveOrg.DataContent.Clone();
  62.                             PendingRemote = ReceiveOrg.Remote;
  63.                         }
  64.                     }

  65.                     //将初步处理的数据,进行识别
  66.                     while (true)   //进行多次识别,直到不存在有效数据
  67.                     {
  68.                         if (PendingDataBuffer == null)
  69.                         {
  70.                             break;
  71.                         }

  72.                         //识别连接后的数据,输出有效数据、无效数据、未完成数据
  73.                         //有效数据存入
  74.                         DataIdentResultStruct DataIdentResult = HelperNWGY10Encoding.IdentData(PendingDataBuffer);   
  75.                         if (DataIdentResult == null)
  76.                         {
  77.                             break;
  78.                         }

  79.                         if (DataIdentResult.InvalidData != null)   //无效数据通过LOG输出
  80.                         {
  81.                             string str = "【" + ProjectSetting.ProjectCode + "】" + "NWGY数据识别出现无效数据段:" + PendingRemote.Address.ToString() + ":" + PendingRemote.Port.ToString() + ",";
  82.                             foreach (byte d in PendingDataBuffer)
  83.                             {
  84.                                 str += d.ToString("X2") + " ";
  85.                             }
  86.                             Log.WriteLine(str);

  87.                             //通信错误数+1
  88.                             lock (CommunicatorStateLocker)
  89.                             {
  90.                                 CommunicatorState.CommunicationErrorCount++;
  91.                             }
  92.                         }
  93.                         PendingDataBuffer = DataIdentResult.IncompleteData;   //未完成数据进行缓存,以便进行下一次识别
  94.                         if (DataIdentResult.ValidData != null)        //存在有效数据,进行缓存,并进行下一次识别,否则跳出识别循环
  95.                         {
  96.                             UdpReceiveDataStruct ReceiveData = new UdpReceiveDataStruct();
  97.                             ReceiveData.Remote = PendingRemote;
  98.                             ReceiveData.DataContent = (byte[])DataIdentResult.ValidData.Clone();
  99.                             lock (UdpReceiveDataListLocker)
  100.                             {
  101.                                 UdpReceiveDataList.Add(ReceiveData);
  102.                             }
  103.                         }
  104.                         else
  105.                         {
  106.                             break;
  107.                         }
  108.                     }
  109.                 }

  110.                 //这里是对识别好的数据进行处理的部分了,删去了。
  111.                 ...
  112.                 //数据处理部分结束

  113.                 if (ServiceStopFlag == true)
  114.                 {
  115.                     break;
  116.                 }

  117.                 //挂起一下,防止CPU利用率过高
  118.                 Thread.Sleep(10);

  119.             }
  120.         }
复制代码



2、处理处理代码中使用的数据识别提取函数

  1. public static DataIdentResultStruct IdentData(byte[] Data)
  2.         {
  3.             if (Data == null)
  4.             {
  5.                 return (null);
  6.             }

  7.             DataIdentResultStruct DataIdentResult = new DataIdentResultStruct();
  8.             byte[] OrgData = (byte[])Data.Clone();

  9.             //查找数据包起始码
  10.             for (int i = 0; i < OrgData.Length; i++)
  11.             {
  12.                 if (OrgData[i] == 0x68)
  13.                 {
  14.                     //找到一个起始码,计算剩下的数据长度是否可能构成一个数据包
  15.                     if (i + 12 > OrgData.Length)   //数据长度不够
  16.                     {
  17.                         if (i > 0)
  18.                         {
  19.                             DataIdentResult.InvalidData = OrgData.Take(i).ToArray();
  20.                         }
  21.                         DataIdentResult.IncompleteData = OrgData.Skip(i).ToArray();

  22.                         return DataIdentResult;
  23.                     }

  24.                     //根据协议计算剩下的数据长度是否可构成一个数据包
  25.                     int DataSegLenth = OrgData[i + 8] * 256 + OrgData[i + 9];
  26.                     int PacketLenth = 12 + DataSegLenth;
  27.                     if(i+ PacketLenth> OrgData.Length)     //数据长度不够
  28.                     {
  29.                         if (i > 0)
  30.                         {
  31.                             DataIdentResult.InvalidData = OrgData.Take(i).ToArray();
  32.                         }
  33.                         DataIdentResult.IncompleteData = OrgData.Skip(i).ToArray();

  34.                         return DataIdentResult;
  35.                     }

  36.                     //判断数据包结束码
  37.                     if (OrgData[i + PacketLenth - 1] != 0x16)   //结束码不正确,继续循环
  38.                     {
  39.                         continue;
  40.                     }

  41.                     //计算SUM是否正确
  42.                     if (SumCalc(OrgData, i + 1, PacketLenth - 3) != OrgData[i + PacketLenth - 2])   //SUM不正确,继续循环
  43.                     {
  44.                         continue;
  45.                     }

  46.                     //能运行至此,说明数据包是正确的
  47.                     if (i > 0)
  48.                     {
  49.                         DataIdentResult.InvalidData = OrgData.Take(i).ToArray();
  50.                     }
  51.                     DataIdentResult.ValidData = OrgData.Skip(i).Take(PacketLenth).ToArray();
  52.                     if (i + PacketLenth < OrgData.Length)
  53.                     {
  54.                         DataIdentResult.IncompleteData = OrgData.Skip(i + PacketLenth).ToArray();
  55.                     }

  56.                     return DataIdentResult;
  57.                 }
  58.             }

  59.             //如果循环执行到结束了,说明没找到有效的数据包,所有的数据均为未完成数据
  60.             DataIdentResult.IncompleteData = (byte[])OrgData.Clone();

  61.             return DataIdentResult;
  62.         }
复制代码

出0入8汤圆

 楼主| 发表于 2017-1-13 22:33:00 | 显示全部楼层
经过多方获取得到的信息,我已经得到我想要的答案了(服务器可以做,并且也不是很难做)。
目前有200元话费+500莫元,你不知道给谁;我没法判断谁的信息最有价值;所以交给坛友决定吧!谢谢你们的围观与回复。

可以在后面留言,由于明天与做服务器的对垒所以话费和莫元会在明天晚些时候决定给谁。

出0入0汤圆

发表于 2017-1-14 12:06:56 来自手机 | 显示全部楼层
本帖最后由 armku 于 2017-1-14 12:08 编辑

http://www.newlifex.com/showtopic-1550.aspx
标准网络封包协议:1 Flag + 1 Sequence + 2 Length + N Payload
1个字节标识位,标识请求、响应、错误、加密、压缩等;
1个字节序列号,用于请求响应包配对;
2个字节数据长度N,指示后续负载数据长度(不包含头部4个字节),解决粘包问题;
N个字节负载数据,数据内容完全由业务决定,最大长度65535=64k。

出0入8汤圆

 楼主| 发表于 2017-1-16 10:23:09 | 显示全部楼层
因为没有人帮我评价一下谁的信息最有用,所以我自己来了;有不当的地方还请大家见谅。
18楼的ywhbn网友500莫元
25楼aozima网友100元话费
33楼fchen2网络100元话费
上面网友可以私信电话号码给我,我冲值话费给你

出40入42汤圆

发表于 2017-1-16 10:52:23 | 显示全部楼层
lindabell 发表于 2017-1-16 10:23
因为没有人帮我评价一下谁的信息最有用,所以我自己来了;有不当的地方还请大家见谅。
18楼的ywhbn网友500 ...

比较关心楼主的问题解决了吗?

出0入0汤圆

发表于 2017-1-16 10:58:17 | 显示全部楼层
服务用MINA框架,后台没一点压力,数据包给分的好好的

如果你会MQTT,那更方便

出0入8汤圆

 楼主| 发表于 2017-1-16 13:22:52 | 显示全部楼层
落叶知秋 发表于 2017-1-16 10:52
比较关心楼主的问题解决了吗?

经过各段抓包100%肯定是服务器处理黏包的问题,迫使服务器修改代码;目前服务器回应说没有延时了。

出0入8汤圆

 楼主| 发表于 2017-1-16 13:24:57 | 显示全部楼层
mfkqqw 发表于 2017-1-16 10:58
服务用MINA框架,后台没一点压力,数据包给分的好好的

如果你会MQTT,那更方便 ...

MQTT玩过一点点,但还没有上个项目。
年后会把mqtt搭建起来

出0入0汤圆

发表于 2017-1-17 19:48:17 | 显示全部楼层
lindabell 发表于 2017-1-16 10:23
因为没有人帮我评价一下谁的信息最有用,所以我自己来了;有不当的地方还请大家见谅。
18楼的ywhbn网友500 ...

多谢楼主。后续如果有什么问题,可以联系

出0入22汤圆

发表于 2017-1-17 20:02:44 | 显示全部楼层
LZ 你的APP 和服务器是外发给别人做的吧。

这个问题 根本就不是一个问题。做底层硬件的都知道自动处理啊 。

外发的话,后面的问题会更多,各种推脱,各种技术不行,
如果你的技术 镇不住他们的话,以后还会有大把的问题等着你去处理。

出0入8汤圆

 楼主| 发表于 2017-1-19 10:54:46 | 显示全部楼层
25楼aozima
33楼fchen2
都没有发私信给我,顶一下。
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-26 03:25

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表