Broken Pipe

Broken Pipe发生的原因

当某个进程试图往一个已收到RST的SOCKET连接写数据,就会出现Broken Pipe。
(由于TCP协议层已经处于RST状态了,因此不会将数据发出,而是发一个SIGPIPE信号给应用层,SIGPIPE信号的缺省处理动作是终止程序。)

那么确定什么时候TCP会发送RST报文段,就可以确定Broken Pipe发生的具体原因。

之前已经分析了TCP RST报文产生的几种情景了。

原因分析

broken pipe出现的前提条件是进程试图往一个已经在RST状态的TCP连接写入数据。

那么这个写入数据,到底应该怎么理解呢?到底是进程试图往本地SendQ发送缓存区写入数据还是TCP协议试图将SendQ的数据发送到对端的RecvQ呢?按照字面意思应该是前者。

之前我们已经分析了几种会出现RST报文的情况。
结合我们出现该异常的接口分析。我们发现我们出错的接口,返回的数据,最小的8K多,最大的超过128K。查阅了几天的异常日志,都没有发现一个报出broken pipe异常错误的接口的返回数据小于8K。

根据RST报文产生的情况,我们可以做出如下推断,当Client端与我们的服务器建立了TCP链接之后。当TCP协议将服务端SendQ队列里的内容发送到对端(Client)的ReceQ队列中后,Client关闭了进程,此时ReceQ 读取缓存区还有数据未被读取(不管ReceQ的数据Client端有没有读取过,也不管TCP将多少服务端的数据发到了ReceQ,总之,就是在关闭的时候,还有数据存在于读取缓存区中),这时候关闭socket,会导致端Client端产生一个RST重置报文。
这时候服务端的数据还没有写完,会继续写入,当再次写入的时候,TCP协议已经是RST状态的了,这个时候,就会发生broken pipe。

上面的推论能够解释broken pipe发生的一整个流程。

那我们来查看下linux服务器的默认SendQ大小与我们接口返回的数据大小的对比,就能够是否确实是这些接口的数据写入都需要多次写入缓存区。

那么如何查看linux默认的SendQ缓冲区大小呢。linux给我们提供了相应的命令

ubuntu@VM-104-50-ubuntu:/data/iyourcar$ cat /proc/sys/net/ipv4/tcp_wmem
4096    16384   4194304

最小    默认     最大

我们发现,默认写缓存区大小是16k,可是我们的接口返回的数据最小的是8k多呀,这个接口返回的数据是可以一次写入缓存区的呀。咋回事呀,怎么这样也会broken呢。如果服务端一次性写入16k数据到写缓存区,那么是不可能出现broken pipe的呀。那只能证明我们的程序并不是一次性写入16k的数据给缓存区,这个大小肯定是要比8k多要小的。那我们就来求证一下,用一个会报出异常的接口在出现异常的地方进行debug,主要debug写入数据的流程。

使用的内置容器是Tomcat

OutputBuffer#appendByteArray


 public static final int DEFAULT_BUFFER_SIZE = 8 * 1024;

  public OutputBuffer() {

        this(DEFAULT_BUFFER_SIZE);

    }
   private void appendByteArray(byte src[], int off, int len) throws IOException {
        if (len == 0) {
            return;
        }

        int limit = bb.capacity();
        //我们发现,每一次写入的字节数bb.capacity()大小,而默认的capacity大小就是8*1024,也就是8k
        while (len >= limit) {
            realWriteBytes(ByteBuffer.wrap(src, off, limit));
            len = len - limit;
            off = off + limit;
        }

        if (len > 0) {
            transfer(src, off, len, bb);
        }
    }

我们发现,实际上,tomcat帮我们向socket写入数据的时候,是每8k写入一次SendQ(但是真正TCP发送数据,可能是分很多块去发送到对端的ReceQ的)

后来我们将内置web容器换成了undertow,发现依旧会发生这种情况,应该默认也是一次写入8k吧,具体还没有debug。

所以当使用的容器是tomcat的时候,只要接口返回数据的大小大于8k,就可能会出现broken pipe。

猜你喜欢

转载自blog.csdn.net/BryantLmm/article/details/81671580