博文原创,转载请联系博主!linux
使用C语言也有两个年头了,BUG写出来过很多,也改过很多BUG。可是恰恰就是有这么一个BUG让我手头的项目停工了两天,缘由从百度找到谷歌,资料从MAN手册找到RFC也没有找到问题的缘由,可是真正发现BUG缘由以后实在是让本身汗颜。git
无论如何,决定把这个BUG写进博文,也是给学习C语言的朋友们提个醒,查看BUG的眼光不要过高,思考问题要自底向上思考。github
具体项目在个人github里: https://github.com/yue9944882/HttpAccelerater编程
正文:vim
问题大体发现是这样的,在这个HTTP下载器中,实质上编程逻辑都是在传输层TCP套接字上完成的,具体细节就很少提,在通信过程当中是经过一对send和recv函数完成的,recv函数原型以下所示:(linux环境<sys/socket.h>)服务器
int recv( _In_ SOCKET s, _Out_ char *buf, _In_ int len, _In_ int flags);socket
sockfd: 接收端套接字描述符函数
buff: 用来存放recv函数接收到的数据的缓冲区学习
nbytes: 指明buff的长度测试
flags: 通常置为0
返回值: recv函数返回其实际copy的字节数
flags | 说明 | recv | send |
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,也要紧紧记住每一个优先级和结合性!