传输层协议解析

概述

Thrift源码解析(二)序列化协议一文中介绍了thrift中传输的数据流怎么序列化,本文介绍数据流怎么传输。如 Thrift源码解析(一)主要类概述一文中的类继承图所示,thrift中所有的传输层协议的基类是TTransport。另外,需要说明的一点是,thrift是基于TCP协议的。

基类剖析

先看看TTransport这个基类有哪些common的抽象函数:

/**
   * Queries whether the transport is open.
   *
   * @return True if the transport is open.
   */
  public abstract boolean isOpen();

  /**
   * Is there more data to be read?
   *
   * @return True if the remote side is still alive and feeding us
   */
  public boolean peek() {
    return isOpen();
  }

  /**
   * Opens the transport for reading/writing.
   *
   * @throws TTransportException if the transport could not be opened
   */
  public abstract void open()
    throws TTransportException;

  /**
   * Closes the transport.
   */
  public abstract void close();

  /**
   * Reads up to len bytes into buffer buf, starting at offset off.
   *
   * @param buf Array to read into
   * @param off Index to start reading at
   * @param len Maximum number of bytes to read
   * @return The number of bytes actually read
   * @throws TTransportException if there was an error reading data
   */
  public abstract int read(byte[] buf, int off, int len)
    throws TTransportException;

  /**
   * Guarantees that all of len bytes are actually read off the transport.
   *
   * @param buf Array to read into
   * @param off Index to start reading at
   * @param len Maximum number of bytes to read
   * @return The number of bytes actually read, which must be equal to len
   * @throws TTransportException if there was an error reading data
   */
  public int readAll(byte[] buf, int off, int len)
    throws TTransportException {
    int got = 0;
    int ret = 0;
    while (got < len) {
      ret = read(buf, off+got, len-got);
      if (ret <= 0) {
        throw new TTransportException(
            "Cannot read. Remote side has closed. Tried to read "
                + len
                + " bytes, but only got "
                + got
                + " bytes. (This is often indicative of an internal error on the server side. Please check your server logs.)");
      }
      got += ret;
    }
    return got;
  }

  /**
   * Writes the buffer to the output
   *
   * @param buf The output data buffer
   * @throws TTransportException if an error occurs writing data
   */
  public void write(byte[] buf) throws TTransportException {
    write(buf, 0, buf.length);
  }

  /**
   * Writes up to len bytes from the buffer.
   *
   * @param buf The output data buffer
   * @param off The offset to start writing from
   * @param len The number of bytes to write
   * @throws TTransportException if there was an error writing data
   */
  public abstract void write(byte[] buf, int off, int len)
    throws TTransportException;

  /**
   * Flush any pending data out of a transport buffer.
   *
   * @throws TTransportException if there was an error writing out data.
   */
  public void flush()
    throws TTransportException {}

  /**
   * Access the protocol's underlying buffer directly. If this is not a
   * buffered transport, return null.
   * @return protocol's Underlying buffer
   */
  public byte[] getBuffer() {
    return null;
  }

  /**
   * Return the index within the underlying buffer that specifies the next spot
   * that should be read from.
   * @return index within the underlying buffer that specifies the next spot
   * that should be read from
   */
  public int getBufferPosition() {
    return 0;
  }

  /**
   * Get the number of bytes remaining in the underlying buffer. Returns -1 if
   * this is a non-buffered transport.
   * @return the number of bytes remaining in the underlying buffer. <br> Returns -1 if
   * this is a non-buffered transport.
   */
  public int getBytesRemainingInBuffer() {
    return -1;
  }

  /**
   * Consume len bytes from the underlying buffer.
   * @param len
   */
  public void consumeBuffer(int len) {}
  1. isOpen:用户判断底层传输链路是否是ready的;
  2. open:用于打开底层的传输链路;
  3. close:用于关闭底层传输链路;
  4. read:用于从链路中读取数据;
  5. write:用于往链路中写入数据;
  6. flush:用于将内存中的buffer数据写到链路中;
  7. getBufferPosition:返回链路底层buffer数据当前read位置;
  8. getBuffer:用于返回底层buffer数据
  9. getBytesRemainingInBuffer:用于返回当前底层buffer中还有多少数据没有读取;
  10. consumeBuffer:从底层buffer数据中读取一些数据;

具体传输层协议解析

常用的传输层协议有如下一些:

  1. TSocket:阻塞式socket;
  2. TFramedTransport:使用非阻塞方式,以frame为单位进行传输。
  3. TFileTransport:以文件形式进行传输。
  4. TMemoryTransport:将内存用于I/O,java实现时内部实际使用了简单的ByteArrayOutputStream。
  5. TZlibTransport:使用zlib进行压缩, 与其他传输方式联合使用。
  6. TNonblockingTransport:使用非阻塞方式,用于构建异步客户端

本文以TFramedTransport为例,详细介绍TFramedTransport的传输流程。

下面是TFramedTransport的源码:

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.thrift.transport;

import org.apache.thrift.TByteArrayOutputStream;

/**
 * TFramedTransport is a buffered TTransport that ensures a fully read message
 * every time by preceding messages with a 4-byte frame size.
 */
public class TFramedTransport extends TTransport {

    protected static final int DEFAULT_MAX_LENGTH = 16384000;

    private int maxLength_;

    /**
     * Underlying transport
     */
    private TTransport transport_ = null;

    /**
     * Buffer for output
     */
    private final TByteArrayOutputStream writeBuffer_ =
            new TByteArrayOutputStream(1024);

    /**
     * Buffer for input
     */
    private TMemoryInputTransport readBuffer_ = new TMemoryInputTransport(new byte[0]);

    public static class Factory extends TTransportFactory {
        private int maxLength_;

        public Factory() {
            maxLength_ = TFramedTransport.DEFAULT_MAX_LENGTH;
        }

        public Factory(int maxLength) {
            maxLength_ = maxLength;
        }

        @Override
        public TTransport getTransport(TTransport base) {
            return new TFramedTransport(base, maxLength_);
        }
    }

    /**
     * Constructor wraps around another transport
     */
    public TFramedTransport(TTransport transport, int maxLength) {
        transport_ = transport;
        maxLength_ = maxLength;
    }

    public TFramedTransport(TTransport transport) {
        transport_ = transport;
        maxLength_ = TFramedTransport.DEFAULT_MAX_LENGTH;
    }

    public void open() throws TTransportException {
        transport_.open();
    }

    public boolean isOpen() {
        return transport_.isOpen();
    }

    public void close() {
        transport_.close();
    }

    public int read(byte[] buf, int off, int len) throws TTransportException {
        if (readBuffer_ != null) {
            int got = readBuffer_.read(buf, off, len);
            if (got > 0) {
                return got;
            }
        }

        // Read another frame of data
        readFrame();

        return readBuffer_.read(buf, off, len);
    }

    @Override
    public byte[] getBuffer() {
        return readBuffer_.getBuffer();
    }

    @Override
    public int getBufferPosition() {
        return readBuffer_.getBufferPosition();
    }

    @Override
    public int getBytesRemainingInBuffer() {
        return readBuffer_.getBytesRemainingInBuffer();
    }

    @Override
    public void consumeBuffer(int len) {
        readBuffer_.consumeBuffer(len);
    }

    private final byte[] i32buf = new byte[4];

    private void readFrame() throws TTransportException {
        transport_.readAll(i32buf, 0, 4);
        int size = decodeFrameSize(i32buf);

        if (size < 0) {
            close();
            throw new TTransportException(TTransportException.CORRUPTED_DATA, "Read a negative frame size (" + size + ")!");
        }

        if (size > maxLength_) {
            close();
            throw new TTransportException(TTransportException.CORRUPTED_DATA,
                    "Frame size (" + size + ") larger than max length (" + maxLength_ + ")!");
        }

        byte[] buff = new byte[size];
        transport_.readAll(buff, 0, size);
        readBuffer_.reset(buff);
    }

    public void write(byte[] buf, int off, int len) throws TTransportException {
        writeBuffer_.write(buf, off, len);
    }

    @Override
    public void flush() throws TTransportException {
        byte[] buf = writeBuffer_.get();
        int len = writeBuffer_.len();
        writeBuffer_.reset();

        encodeFrameSize(len, i32buf);
        transport_.write(i32buf, 0, 4);
        transport_.write(buf, 0, len);
        transport_.flush();
    }

    public static final void encodeFrameSize(final int frameSize, final byte[] buf) {
        buf[0] = (byte) (0xff & (frameSize >> 24));
        buf[1] = (byte) (0xff & (frameSize >> 16));
        buf[2] = (byte) (0xff & (frameSize >> 8));
        buf[3] = (byte) (0xff & (frameSize));
    }

    public static final int decodeFrameSize(final byte[] buf) {
        return
                ((buf[0] & 0xff) << 24) |
                        ((buf[1] & 0xff) << 16) |
                        ((buf[2] & 0xff) << 8) |
                        ((buf[3] & 0xff));
    }
}

可以看到,TFramedTransport中有一个成员变量private TTransport transport_ = null;,这个表示更底层的TTransport,比如TSocket,TFramedTransport的IO操作最终都会交给这个底层的Transport来执行。

private final TByteArrayOutputStream writeBuffer_ = new TByteArrayOutputStream(1024);

这个writeBuffer_从名字就可以看出来是用于write操作的buffer的。

private TMemoryInputTransport readBuffer_ = new TMemoryInputTransport(new byte[0]);

同样地,也有一个readBuffer_用于将从底层Transport中读出的数据暂存到里面。

接下来,提供一个Factory工厂类,该类的作用就是用于创建TFramedTransport实例。

接下来的open,isOpen,close,getBuffer等函数都比较简单,就不赘述,关键看readwriteflush怎么实现的:

public int read(byte[] buf, int off, int len) throws TTransportException {
        if (readBuffer_ != null) {
            int got = readBuffer_.read(buf, off, len);
            if (got > 0) {
                return got;
            }
        }

        // Read another frame of data
        readFrame();

        return readBuffer_.read(buf, off, len);
    }

    private void readFrame() throws TTransportException {
        transport_.readAll(i32buf, 0, 4);
        int size = decodeFrameSize(i32buf);

        if (size < 0) {
            close();
            throw new TTransportException(TTransportException.CORRUPTED_DATA, "Read a negative frame size (" + size + ")!");
        }

        if (size > maxLength_) {
            close();
            throw new TTransportException(TTransportException.CORRUPTED_DATA,
                    "Frame size (" + size + ") larger than max length (" + maxLength_ + ")!");
        }

        byte[] buff = new byte[size];
        transport_.readAll(buff, 0, size);
        readBuffer_.reset(buff);
    }

    public void write(byte[] buf, int off, int len) throws TTransportException {
        writeBuffer_.write(buf, off, len);
    }

    @Override
    public void flush() throws TTransportException {
        byte[] buf = writeBuffer_.get();
        int len = writeBuffer_.len();
        writeBuffer_.reset();

        encodeFrameSize(len, i32buf);
        transport_.write(i32buf, 0, 4);
        transport_.write(buf, 0, len);
        transport_.flush();
    }
  1. read操作:先从readBuffer中读取指定长度的数据,如果读取到的数据长度大于0,说明成功读取到数据,直接返回;如果读不到数据,则需要从帧中load数据,从帧中load数据时,先读出头部的四个字节,表示后面数据的总长度size,然后再读取后面长度的size的数据,存储到readBuffer中。
  2. write操作:write操作直接调用原声java的接口将数据暂时写到writeBuffer中;
  3. flush操作:flush操作才是真正将内存缓存中的数据通过网络发送出去的操作,首先计算writeBuffer的总长度size,写到头部的四个字节中,然后再将全量数据写到剩余的size个字节中,最后调用底层Transport(比如TSocket)的flush操作将数据发送出去。显然,这个操作和read操作是相反的~

所以总结一下:TFramedTransport的数据流形式是:4字节数据长度+ 数据

一般来说,TFramedTransport都是配合TSocket使用的,下面就看看使用最多的最基本的TSocket的实现细节:

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.apache.thrift.transport;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;

/**
 * Socket implementation of the TTransport interface. To be commented soon!
 */
public class TSocket extends TIOStreamTransport {

    private static final Logger LOGGER = LoggerFactory.getLogger(TSocket.class.getName());

    /**
     * Wrapped Socket object
     */
    private Socket socket_;

    /**
     * Remote host
     */
    private String host_;

    /**
     * Remote port
     */
    private int port_;

    /**
     * Socket timeout - read timeout on the socket
     */
    private int socketTimeout_;

    /**
     * Connection timeout
     */
    private int connectTimeout_;

    /**
     * Constructor that takes an already created socket.
     *
     * @param socket Already created socket object
     * @throws TTransportException if there is an error setting up the streams
     */
    public TSocket(Socket socket) throws TTransportException {
        socket_ = socket;
        try {
            socket_.setSoLinger(false, 0);
            socket_.setTcpNoDelay(true);
            socket_.setKeepAlive(true);
        } catch (SocketException sx) {
            LOGGER.warn("Could not configure socket.", sx);
        }

        if (isOpen()) {
            try {
                inputStream_ = new BufferedInputStream(socket_.getInputStream(), 1024);
                outputStream_ = new BufferedOutputStream(socket_.getOutputStream(), 1024);
            } catch (IOException iox) {
                close();
                throw new TTransportException(TTransportException.NOT_OPEN, iox);
            }
        }
    }

    /**
     * Creates a new unconnected socket that will connect to the given host
     * on the given port.
     *
     * @param host Remote host
     * @param port Remote port
     */
    public TSocket(String host, int port) {
        this(host, port, 0);
    }

    /**
     * Creates a new unconnected socket that will connect to the given host
     * on the given port.
     *
     * @param host    Remote host
     * @param port    Remote port
     * @param timeout Socket timeout and connection timeout
     */
    public TSocket(String host, int port, int timeout) {
        this(host, port, timeout, timeout);
    }

    /**
     * Creates a new unconnected socket that will connect to the given host
     * on the given port, with a specific connection timeout and a
     * specific socket timeout.
     *
     * @param host           Remote host
     * @param port           Remote port
     * @param socketTimeout  Socket timeout
     * @param connectTimeout Connection timeout
     */
    public TSocket(String host, int port, int socketTimeout, int connectTimeout) {
        host_ = host;
        port_ = port;
        socketTimeout_ = socketTimeout;
        connectTimeout_ = connectTimeout;
        initSocket();
    }

    /**
     * Initializes the socket object
     */
    private void initSocket() {
        socket_ = new Socket();
        try {
            socket_.setSoLinger(false, 0);
            socket_.setTcpNoDelay(true);
            socket_.setKeepAlive(true);
            socket_.setSoTimeout(socketTimeout_);
        } catch (SocketException sx) {
            LOGGER.error("Could not configure socket.", sx);
        }
    }

    /**
     * Sets the socket timeout and connection timeout.
     *
     * @param timeout Milliseconds timeout
     */
    public void setTimeout(int timeout) {
        this.setConnectTimeout(timeout);
        this.setSocketTimeout(timeout);
    }

    /**
     * Sets the time after which the connection attempt will time out
     *
     * @param timeout Milliseconds timeout
     */
    public void setConnectTimeout(int timeout) {
        connectTimeout_ = timeout;
    }

    /**
     * Sets the socket timeout
     *
     * @param timeout Milliseconds timeout
     */
    public void setSocketTimeout(int timeout) {
        socketTimeout_ = timeout;
        try {
            socket_.setSoTimeout(timeout);
        } catch (SocketException sx) {
            LOGGER.warn("Could not set socket timeout.", sx);
        }
    }

    /**
     * Returns a reference to the underlying socket.
     */
    public Socket getSocket() {
        if (socket_ == null) {
            initSocket();
        }
        return socket_;
    }

    /**
     * Checks whether the socket is connected.
     */
    public boolean isOpen() {
        if (socket_ == null) {
            return false;
        }
        return socket_.isConnected();
    }

    /**
     * Connects the socket, creating a new socket object if necessary.
     */
    public void open() throws TTransportException {
        if (isOpen()) {
            throw new TTransportException(TTransportException.ALREADY_OPEN, "Socket already connected.");
        }

        if (host_ == null || host_.length() == 0) {
            throw new TTransportException(TTransportException.NOT_OPEN, "Cannot open null host.");
        }
        if (port_ <= 0 || port_ > 65535) {
            throw new TTransportException(TTransportException.NOT_OPEN, "Invalid port " + port_);
        }

        if (socket_ == null) {
            initSocket();
        }

        try {
            socket_.connect(new InetSocketAddress(host_, port_), connectTimeout_);
            inputStream_ = new BufferedInputStream(socket_.getInputStream(), 1024);
            outputStream_ = new BufferedOutputStream(socket_.getOutputStream(), 1024);
        } catch (IOException iox) {
            close();
            throw new TTransportException(TTransportException.NOT_OPEN, iox);
        }
    }

    /**
     * Closes the socket.
     */
    public void close() {
        // Close the underlying streams
        super.close();

        // Close the socket
        if (socket_ != null) {
            try {
                socket_.close();
            } catch (IOException iox) {
                LOGGER.warn("Could not close socket.", iox);
            }
            socket_ = null;
        }
    }

}

大致概括一下:TSocket继承自TIOStreamTransport,底层使用的还是原生的Socket,同时维护一个输入流和一个输出流(其实就是socket的inputStream和outputStream),read的时候从输入流读取数据,write的时候从输出流写入数据。上层的Transport在底层基本都是使用TSocket进行网络的传输,上层的作用仅仅是加上一些缓存或者压缩之类的逻辑。