fcgi4j与PHP-FPM通信遇到的问题

12 篇文章 0 订阅
2 篇文章 1 订阅

一.前言

前一篇文章介绍了一些CGI、FCGI相关的概念,详见 理解CGI、FCGI、php-cgi、php-fpm的概念 。从这篇文章知道PHP-FPM是PHP实现FCGI协议的一个组件,负责FCGI协议编码的请求的解析和转发。
fcgi4j是一个Java解析fcgi协议的组件,其git地址为:fcgi4j github

二.问题

在工作中遇到的问题:
在线上,当PHP-FPM对数据包进行分包,fcgi4j拿到第2个包,去解析header时,出现BufferUnderflowException。

三.排查问题

BufferUnderflowException 就是常说的越界问题。比方说,读取NIO的Buffer时,如果Buffer的remaining为1,却要get超过1B的数据,这是就会越界。其中,Buffer的remaining的源码为:

    /**
     * Returns the number of elements between the current position and the
     * limit.
     *
     * @return  The number of elements remaining in this buffer
     */
    public final int remaining() {
        return limit - position;
    }

注意,读取时,position一般指向0,其中有数据;limit一般指向有效数据的下一个位置,其中没有数据。所以,limit-position就是Buffer中存放的数据字节数。请看下图:
这里写图片描述

既然知道问题所在,看看问题是怎么出现的。通过加日志打印出解析HeaderBuffer时,PHP-FPM返回的HeaderBuffer的内容,以及position、limit信息。
这里有必要说下FCGI协议的Header。 详见这篇博文:FCGI协议的header解析

在问题排查过程中,通过添加log信息,发现读取完数据,后面的解析出了问题,以为是PHP底层的PHP-FPM在分包的过程中出现了问题。因为,一个fcgi消息里可以最多存放65535B的数据,但是从日志中看,一个消息体的长度是8814时就被分包了。所以,希望PHP能够排查下底层的FPM实现。但是FPM是C语言实现的,PHP无法排查。只能在JAVA上继续做文章。
只是从Java代码上看是无法说服是PHP-FPM底层实现出现问题的。如果是PHP-FPM底层的问题,PHP业务方会通过其他途径解决这个问题(任由问题继续,领导来拍板)。所以,需要第三方的解析来看看数据包是不是出现问题。选择了tcpdump+wireshark进行抓包解析。悲剧的是,抓包工具能够正常解析所有数据。所以,是Java的fcgi4j出错了。tcpdump+wireshark抓包的使用,详见这篇博文:tcpdump+wireshark的使用

通过添加日志发现fcgi4j在边界判断上有问题。

private int readStdoutData(ByteBuffer buffer, int available, int padding) throws IOException {
        if (!buffer.hasRemaining()) {
            return 0;
        }

        int read;
        if (buffer.remaining() >= available) {
            int limit = buffer.limit();

            buffer.limit(buffer.position() + available);
            read = IoUtils.socketRread(socketChannel, buffer);
            buffer.limit(limit);

            if (padding != 0) {
                ByteBuffer paddingBuffer = ByteBuffer.allocate(padding);
                IoUtils.socketRread(socketChannel, paddingBuffer);
                paddingBuffer.flip();
                LOG.debug("padding data : " + byteBufferToString(paddingBuffer));
            }
        } else {
            read = IoUtils.socketRread(socketChannel, buffer);
        }

        return read;
    }

上面的if (buffer.remaining() >= available) 当时没有等于号,这样导致当remaining==available时,导致padding读取不到。这样后面解析时就错过了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值