zoukankan      html  css  js  c++  java
  • 【反思】一个价值两天的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首部和实体是混合在一起的,所以就需要我们用 四个字符作为标志来检测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:	%d
    dw:	%d
    ",curPos,dw);
                break;
            }else{
                dw=pwrite(file,recvBuf,dr,curPos);
                curPos+=dw;
            }
            //printf("offset:	%d
    dw:	%d
    ",curPos,dw);
        }

      在这里recv返回的dr值竟然是正常的非零正值--从内核读取进缓冲区的字节数! 那么我们自然就会开始认为是recv函数的第一次使用才会返回0,之后的使用不会再出现问题。于是我就用了一个“弄巧成拙的办法”:首先使用bzero函数将缓冲区填充满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!
    ");
            exit(-1);
        }

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

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

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

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

    C语言写多了,有些代码会越写越简练,比如声明和运算混写,函数参数局部全局混写,但是对于关系运算符的优先级是最不能忽略的,无论是哪个语言,哪怕是运算符关系符最混乱的perl,也要牢牢记住每个优先级和结合性!

     

     

  • 相关阅读:
    hdu6229 Wandering Robots 2017沈阳区域赛M题 思维加map
    hdu6223 Infinite Fraction Path 2017沈阳区域赛G题 bfs加剪枝(好题)
    hdu6438 Buy and Resell 买卖物品 ccpc网络赛 贪心
    hdu6441 Find Integer 求勾股数 费马大定理
    bzoj 1176 Mokia
    luogu 3415 祭坛
    bzoj 1010 玩具装箱
    bzoj 3312 No Change
    luogu 3383【模板】线性筛素数
    bzoj 1067 降雨量
  • 原文地址:https://www.cnblogs.com/guguli/p/4802812.html
Copyright © 2011-2022 走看看