我在暑假做课设的时候要做一个文件传输的功能,于是我有如下设计:
发送端A:
char szbuf[MAX_LEN];
while (1)
{
/* 将前1个字节作为消息的标志位(标志该包是否是文件的结尾) */
/* 将文件读入(szbuf+1) */
send(....);
}
接收端B:
char szbuf[MAX_LEN];
while (1)
{
recv(....);
/* 将收到的数据写入文件 */
if(/* 标志位说明是文件结尾 */)
{
/* 关闭文件 */
}
}
假设我们有一个文本文件,内容都是a
那么按我们的预期一个包的内容应该是
1
aaaaaaaaaa(假设0表示文件结尾,1表示文件内容,每次发送10个字节的文件内容)
但是有的时候在接收端会读到第一个字节为
a的情况,导致判断的时候逻辑出错。为什么呢?
上网查了以后,得到以下结论:
由于TCP在发送包的时候是基于流式的,所以会出现以下几种情况:
1.假设发送端连续发送两个
1
aaaaaaaaaa
包,那么在接收端的缓冲区中会连续存储着1aaaaaaaaaa1aaaaaaaaaa接收端先后两次按照预期的从缓冲区中读出11个字节,得到1aaaaaaaaaa。
2.发送端在发送一个
1aaaaaaaaaa的包时,可能会是分两次发送的,也就是说可能第一次只发送了
1
aaaaa的数据,而第二次将剩下的
5个a与第二个包并为
aaaaa
1
aaaaaaaaaa然后再发送。由于TCP基于流式的性质,那么以上发送的
1
aaaaa
和aaaaa1aaaaaaaaaa会被先后写入接收端的缓冲区,合并成1aaaaaaaaaa1aaaaaaaaaa。然后接收端先后两次按照预期的从缓冲区中读出11个字节1aaaaaaaaaa。
以上说的是理想情况下的发送与接收,实际情况下,可能会出现第3种情况:
发送端在发送一个
1aaaaaaaaaa的包时,分两次发送的,第一次发送了
1
aaaaa的数据。正当此时,接收端去读缓冲区的时候发现缓冲区中只有6个字节的数据,那么接收端就会只读6个字节上来。那么在下一个包
aaaaa
1
aaaaaaaaaa
到达后,接收端如果还是读11个字节的话就会得到aaaaa1aaaaa的结果。也就是我们之前遇到的问题。
那么有什么方法可以解决我们这个问题呢?
我总结的有两个:
1.既然是文件发送,整个文件是连续的,那么我在发送的时候不需要标志位,接受端不论每次收到几个字节都一股脑地写入文件,在发送完成的时候,由发送端closesocket就可以。
这样实现确实是可以做到不发生错误,但是有一个问题,如果我在发送TCP包的时候确实需要一些标志状态的标志位呢?我在网上找到了另一种方法
2.每次在接收端接受的时候判断收到的字节是否为预期的大小,如果不是,那么再次recv剩下的大小,直到接收端收到预期的大小为止。这样就可以保证每次发送端发出了什么信息,接收端也能收到同样的信息。
看了以上的解释后,我再分享一个问题:
我在使用我最初的设计(也就是最前面的那两段会出错的伪码)写出代码后,在两台机子上测试,一台是我家里7、8年前买的老古董电脑,一台是我现在用的本本,此时会有一个神奇的情况,如果是我家的老电脑向我的本本发的时候,会出现逻辑混乱,挂了……然而要是用我的本本向老电脑发文件的时候,却一点问题没有~为什么呢~~?(大家先想一下再看下我的想法)
↓
↓
↓
↓
↓
↓
↓
↓
↓
↓
↓
↓
↓
我的想法是这样的:如果是本本向老电脑发时,由于send速度快些,老电脑recv慢些,所以recv的时候缓冲区中都是充裕的,不会出现那种读不全的情况;而老电脑向本本发的时候,由于send慢,recv快,所以可能在本本recv的时候,老电脑还没send完,这样就会出现前面说的第3中情况。(个人见解,如果异议,敬候……)