最近讨论网络数据粘包的问题太少了,本人就发一贴来讨论下!在TCP网络传输中数据包粘包这个问题是一难题。所以请各位朋友给出一个答案:也就是说如果将缓冲区设为:1020字节的话,那么我们也把要传送的数据包也强制控制在1020字节(不够则补,有多则分包),那么还会不会出现粘包、分包等等问题?并且发送方不使用线程暂停函数暂停发送的方法!如果此方法可以解决上述问题的话,那么还有没有什么别的不好的地方!希望大家本着已测试过并有真实结论的态度给出准确答案。
调试欢乐多
比如一个数据包大小为 8192 被分成了8个包
你Receive一次 只能接受1024的数据,要Receive 8次才能接受完整,才能开始处理。至于集合顺序问题可以用Queue<T>
我指的是这样的问题:就算用Queue<T>这个泛型集合对象来储存每个数据包,那么如果有两个不同套接字发来了两个数据包(应该是数据流,因为TCP是基于数据流的无边界协议而无数据包概念),如果只是这样的话很容易判断顺序问题,但如果两个不同的套接字在不停的接收数据包,并且没有顺序的放进Queue<T>集合中,那么就算是我在包头加入开始标识符也没用,因为后面收到的数据包我并不知道哪个是接在哪个后面的?所以问题就在于此!是否解决此问题可以在Queue<T>集合中的每个套接字的每段数据包集合里面添加一个顺序标记变量来标记哪个数据包是排在第几?不过这样不知从何下手!
朋友,对于你这个说法,我现在想举个例子:比如我定义了一个Queue<byte>集合,我把所有客户端套接字接收到的byte字节都放在这里面,也就是说这里面会出现A客户端套接字的BYTE数组,也会有B客户端的BYTE数组,如此不断增加...;那么,按你说的只要定义一个包结构就可以解决,比如包结构为:包长度+包内容,那么就像这样来发送每个包,那么当A客户端和B客户端多次发送后,并且服务器端也多次接收了并把每次接收到的数据都一条一条的放进了Queue<byte>集合中,注意,放进的顺序不可能是按客户端发送的顺序一样放的,而是来一条就放一条;那么现在问题来了,比如我要把Queue<byte>集合中A客户端发送的几条数据组合成一个完整的包时,我根本不能知道哪条数据在哪条数据后面,也就是说我即便定义了包长度,我也不知道包含了包长度的那条数据在Queue<byte>集合中的哪个位置,即便我知道包含包长度的那条数据在哪里,并且我取出来了,那么我又怎么能知道紧接着这条数据后面的那条数据放在了Queue<byet>集合的哪个位置呢?如果你说只要找到相同IP和端口的那条数据就行了,但问题是Queue<byte>集合里面现在有N条相同IP和端口的数据,主要问题就在于此?也就是说如何做到TCP协议一样,如何把包顺序组织好!
我想我这样描述应该够清楚问题的本质原因了吧?还请SP1234朋友,dancingbit朋友, Linux7985(上善若水)朋友以及各位了解的朋友来解答此问题!
协议上定义一个数据头4个字节(int)存放数据包实际大小,每次在socket上进行读写操作时都要把数据加上数据长度做成新的数据包发送。
程序上定义一个Queue<byte[]> buffer,一个读写线程每次从socket读出来的byte[]数据不做任何处理全部enqueue进buffer,然后还有一个包组装线程,遍历buffer,按照读4个字节为长度len,读len个字节顺序处理,有个技巧是当读完len个字节后dequeue出来的byte[]还有数据时再把这些数据enqueue回buffer。
最后读完一个完整协议包后把协议包发送给业务线程处理就行了。
然后
while(count = 包长度)
{Receive 数据}
也就是读取一个完整的自定义包,
再加入集合
while(count != 包长度)
{
Receive 数据;
}
这里要注意Receive 的数据可能包括下一个数据包的数据。
所以每次Receive 都要计算一下,当前包的剩余长度
这位朋友,你还是没全部看明白我的意思,我的意思是说Queue<byte[]> 不止是放了一个客户端套接字接收的字节数组,而是放了多个客户端的字节数组;正因为如此才不好判断某一个客户端接收的多条数据的顺序问题,因为Queue<byte[]> 里面存的每条数据很可能是比如:第一条是A客户端的数据,第二条是B客户端的数据,第三条又可能是A客户端的数据;所以这样一来根本没办法重组A客户端的几条数据为一个完整客户端。这应该属于逻辑问题,但确实属于一个比较“难”解决的问题,似乎需要TCP保持数据不乱序的算法,但本人对TCP非常不熟,所以根本无法知道其算法的实现;再者,也许TCP实现数据不乱序的算法根本就不可能在集合里面实现,而是定义了复杂的数据结构才得以实现!本人现想到一个方法可以解决:为每个客户端定义一个可变长的字节数组变量,把每次客户端接收到的数据预先放在此字节数组中,然后待解析线程使用解析数据包的算法来解析出每个包之后再放入Queue<解析后的对象类>中,最后由服务端数据处理线程取出处理!但这个方法看起来似乎多了一步,很可能影响效率,也不知会不会产生大量内存碎片。所以还请各位朋友讨论是否还有更好的方法!
1. TCP 是不会出现乱序的,除非你自己打乱它
2. TCP 每个客户端连接之间的数据是独立的,除非你自己混淆它在 codeproject 上随便找个例子都能够解决你的问题。
朋友,我不是说TCP会乱序,我的意思是说我每个套接字接到数据后都要存入一个公用的集合对象里面,所以这样一来会乱序,所以我才提问有没有方法在这样的乱序情况下重组数据!
你不会每一个socket使用一个独立的Queue么?非要把所有的socket放在一起干嘛,一个简单的Dictionary<Socket, Queue<byte[]>>就搞定了,干嘛把问题想的那么复杂。
朋友,我就是想用Dictionary<Socket, Queue<byte[]>>这个方法解决。但是,针对我提的这个问题单纯用这个方法我不说绝对,可是也非常难解决这问题,正像我上面说的就算我们以Dictionary<Socket, Queue<byte[]>>这样的集合去存每个套接字和相应的字节数组,但由于并不是每一条集合信息里面的字节数组都是一个完整的包,基本上要两条以上的集合内容才能组成一个完整的数据包,所以在这种情况下定义包头内容也是无济于事的;比如:现在的集合是这样的:Dictionary<Socket(A), Queue<byte[]>>
Dictionary<Socket(B), Queue<byte[]>>
Dictionary<Socket(A), Queue<byte[]>>
Dictionary<Socket(B), Queue<byte[]>>
那么多必要要把两条A的字节数组合在一起才能组成一个包的话,我如何知道哪条是在前哪条是在后?这只是只有两条的情况下,如果有N条呢?那不是更搞不清顺序了?
假定有这么一个类:
class Peer
{
TcpClient c;
Queue<byte[]> buffer
}
及 一个List<Peer> peers;当Accept的时候就创建一个Peer,把c设为Client,并存入peers。到此在peers里就存放了所有连入得客户端TcpClient及一个读写用buffer。
然后读循环:
while(true)
{
for (int i = 0; i < peers.Count; ++i)
{
if (peers[i].c.Poll(0, SelectMode.SelectRead))
{
可以读写,通过异步或同步read把数据读入peers[i].buffer
}
}
}然后数据组装线程:
while(true)
{
for (int i = 0; i < peers.Count; ++i)
{
if (peers[i].buffer.Count > 0)
{
进行数据包拆分,组合成真正需要的数据包并发送到业务处理线程
}
}
}要完整代码的话可以自行搜一下论坛。
老兄,麻烦你看清楚内容再回贴,你说的这个方法能处理乱顺问题吗?
看清楚:
Dictionary<Socket(A), Queue<byte[]>>
Dictionary<Socket(B), Queue<byte[]>>
Dictionary<Socket(A), Queue<byte[]>>
Dictionary<Socket(B), Queue<byte[]>>
这种情况下就是你说的方法,但你给出的:
while(true)
{
for (int i = 0; i < peers.Count; ++i)
{
if (peers[i].c.Poll(0, SelectMode.SelectRead))
{
可以读写,通过异步或同步read把数据读入peers[i].buffer
}
}
}然后数据组装线程:
while(true)
{
for (int i = 0; i < peers.Count; ++i)
{
if (peers[i].buffer.Count > 0)
{
进行数据包拆分,组合成真正需要的数据包并发送到业务处理线程
}
}
}能分析出哪条数据在哪条数据之后吗?我都已经说了所有数据要放在公共接收字节数组集合里;如果要用到和你那么复杂的算法才能解决的话,那我上面给出的方法不就已经解决问题了吗!