goahead库64位移植--无法接收http消息问题记录

前言

发现又有相当长的一段时间未更新博客了,恰巧最近在进行32bit到64bit位的移植,而正在一过程中还是碰到了许多有趣的问题,因此,将这些问题贴在这,一方面方便以后查看,一方面也希望能够给别人带来点启示,废话不多说,现在开始

问题

goahead库作为嵌入式的一个webServer,相信很多人都不陌生,而这次,我们需要将goahead库移植到armv8的64位系统上,在进行常规的交叉编译成功生成静态库并链接至应用后,发现使用了goahead库的应用在64位的系统上,无法正常接收http请求。

解决过程

整个解决过程主要总结为以下的三部分:

1.网络包和应用日志分析:
遇到这个问题后,我们一开始以为是在tcp建立连接的时候出现了问题,因此利用wireshark进行抓包分析后发现:tcp三次握手正常,且client端正常发出了http请求,且http请求的报文段ACK也返回了客户端,但是应用却没有返回任何http响应。且查看应用日志发现,上层应用并未从goahead库中接收到任何http请求,因此也就自然无法返回响应。通过这两点说明,问题出现在goahead库在tcp三次握手成功后对数据的读取上。
2. GDB调试:
在确定了问题出现在三次握手后,利用GDB进行调试,但是在整个调试过程中,流程均依照正常的步骤进行,并且调试时的http的请求被上层应用正常接受,并返回了响应。当但不再使用GDB调试的时,问题又依然出现。
3. 最终问题原因:
利用GDB调试的过程中,问题并未出现,从这点线索上,在仔细阅读了goahead库的代码时发现,goahead整个过程的处理大概如下(采用的是select的IO多路复用):

socketSelect(事件轮询)--》socketProcess(处理事件)--
socketAccept(接受连接)--》websAccept(连接事情处理回调)--
socketEvent(连接成功后的socket事件加入监听)--》readEvent(处理读事件)

其在作为http-server时监听端口收到新的连接的处理流程大致如上,当进行GDB时,问题不出现的原因出现在函数:readEvent,在该函数中,会尝试先对准备加入读事件的socket进行读取,如果没有数据能够被读取时,该socket才会被加入到select的监听事件中。但是问题就出在当我们进行GDB调试的过程中,进程会被阻塞暂停,此时读取缓冲区已经将client的数据缓存起来,当GDB的调试继续往下时,缓冲区的数据能够被读取,这就导致了后续http请求能够被正确的发生到上层应用。

static void readEvent(Webs *wp)
{
    .......
    /**
    * 在进行GDB调试时,进程被短暂的暂停,此时读取缓存区已经缓存有客户端数据,读取成功,会调用函数:websPump 进行包的解析和发生至应用层
    **/
    if ((nbytes = websRead(wp, (char*) rxbuf->endp, ME_GOAHEAD_LIMIT_BUFFER)) > 0) {
        wp->lastRead = nbytes;
        bufAdjustEnd(rxbuf, nbytes);
        bufAddNull(rxbuf);
    }
    if (nbytes > 0 || wp->state > WEBS_BEGIN) {
        websPump(wp);
    }
........
}

这一现象证明,数据能够被正常接受,且能够被正常读取,而与GDB调试唯一不同的步骤是:当不再进行调试时,缓冲区不存在数据,此时连接socket会被正常加入到select的监听事件中,后续在轮询的过程中继续读取。因此,初步怀疑是因为select进行事件轮询的时候没有正确的处理应该进行后续读取的事件。
通过对轮询事件的处理的代码阅读以及GDB调试,终于确定了问题所在(当然这个过程并没有很顺利的就确定了,由于过程繁琐,因此就不再赘述了):

PUBLIC int socketSelect(int sid, int timeout)
{
    .......
    /**
    * 64bit中:
    * NBBY:8
    * sizeof(fd_mask):8
    **/
    index = sp->sock / (NBBY * sizeof(fd_mask));
    bit = 1 << (sp->sock % (NBBY * sizeof(fd_mask)));
    .......
}

熟悉select的人都知道,select使用的是描述符集,描述符集通常表示为一个整数的数组(fd_set),其整数数组不同的下标表示不同的范围(在64系统下,如数组下标index=0时,数组元素对应的是:0~63,如果描述符为32,就会在index=0的第32位上),而在goahead中,并未使用系统提供的宏定义:

void FD_ZERO(fd_set *fdset);
void FD_SET(int fd, fd_set *fdset);
void FD_ISSET(nt fd, fd_set *fdset);
void FD_CLR(fd_set *fdset);

goahead自定义实现了上述的这几个宏定义,也就是函数:socketSelect 中列出的,但是由于在不同的系统上,下面的代码存在着问题:

/**
* 不同的系统上,常数1会表示为不同的类型
**/
bit = 1 << (sp->sock % (NBBY * sizeof(fd_mask)));

如注释说明的一样,恰好常数1在我们移植的系统上表示为了32bit的int型整数,因此上述在进行大于32bit的移位时会出现溢出,这也就导致了select的描述符集没有正常添加描述符,也就导致了后续的读事件的socket无法被正常监听到并触发。

解决办法

在确定了问题的原因后,采用了后缀的方式明确了常数1的类型:

bit = 1UL << (sp->sock % (NBBY * sizeof(fd_mask)));

至此,将上述应该的修改的地方都修改后,该问题也就正常解决了。

总结

整个问题的解决过程其实也并没有博文这么清晰明了,期间还是走了错路,而想要说明的是,对于疑难的问题,大胆的怀疑是应该,只有先确定一个怀疑点后,去进行验证的过程中也许会推翻前一个怀疑,但是也有可能会给你提供一条新的线索。因此怀疑,验证,再怀疑是解决问题一个个人觉得最靠谱的解法(纯属个人看法)。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值