流式会话(Streamed Conversation)指的是在人机交互的过程中,以流的形式进行信息传输,而不是将信息一次性返回。常见的应用场景:如微信的多人聊天、chatgpt的人机交互等。流式会话有如下技术进行实现:
长轮询是一种服务器推送技术。当客户端想服务器端发送请求,会建立连接服务器端发送数据,客户端会对数据进行即时处理,如果没有数据连接挂起但是不会中断。流程为:
WebSocke是一种协议提供了全双工的通信渠道,使得客户端和服务器端可以进行双向通信,不需要进行频繁的轮询等机制进行检查更新。
SSE指的是服务器端向客户端发送消息的单向通信(半双工通信)技术,属于HTML5的一部分,它是基于HTTP协议进行工作使用起来比较简单。对于那些不需从客户端到服务器端实时通信的场景来说是一个轻量级的选择。
(1)maven依赖
com.squareup.okhttp3
okhttp-sse
4.9.3
(2)普通的HTTP请求
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(1000, TimeUnit.SECONDS)
.writeTimeout(1000, TimeUnit.SECONDS)
.readTimeout(1000, TimeUnit.SECONDS)
.build();
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json;charset=utf-8"), 请求体字符串格式);
Request request = new Request.Builder()
.url(url)
.addHeader("key", "value")
.post(requestBody)
.build();
try {
Response execute = client.newCall(request).execute();
if (execute.code() != 200) {
throw new RuntimeException(execute.message());
}
ResponseBody body = execute.body();
System.out.println(execute.code());
System.out.println(JSONObject.toJSONString(body));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
(3)HTTP - Response
public void TestOkHttpSse() throws InterruptedException {
OkHttpClient client = new OkHttpClient.Builder()
.connectTimeout(1000, TimeUnit.SECONDS)
.writeTimeout(1000, TimeUnit.SECONDS)
.readTimeout(1000, TimeUnit.SECONDS)
.build();
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json;charset=utf-8"), 请求体字符串格式);
Request request = new Request.Builder()
.url(url)
.addHeader("key", "value")
.post(requestBody)
.build();
CountDownLatch countDownLatch = new CountDownLatch(1);
EventSource.Factory factory = EventSources.createFactory(client);
StringBuffer output = new StringBuffer();
// 自定义监听器
EventSourceListener eventSourceListener = new EventSourceListener() {
@Override
public void onOpen(EventSource eventSource, Response response) {
System.out.println(JSONObject.toJSONString(response));
}
@Override
public void onEvent(EventSource eventSource, String id, String type, String data) {
// 接受消息 data
httpServletResponse.getWriter().write(data + "\n");
httpServletResponse.getWriter().flush();
System.out.println(JSONObject.toJSONString(data));
}
@Override
public void onClosed(EventSource eventSource) {
System.out.println("sse close: {}");
countDownLatch.countDown();
}
@Override
public void onFailure(EventSource eventSource, Throwable t, Response response) {
countDownLatch.countDown();
}
};
// 创建事件
EventSource eventSource = factory.newEventSource(request, eventSourceListener);
countDownLatch.await();
}
(4)HTTP-SSE
public class OkHttpSSEListener extends EventSourceListener {
private static final Logger LOGGER = LoggerFactory.getLogger(OkHttpSSEListener.class);
@Getter
private final CountDownLatch countDownLatch = new CountDownLatch(1);
private final SseEmitter emitter;
@Getter
private final List<String> contextStreams = new ArrayList<>();
@Getter
private String errorMessage;
public OkHttpSSEListener(SseEmitter emitter) {
requireNonNull(emitter, "httpServletResponse is null");
this.emitter = emitter;
}
@Override
public void onClosed(@NotNull EventSource eventSource) {
LOGGER.info("-- OpenAi see close ... --");
emitter.complete();
countDownLatch.countDown();
}
@Override
public void onEvent(@NotNull EventSource eventSource, @Nullable String id, @Nullable String type, @NotNull String data) {
try {
LOGGER.info("chat stream data: {}", data);
if ("[DONE]".equals(data)) {
LOGGER.info("-- OpenAI return data success --");
} else {
contextStreams.add(data);
}
emitter.send(data, MediaType.APPLICATION_JSON);
} catch (IOException e) {
countDownLatch.countDown();
LOGGER.error(e.getMessage(), e);
throw new RuntimeException(e);
}
}
@Override
public void onFailure(@NotNull EventSource eventSource, @Nullable Throwable t, @Nullable Response response) {
try {
if (Objects.isNull(response)) {
throw new RuntimeException(String.format("-- OpenAi sse connection exception: %s --", t.getMessage()), t);
} else {
ResponseBody body = response.body();
if (Objects.isNull(body)) {
throw new RuntimeException(String.format("-- OpenAi sse connection exception: %s --", response));
} else {
throw new RuntimeException(String.format("-- OpenAi sse connection exception: %s --", body.string()));
}
}
} catch(Exception e) {
LOGGER.error(e.getMessage(), e);
errorMessage = e.getMessage();
}
countDownLatch.countDown();
eventSource.cancel();
}
@Override
public void onOpen(@NotNull EventSource eventSource, @NotNull Response response) {
LOGGER.info("-- OpenAI establishes sse connection... --");
}
支持流式传输的RPC远程调用框架,gRPC流既可以是单向的也可以是双向的。
引入了多路复用功能,允许连接发送多个请求和响应,不会相互阻塞。