您的位置:首页 > 编程语言 > C语言/C++

【反思】一个价值两天的BUG,无论工作还是学习C语言的朋友都看看吧!

2015-09-12 12:20 363 查看
博文原创,转载请联系博主!

使用C语言也有两个年头了,BUG写出来过不少,也改过不少BUG。但是偏偏就是有这么一个BUG让我手头的项目停工了两天,原因从百度找到谷歌,资料从MAN手册找到RFC也没有找到问题的原因,但是真正发现BUG原因之后实在是让自己汗颜。

不管如何,决定把这个BUG写进博文,也是给学习C语言的朋友们提个醒,查看BUG的眼光不要太高,思考问题要自底向上思考。

具体项目在我的github里:  https://github.com/yue9944882/HttpAccelerater

正文:

问题大致发现是这样的,在这个HTTP下载器中,实质上编程逻辑都是在传输层TCP套接字上完成的,具体细节就不多提,在通讯过程中是通过一对send和recv函数完成的,recv函数原型如下所示:(linux环境<sys/socket.h>)

int recv( _In_ SOCKET s, _Out_ char *buf, _In_ int len, _In_ int flags);

sockfd:   接收端套接字描述符

buff:   用来存放recv函数接收到的数据的缓冲区

nbytes:   指明buff的长度

flags:    一般置为0

返回值:  recv函数返回其实际copy的字节数

flags说明recvsend
MSG_DONTROUTE绕过路由表查找
MSG_DONTWAIT仅本操作非阻塞
MSG_OOB    发送或接收带外数据
MSG_PEEK  窥看外来消息
MSG_WAITALL  等待所有数据
  问题就是出在recv函数的返回值这里,因为和HTTP服务器的通讯过程中,服务器端所返回的内容不仅仅是包括一个所请求的文件数据,还有http的报头,而且在recv得到的数据中HTTP首部和实体是混合在一起的,所以就需要我们用\r\n\r\n四个字符作为标志来检测HTTP首部的结束,而且又因为recv得到的数据并不是完整地填充进接收数据的缓冲区中的,所以我们计算接收到的文件的第一段数据的偏移是这样的:

[ HTTP首部结束的偏移,实际读进缓冲区的偏移 ]

因为HTTP首部长度远远小于缓冲区长度就忽略首部填满缓冲区的情况。

recv函数是得到实际读进缓冲区偏移的关键,可是实际调试过程中每次recv返回的值都是0!

可是缓冲区里面却读进了请求的数据,里面也有完整的HTTP首部,这究竟是为什么呢?

  那么我们查看一下recv函数返回0的具体原因:

recv()
returns 0 only when you request a 0-byte buffer or the other peer has gracefully disconnected.

  首先我们的缓冲区确确实实写进了数据,就谈不上0-byte buffer,另一种情况就是TCP连接的正常关闭,即服务器端发送FIN包,但是如下所示,我们的代码中有后续的while循环仍然接受到了服务器端传送的数据,代码如下所示:

while(curPos<gURLinfo.llContentLen){
dr=recv(sockdesc,recvBuf,4096,0);
if(dr+curPos>gURLinfo.llContentLen){
dw=pwrite(file,recvBuf,gURLinfo.llContentLen-headlength-curPos,curPos);
curPos+=dw;
//printf("offset:\t%d\ndw:\t%d\n",curPos,dw);
break;
}else{
dw=pwrite(file,recvBuf,dr,curPos);
curPos+=dw;
}
//printf("offset:\t%d\ndw:\t%d\n",curPos,dw);
}


  在这里recv返回的dr值竟然是正常的非零正值--从内核读取进缓冲区的字节数! 那么我们自然就会开始认为是recv函数的第一次使用才会返回0,之后的使用不会再出现问题。于是我就用了一个“弄巧成拙的办法”:首先使用bzero函数将缓冲区填充满0,再在缓冲区中寻找以‘\0’为结束标志进行扫描,扫描结束的时候得到缓冲区内实际字节的长度。但是实际测试的时候发现,这个办法用于下载纯粹的txt格式的文件是没有问题的,然而当下载二进制文件例如图片,压缩后文件的时候,就会出现问题!ps:这样下载下来的图片竟然还是偏红的,害得我去图片编码区RBG值寻找BUG真相,浪费了很多时间。

  既然总是返回0,我们来查看一下是否是有错误发生吧,于是加入了<errno.h>,结果输出出来了errno还是0,也就是无错误发生!

最终这个问题的解决过程是这样的:

1.使用wget下载完整的图片。

2.使用 vim -b 图片 和正常的图片进行对比(:%!xxd 查看),发现和正常图片不同之处,在于一些空白0字段的填充

3.进而发现还是第一次recv函数导致的文件内容不对

4.最终问题锁定在了这样一段代码,也是让我最汗颜的:

if(dr=recv(sockdesc,recvBuf,4096,0)==-1){
fprintf(stderr,"Header Recving Failure!\n");
exit(-1);
}


这是recv函数第一次接收读取字节数的代码,也就是这段代码导致了recv读取字节数的不可知,返回值永远为0。相信C语言的老手已经看出来了,这段代码的问题:

关系运算符优先级大于赋值运算符!!!

真正的正确的代码应该是这样写的:

if((dr=recv(sockdesc,recvBuf,4096,0))==-1){
fprintf(stderr,"Header Recving Failure!\n");
exit(-1);
}


C语言写多了,有些代码会越写越简练,比如声明和运算混写,函数参数局部全局混写,但是对于关系运算符的优先级是最不能忽略的,无论是哪个语言,哪怕是运算符关系符最混乱的perl,也要牢牢记住每个优先级和结合性!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: