传输层协议解析
概述
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) {}
isOpen
:用户判断底层传输链路是否是ready的;open
:用于打开底层的传输链路;close
:用于关闭底层传输链路;read
:用于从链路中读取数据;write
:用于往链路中写入数据;flush
:用于将内存中的buffer数据写到链路中;getBufferPosition
:返回链路底层buffer数据当前read位置;getBuffer
:用于返回底层buffer数据getBytesRemainingInBuffer
:用于返回当前底层buffer中还有多少数据没有读取;consumeBuffer
:从底层buffer数据中读取一些数据;
具体传输层协议解析
常用的传输层协议有如下一些:
- TSocket:阻塞式socket;
- TFramedTransport:使用非阻塞方式,以frame为单位进行传输。
- TFileTransport:以文件形式进行传输。
- TMemoryTransport:将内存用于I/O,java实现时内部实际使用了简单的ByteArrayOutputStream。
- TZlibTransport:使用zlib进行压缩, 与其他传输方式联合使用。
- 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
等函数都比较简单,就不赘述,关键看read
,write
和flush
怎么实现的:
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();
}
- read操作:先从readBuffer中读取指定长度的数据,如果读取到的数据长度大于0,说明成功读取到数据,直接返回;如果读不到数据,则需要从帧中load数据,从帧中load数据时,先读出头部的四个字节,表示后面数据的总长度size,然后再读取后面长度的size的数据,存储到readBuffer中。
- write操作:write操作直接调用原声java的接口将数据暂时写到writeBuffer中;
- 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进行网络的传输,上层的作用仅仅是加上一些缓存或者压缩之类的逻辑。