OkHttp的实现原理(二)之异步

本文深入剖析OkHttp异步请求的实现原理,包括AsyncCall类的作用、分发器Dispatcher的工作流程以及线程池的使用等关键环节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上一篇我分析了OkHttp采用同步请求的实现原理,这一篇我将继续分析它的第二种请求方式——异步请求,由于这是基于上一篇文章的内容之上的,所以在看这篇文章之前请先浏览上一篇文章http://blog.youkuaiyun.com/kbqf24/article/details/56839535

所谓异步,就是我们不需要坐着等待它出结果,当它有了结果后会回调相应的方法。

让我们来看看OKHttp的异步请求是如何实现的:

public void enqueue(Callback responseCallback) {
        this.enqueue(responseCallback, false);
    }

我们要传一个Callback接口对象进去,

 void enqueue(Callback responseCallback, boolean forWebSocket) {
        synchronized(this) {
            if(this.executed) {
                throw new IllegalStateException("Already Executed");
            }

            this.executed = true;
        }

        this.client.getDispatcher().enqueue(new Call.AsyncCall(responseCallback, forWebSocket, null));
    }

首先还是要获得一个分发器,这点跟同步请求是一模一样的,接着往下看

synchronized void enqueue(AsyncCall call) {
        if(this.runningCalls.size() < this.maxRequests && this.runningCallsForHost(call) < this.maxRequestsPerHost) {
            this.runningCalls.add(call);
            this.getExecutorService().execute(call);
        } else {
            this.readyCalls.add(call);
        }

    }

原来进行异步请求是有门槛限制的,它要求总的异步请求的数量小于maxRequests =64,并且对于一个主机来说最多同时发出5个请求,如果没有满足这个条件,那么就将这个call对象加入到readyCalls数组中以备日后使用,这个数组相当于是一个缓冲数组,如果满足了这个条件,那么就将AsyncCall这个对象添加到runningCalls数组当中,并且使用线程池来执行这个任务,那么这个AsyncCall到底是什么了?它是Call的一个内部类并且实现了Runnable接口,那么走 this.getExecutorService().execute(call);这个方法当然就是要去执行call里面的run()方法啊,我们来看看这个方法:

 public final void run() {
        String oldName = Thread.currentThread().getName();
        Thread.currentThread().setName(this.name);

        try {
            this.execute();
        } finally {
            Thread.currentThread().setName(oldName);
        }

    }

这个方法会去执行execute(),并且会在执行期间给每个任务所在的线程取不同的名字。

 protected void execute() {
            boolean signalledCallback = false;

            try {
                Response e = Call.this.getResponseWithInterceptorChain(this.forWebSocket);
                if(Call.this.canceled) {
                    signalledCallback = true;
                    this.responseCallback.onFailure(Call.this.originalRequest, new IOException("Canceled"));
                } else {
                    signalledCallback = true;
                    this.responseCallback.onResponse(e);
                }
            } catch (IOException var7) {
                if(signalledCallback) {
                    Internal.logger.log(Level.INFO, "Callback failure for " + Call.this.toLoggableString(), var7);
                } else {
                    Request request = Call.this.engine == null?Call.this.originalRequest:Call.this.engine.getRequest();
                    this.responseCallback.onFailure(request, var7);
                }
            } finally {
                Call.this.client.getDispatcher().finished(this);
            }

        }
Response e = Call.this.getResponseWithInterceptorChain(this.forWebSocket);

这个方法是不是也很熟悉啊,跟同步获取数据的方法是一摸一样的,一个方法在上一篇文章中详细说明了的,这里就不再展开来说了,然后再判断这个任务是否被cancel掉了,如果是,那么就会调用我们传入的Callback接口对象的onFailure()方法,如果没有被取消,那么就会回调该接口中的onResponse(response)方法。
那么异步请求的代码我们也就分析完了,跟同步请求其实区别并不大,我们来分析一下他们的区别,
1. 同步:将Call对象之间放入分发器Dispatcher中
异步:将AsyncCall对象放入分发器Dispatcher中,
2. 同步方法不会自动给你开线程去调用getResponseWithInterceptorChain(this.forWebSocket)获取数据,所以你必须自己开启线程去执行。
异步方法OkHttp会使用内部的线程池中的线程去执行获取数据(getResponseWithInterceptorChain(this.forWebSocket))的耗时操作,所以你可以在主线程中使用异步请求。
3. 同步方法拿到返回的结果后直接返回,而异步请求获取到结果后并不直接返回,而是将结果作为参数回调接口的方法去使用的。

<think>我们使用OkHttp实现SSE(Server-SentEvents)的流式响应处理。关键点在于:1.使用OkHttp发起请求,并处理事件流(text/event-stream)2.解析每一行,判断事件类型3.当遇到事件类型为"info"时,中断连接,并将之前收集的数据存入数据库注意:由于事件流是持续不断的,我们需要在读取过程中判断是否遇到info事件,然后主动关闭连接。实现步骤:a.创建OkHttpClient实例,发起异步请求。b.在回调中处理响应体,读取事件流(响应体是一个流,我们可以按行读取)。c.维护一个StringBuilder来收集事件数据(非info事件的数据)。d.当读取到事件类型为info时,停止读取(关闭响应体),并将收集的数据存入数据库。注意:由于事件流的格式,每个事件由多行组成,事件之间由两个换行符分隔。每行可能包含:event:<事件类型>data:<数据>id:<事件id>retry:<重连时间>我们主要关注event和data行。示例事件流:event:messagedata:{"content":"部分结果"}(空行)event:infodata:{"status":"complete"}当解析到event为info时,我们中断连接,并将之前收集的数据(所有message事件的数据)存入数据库。代码实现如下:</think> 我将使用 OkHttp 实现后端 HTTP 事件流处理,包括在解析到 info 类型时中断连接并存库的方案。以下是完整实现: ### 使用 OkHttp 的 SSE 处理方案 ```java import okhttp3.*; import java.io.IOException; import okio.Buffer; import reactor.core.publisher.Flux; import org.springframework.data.r2dbc.core.R2dbcEntityTemplate; public class SseProcessor { private final OkHttpClient client = new OkHttpClient(); private final String apiUrl = "https://api.example.com/stream"; private final R2dbcEntityTemplate dbTemplate; public SseProcessor(R2dbcEntityTemplate dbTemplate) { this.dbTemplate = dbTemplate; } public void processSseStream() { Request request = new Request.Builder() .url(apiUrl) .header("Accept", "text/event-stream") .build(); client.newCall(request).enqueue(new Callback() { private final StringBuilder buffer = new StringBuilder(); private boolean shouldContinue = true; @Override public void onResponse(Call call, Response response) throws IOException { if (!response.isSuccessful()) throw new IOException("Unexpected code " + response); // 解析SSE流 try (ResponseBody responseBody = response.body(); Buffer reader = new Buffer()) { while (shouldContinue && responseBody.source().read(reader, 8192) != -1) { String line = reader.readUtf8Line(); if (line == null) continue; // 处理不同类型的事件 switch (parseEventType(line)) { case "info": saveToDatabase(buffer.toString()); shouldContinue = false; // 中断连接 call.cancel(); // 主动取消请求 break; case "message": buffer.append(parseData(line)).append("\n"); break; case "other": // 处理其他类型事件 break; } } } } @Override public void onFailure(Call call, IOException e) { // 处理连接失败情况 if (!call.isCanceled()) { // 保存部分结果 saveToDatabase(buffer.toString()); } } }); } private String parseEventType(String line) { if (line.startsWith("event: info")) return "info"; if (line.startsWith("event: message")) return "message"; return "other"; } private String parseData(String line) { return line.replaceFirst("data:\\s*", ""); } private void saveToDatabase(String content) { dbTemplate.insert(new ResultEntity(content)) .subscribe( result -> log.info("保存成功: ID={}", result.getId()), error -> log.error("存储失败", error) ); } } // 实体类 public class ResultEntity { @Id private Long id; private String content; private Instant createdAt = Instant.now(); public ResultEntity(String content) { this.content = content; } // getters/setters } ``` ### 关键实现原理 1. **OkHttp SSE连接处理** - 使用`OkHttpClient`建立HTTP连接 - 通过`ResponseBody.source()`获取响应流 - 逐行读取事件流内容 2. **中断处理流程** ```mermaid graph TD A[发起SSE请求] --> B[接收事件行] B --> C{类型判断} C --info事件--> D[存库] D --> E[设置中断标志] E --> F[取消请求] C --非info事件--> G[数据缓冲] G --> B ``` 3. **数据库处理优化** - 使用Spring Data R2DBC实现非阻塞存储 - 实体自动映射(Content字段) - 时间戳自动记录(createdAt) 4. **异常处理** ```java @Override public void onFailure(Call call, IOException e) { if (!call.isCanceled()) { // 仅保存非主动取消的场景 saveToDatabase(buffer.toString()); } } ``` 5. **性能优化点** - 固定大小缓冲区(8192字节) - 事件类型快速解析(避免JSON全解析) - 基于Flux的响应式存储 ### 应用场景 此方案适用于: 1. AI模型流式响应中断处理 2. 实时日志分析系统 3. 金融行情处理(重要事件触发保存) 4. IoT设备状态监控流
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值