【反思】一个价值两天的BUG,无论工作还是学习C语言的朋友都看看吧!
博文原创,转载请联系博主!
使用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 | 说明 | 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,也要牢牢记住每个优先级和结合性!
【反思】一个价值两天的BUG,无论工作还是学习C语言的朋友都看看吧!的更多相关文章
- 一个诡异的MySQL查询超时问题,居然隐藏着存在了两年的BUG
这一周线上碰到一个诡异的BUG. 线上有个定时任务,这个任务需要查询一个表几天范围内的一些数据做一些处理,每隔十分钟执行一次,直至成功. 通过日志发现,从凌晨5:26分开始到5:56任务执行了三次,三 ...
- 联想ERP项目实施案例分析(10):回到最初再反思IT价值
联想ERP项目实施案例分析(10):回到最初再反思IT价值 投入上千万(未来每年的维护费也非常高),投入一年实施时间,高级副总裁亲自挂帅,各级业务部门管理者亲自负责.骨干业务人员充当区域IT实施者/推 ...
- 一个历时五天的 Bug
一个程序员在没有成长成为架构师之前,几乎都要跟 Bug为伴,程序员有很多时间都是花在了查找各种 Bug上. 我印象深刻的一个Bug, 是一个服务器网络框架无锁队列的 Bug .那个 Bug 连续查找了 ...
- 一个月薪两万的Web安全工程师要掌握哪些技能?
作为一个薪水两万起步的工作,我想知道这些牛人们都会哪些技能呢? Web安全相关概念.熟悉渗透相关工具.渗透实战操作.关注安全圈动态.熟悉Windows/Kali Linux.服务器安全配置.脚本编程学 ...
- 对于长沙互联网发展,一个外来两年Java程序员的所见所感所愿
惟楚有材,于斯为盛 本文有感于2019长沙互联网求职招聘大会,内容比较多,但都是我自己的一些所见.所感和所愿. 2019年3月的最后一天,参加2019长沙互联网求职招聘大会,看到了很多的招聘企业,也看 ...
- Spring 循环引用(一)一个循环依赖引发的 BUG
Spring 循环引用(一)一个循环依赖引发的 BUG Spring 系列目录(https://www.cnblogs.com/binarylei/p/10198698.html) Spring 循环 ...
- android一个下拉放大库bug的解决过程及思考
android一个下拉放大库bug的解决过程及思考 起因 项目中要做一个下拉缩放图片的效果,搜索了下github上面,找到了两个方案. https://github.com/Frank-Zhu/Pul ...
- 从一个跨二十年的glibc bug说起
1. 缘起 这几天调gcc 7.5.0 +glibc 2.23的交叉编译工具链,由于gcc 7.5.0的默认打开Werr,偶然发现了glibc一个隐藏了二十年的世纪大bug. 这个bug在glibc ...
- netty系列之:一个价值上亿的网站速度优化方案
目录 简介 本文的目标 支持多个图片服务 http2处理器 处理页面和图像 价值上亿的速度优化方案 总结 简介 其实软件界最赚钱的不是写代码的,写代码的只能叫马龙,高级点的叫做程序员,都是苦力活.那么 ...
随机推荐
- Rabbitmq消息队列(一) centos下安装rabbitmq
1.简介 AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计.消息中间件主要用于组件之间的解耦,消息的 ...
- js删除cookie的方法
以上图片有两种方法,推荐第二种
- Oracle的substr函数简单用法与substring区别
substr(字符串,截取开始位置,截取长度) //返回截取的字 substr('Hello World',0,1) //返回结果为 'H' *从字符串第一个字符开始截取长度为1的字符串 subst ...
- 微软2016校园招聘在线笔试第二场 题目1 : Lucky Substrings
时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 A string s is LUCKY if and only if the number of different ch ...
- php 微信开发之新增上传/获取下载临时素材
php 微信开发之新增上传/获取下载临时素材 代码 <?php define("AppID","");//你的id define("AppSec ...
- spring boot集成activemq
spring boot集成activemq 转自:https://blog.csdn.net/maiyikai/article/details/77199300
- 浅谈 MVP in Android(转)
我自己写的demo:https://pan.baidu.com/s/1dFImVYD 一.概述 对于MVP(Model View Presenter),大多数人都能说出一二:“MVC的演化版本”,“让 ...
- JavaEE应用基础平台 AOS-V0.1 RELEASED
写在最前面 AOS是一个有着悠久历史传承和发扬的平台.她的前世G4Studio自2010年公布V1.0版本号以来,先后经过多次版本号更新.并得到了一些小伙伴的认可和使用.但我们希望做得更好,走得更远. ...
- git介绍和常用指令
Git介绍和常用指令 介绍:Git和SVN一样都是版本控制工具.不同的是Git是分布式的,SVN是集中式的.Git开始用可能感觉难点,等你用习惯了你就会觉得svn是有点恐怖.(如果一个项目有好多人一起 ...
- Brain Network (medium)(DFS)
H - Brain Network (medium) Time Limit:2000MS Memory Limit:262144KB 64bit IO Format:%I64d &am ...