前面一篇文章中我们分析了Okhttp的底层请求创建连接过程,其中提到了两个数据结构Sink和Source,它们是用来在连接建立好后,进行数据传输的,可以提高数据的传输性能。那么这两个数据结构究竟是什么呢?它们是怎么提高传输效率的呢?本篇文章将对其进行深度的剖析和分析。
实际上oKhttp底层的通信依赖于okio这个独立的库,这个库相对于java的IO来说,拥有许多特性,使其在性能上非常出色,而且也易于上手。okio库中有几个重要的接口和类,它们是支撑整个库的基础,下面先简要介绍一下:
- Sink,Source:面向流的,用于完成数据传输的接口
- BufferedSink,BufferedSource:面向缓冲的数据传输接口
- Buffer:缓冲对象
- RealBufferedSink,RealBufferedSource:面向缓冲的数据传输对象实现类
sink和source
先来看下Sink的接口定义:
public interface Sink extends Closeable, Flushable {
//从source中将byteCount大小的数据写到当前sink中
void write(Buffer source, long byteCount) throws IOException;
//强制将当前sink中存储的数据写到目的端
@Override void flush() throws IOException;
Timeout timeout();
//关闭sink,在关闭之前会先将数据写出到目的端
@Override void close() throws IOException;
}
从接口的定义上,我们可以看出sink是一个用于将buffer中的数据传输到目的端的数据结构,官方的解释:Receives a stream of bytes. Use this interface to write data wherever it's needed: to the network, storage, or a buffer in memory. Sinks may be layered to transform received data, such as to compress, encrypt, throttle, or add protocol framing,翻译过来大概是说:它用来接收字节流。使用此接口你可已把数据写入到需要的地方,例如网络,存储设备或缓存。sink还可以对接收到的数据进行分层处理,例如压缩,加密,限制或添加协议帧。简单点理解,你可以认为它就是一用来处理发送数据面向流的结构。具体的操作由write(Buffer source, long byteCount)方法完成。
Source同理,这里我们就不具体解释了,它的作用就是用来对接收的数据进行处理的数据结构。其接口定义如下:
public interface Source extends Closeable {
//从当前source中读取byteCount大小的数据到sink
long read(Buffer sink, long byteCount) throws IOException;
Timeout timeout();
@Override void close() throws IOException;
}
数据的操作由read(Buffer sink, long byteCount)方法完成
BufferedSouce和BufferedSink
BufferedSouce和BufferedSink接口分别继承了Soure和Sink接口,使其在在原有面向流的接口之上,有了面向缓冲的功能。
Buffer
Buffer是BufferedSource和BufferedSink的实现类。其实现了面向缓冲的read和write方法,具体依赖于一个叫做Segment的对象。
RealBufferedSink和RealBufferedSource
这两个类是真正的实现类,内部依赖于Buffer和Sink,Source。其工作原理如下图所示:
- 请求的传输是在Http1Codec对象的writeRequestHeaders方法中发起的,其中又调用了writeRequest(request.headers(), requestLine)方法来请求;
- 在writeRequest方法中,调用了sink的writeUtf8的方法,改方法对传输的内容进行UTF8编码,写入最终要进行数据传输的sink。这里的sink是在RealConnection类的connectSocket方法中创建:sink = Okio.buffer(Okio.sink(rawSocket))。这里创建的是一个带缓冲的sink,即RealBufferdSink;
- 在writeUtf8方法中,会将数据写入buffer的segment结构中,然后再将segment缓冲中的数据通过socket传输到远端
@Override public BufferedSink writeUtf8(String string) throws IOException {
if (closed) throw new IllegalStateException("closed");
//数据写入buffer
buffer.writeUtf8(string);
//读取buffer中的内容,通过socket写出
return emitCompleteSegments();
}
其实整个OKio库的核心在于它的缓冲机制,其底层的通信依然依赖于java中的流:即inputStream和outputStream。该库对这两个流进行了二次封装,通过加入缓冲来减少io访问的次数,从而提高了通信效率。
总结
实际上,上面提到的几种数据类型是整个框架底层通信的基础。在原有socket的outputStream和inputStream的基础之上,抽象出了Sink和Source的类对象。它们是面向流的,为了提高传输效率,又在此基础之上引入了Buffer,可以将原来的Sink和source封装为面向缓冲的对象。依赖于缓冲内部高效的segment数据结构,数据的输入和输出都会经过此缓冲对象的处理,从而提高了数据的传输效率。下面一节将会深入缓冲结构内部,重点分析segment的工作机制。