目录
Okio的诞生
Okio是用来对数据进行存储和处理IO数据。
InputStream/ OutputStream | 传统的IO,阻塞式IO操作,一直等到有数据才会返回。 |
NIO | 非阻塞式IO。数据从通道(Channel)读取到缓冲区(Buffer),也可以从Buffer读取到Channel中。而Selector允许单线程处理多个通道,用来选出一个可用的通道。 |
Okio | 增加了缓存机制、超时机制来实现快速访问、存储和处理IO数据 |
OKio的简单介绍
缓存模块
由Buffer、Segment、SegmentPool组成。Buffer采用的是有Segment组成的循环链表,来缓存数据;SegmentPool存放的是暂时不用的Segment的单链表,防止频繁进行删除数据操作
超时机制
1)Timeout
在处理InputStream和OutputStream时,传入的超时类,在使用Okio进行读写操作的时候,如果超时就通timeout.throwIfReached()抛出异常。
2)AsyncTimeout
异步超时类,通过一个线程在后台监听Socket是否有超时的操作。
几个重要的类
1)Source/Sink:
接口类。代表输入流和输出流,类似于InputStream/OutputStream,用来进行读写数据;
2)BufferedSource/BufferedSink:
分别继承Source/Sink,扩展了读写功能;
3)RealBufferedSource/RealBufferedSink:
BufferedSource/BufferedSink的实现类,用来完成数据的读写操作。查看里面的源码中可以发现,其实里面就是含有一个Source/Sink对象的代理和一个Buffer对象,真正的去完成读写操作的就是这个Buffer。
简单的读写操作
在使用Okio在进行读写文件的时候,首先要将文件转换成一个Source/Sink,然后在换成BufferedSource/BufferedSink,最后通过API直接读写数据
//获取文件
File file = new File(fileName);
//将file转换成Source
Source source = Okio.source(file);
//将source转换成BufferedSource
BufferedSource buff = Okio.buffer(source);
//读取BufferedSource里面的内容
String result = buff.readString(Charset.forName("utf-8"));
一个简单的java+socket来实现请求服务器
在用java的Socket来进行实现请求服务器的过程,向服务器发送数据就是从socket去取出OutputStream,然后将请求数据写入OutputStream;同样读取服务器返回的数据时,就是从socket中取出InputStream,然后从InputStream数据中读取即可。下面是简单的代码实例
1)向服务器发送数据
//向服务器发送数据
OutputStream outputStream = socket.getOutputStream();
outputStream.write("写数据".getBytes("UTF-8"));
outputStream.close();
2)接收服务器返回的数据
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//然后就等着服务器发送过数据之后,读取服务器发来的信息
String line = null;
StringBuffer buffer = new StringBuffer();
while ((line = bufferedReader.readLine()) != null) {
buffer.append(line);
}
在CacheInterceptor的运用
1)写请求的头部header
httpCodec.writeRequestHeaders(request);
这个httpCodec就是Http1Codec或者Http2Codec的实例。为了方便描述,我们拿Http1Codec举例说明。进入到Http1Codec源码中可以看到,最终调用的是下面的这个方法:
public void writeRequest(Headers headers, String requestLine) throws IOException {
if (state != STATE_IDLE) throw new IllegalStateException("state: " + state);
sink.writeUtf8(requestLine).writeUtf8("\r\n");
for (int i = 0, size = headers.size(); i < size; i++) {
sink.writeUtf8(headers.name(i))
.writeUtf8(": ")
.writeUtf8(headers.value(i))
.writeUtf8("\r\n");
}
sink.writeUtf8("\r\n");
state = STATE_OPEN_REQUEST_BODY;
}
其实就是往sink里面写入了请求头header的数据。 而这个sink是在RealConnection中创建链接通道的时,在实例化Http1Codec的时候,从RealConnection中传入的,而sink的实例化也在RealConnection中。
private void connectSocket(int connectTimeout, int readTimeout, Call call,
EventListener eventListener) throws IOException {
//...代码省略
source = Okio.buffer(Okio.source(rawSocket));
sink = Okio.buffer(Okio.sink(rawSocket));
//...代码省略
}
进入到Okio中的sink()方法中,可以看到
public static Sink sink(Socket socket) throws IOException {
//.....代码省略
Sink sink = sink(socket.getOutputStream(), timeout);
return timeout.sink(sink);
}
就是从socket中获取输出流进行转换成Sink,在经过buffer(),最终返回的是RealBufferedSink
public static BufferedSink buffer(Sink sink) {
return new RealBufferedSink(sink);
}
所以我们在Http1Codec中的sink其实就是RealBufferedSink的对象实例。我们往sink中写入信息,其实就是写到了Buffer的缓存中。
2)写请求体body的数据
CountingSink requestBodyOut =
new CountingSink(httpCodec.createRequestBody(request, contentLength));
BufferedSink bufferedRequestBody = Okio.buffer(requestBodyOut);
request.body().writeTo(bufferedRequestBody);
bufferedRequestBody.close();
和写header的方式一样,先通过httpCodec.createRequestBody()来获取到请求body的数据,然后将数据转换成Sink,再将sink转换成BufferedSink,然后就是调用request().body().writeTo()方法进行将请求体body写入Buffer缓存中。看下request().body()返回的值body该对象就是在通过建造者模式创建Request的时候,在build()方法
Request build() {
//.....代码省略
RequestBody body = this.body;
if (body == null) {
// Try to pull from one of the builders.
if (formBuilder != null) {
body = formBuilder.build();
} else if (multipartBuilder != null) {
body = multipartBuilder.build();
} else if (hasBody) {
// Body is absent, make an empty body.
body = RequestBody.create(null, new byte[0]);
}
}
//.....代码省略
}
假设返回的是MultipartBody,那么最终调用的就是MultipartBody里面的writeTo()方法,进入到源码中可以看到也就是调用sink.write()来将body写入到Buffer的缓存中。
3)向服务器发送数据
httpCodec.finishRequest();
真正的将数据发送给服务器。进入到Http1Codec看下finishRequest()源码:
@Override public void finishRequest() throws IOException {
sink.flush();
}
调用的是RealBufferedSink的flush(),进入到RealBufferedSink中查看源码
@Override public void flush() throws IOException {
if (closed) throw new IllegalStateException("closed");
if (buffer.size > 0) {
sink.write(buffer, buffer.size);
}
sink.flush();
}
最终调用的是的就是RealBufferedSink的write()
@Override public void write(Buffer source, long byteCount)
throws IOException {
if (closed) throw new IllegalStateException("closed");
buffer.write(source, byteCount);
emitCompleteSegments();
}
将请求信息写入到OutputStream中,完成将数据发送给服务器。
4)接收服务器的返回的头部header
if (responseBuilder == null) {
realChain.eventListener().responseHeadersStart(realChain.call());
responseBuilder = httpCodec.readResponseHeaders(false);
}
进入Http1Codec源码中查看
@Override public Response.Builder readResponseHeaders(boolean expectContinue) throws IOException {
//......省略代码
try {
StatusLine statusLine = StatusLine.parse(readHeaderLine());
Response.Builder responseBuilder = new Response.Builder()
.protocol(statusLine.protocol)
.code(statusLine.code)
.message(statusLine.message)
.headers(readHeaders());
//......省略代码
}
通过readHeaderLine()将服务器返回的数据转换成StatusLine。其中通过source将返回的数据读出,而source的传入同1)中提到的sink的方式一样,都是在RealConnection中实例化时传入的。
private String readHeaderLine() throws IOException {
String line = source.readUtf8LineStrict(headerLimit);
headerLimit -= line.length();
return line;
}
查看Okio中的Okio.source()的源码可以发现
public static Source source(Socket socket) throws IOException {
//......省略代码
AsyncTimeout timeout = timeout(socket);
Source source = source(socket.getInputStream(), timeout);
return timeout.source(source);
}
该source()就是从socket的inputStream中读取数据。
5)读取服务器返回的response的body数据
response = response.newBuilder()
.body(httpCodec.openResponseBody(response))
.build();
进入到源码中发现,在Http1Codec中的openResponseBody(),就是根据不同的条件返回RealResponseBody的对象。
return new RealResponseBody(contentType, contentLength, Okio.buffer(source));
总结
Okio提供了阻塞IO和非阻塞IO的功能,同时增加了缓存和超时机制。在HttpCodec中通过Okio来实现读写服务器数据。