arongnet 阅读(2550) 评论(7)
初涉socket编程的朋友经常有下面一些疑惑:
1. 为什么我发了3次,另一端只收到2次?
2. 我每次发送都成功了,为什么对方收到的信息不完整?

这些疑惑往往是对send和recv这两个函数理解不准确所致。send和recv都提供了一个长度参数。对于send而言,这是你希望发送的字节数,而对于recv而言,则是希望收到的最大字节数。

1。 send

send函数的原型是:int send(SOCKET sd, const char * buffer, int len, int flag). 其中len指出buffer中包含的实际字节数,也是程序员希望发出的最大字节数。而这个函数的返回值是实际发送出去的字节数。在网络程序中,正常情况是这个返回值小于len,也就是说buffer中的内容没有完全被发送出去。

为了确保一个缓冲区内的内容被完全被发送出去,我们需要如下代码:

int res;
int pos = 0; //下一次发送需要开始的位置:
while(pos < len)
{
res = send(sd, buffer + pos , len - pos, 0);
if(res <=0) goto err_handler; //去错误处理
pos += res;
}

这样经过多次send,可以确保buffer内的内容都别发送出去。

为了避免发送线程被阻塞,应该考虑把上述代码放到一个子线程中,并通过队列来缓冲所有收发。

2. recv

recv的原型是:int recv(SOCKET sd, char * buffer, int len, int flag),其各个参数的含义同前面send。需要注意的是,系统并不会等待bufer被填满了再返回,而是一旦有数据被收到,就立刻返回。因此不要期望实际收到的数据长度就等于len。

你可以使用前面send的循环算法确保收到len个字节,也可以使用内容驱动的方法实现分段数据分析。不过这个就超出本文内容,也就不再赘述。

3. 粘包
所谓粘包,是指发送端发送的两个报文,在接收端被拼在一起。由于TCP是面向流的协议,报文与报文之间是没有分界符号的。在接收端,所有的数据都逻辑上拼在一起给你。举例来说,你分10次发送10个长度为10的报文,在接收端,你可能只收到一个长度为100的报文,而不会收到10个消息。

为了解决这个问题,你必须在接收端有能力把这些报文分隔开来。如果消息长度总是固定的,这就比较容易,只要按长度取出即可。如果长度不固定,一般有两种方法解决:

a)使用特征字节。例如:如果是聊天程序,发送的是普通文本,一些字符是绝对不可能出现在正文中的,你可以使用这些字符做分隔符隔离不同消息。我们可以使用'\0'做分隔,一般对于全文本传输是比较安全的。

b) 在发送方发送正文前,先发送一个长度。例如你要发送2345字节的内容,你可以先发送一个2字节的长度给对方,然后再发正文。接收放只要收到这个长度信息,就可以正确的分包。需要注意的,到底用多少字节来发送长度是应该预先约定的,一般2字节就足够,不过你约定4字节也是可以的。还要注意的是,如果接收一次报文后,解包完毕还剩下一部分内容,这些内容应该留给下次报文分包使用,而不能扔掉。

4. 丢包
丢包一般都是由于对上面说的理解不足引起的,因为TCP本身是确保不丢包的。



评论列表
advice
re: 粘包、丢包及TCP信息收发
好好读读richard的书
完全不懂网络
nscby
re: 粘包、丢包及TCP信息收发
理解的TCP数据流的概念就不难弄清除粘包和丢包的概念了.
TCP根本没有包这个东西.所谓的包是上层的协议规定的.
没有包也无所谓粘包和丢包了.
不是包.是流.
boli
re: 粘包、丢包及TCP信息收发

刀哥. send 涵数在阻塞模式下是发多少就是多少吧?不会少发的.

Dennisweri
?

发表评论
切换编辑模式