SSE(Server-Sent Events)
SSE(Server-Sent Events,服务器发送事件)是一种用于实现服务器向客户端单向推送数据的Web技术。它允许服务器将实时数据发送到客户端,使客户端能够实时接收服务器上的更新。SSE是基于HTTP协议的一种轻量级通信协议。
工作原理
SSE通过建立一个持久的HTTP连接,服务器通过这个连接发送数据到客户端。与传统的Ajax轮询或长轮询相比,SSE具有更低的延迟和更高的效率,因为它不需要在每次请求数据时都建立新的HTTP连接。
数据传输格式
SSE使用简单的文本格式传输数据,数据以事件流(Event Stream)的形式发送。事件流由多个事件组成,每个事件由一个事件标识符、事件类型和事件数据组成。客户端通过监听事件流来接收服务器发送的事件。
优点
- 实时性:服务器可以主动推送数据到客户端,实现实时更新。
- 简单易用:SSE使用简单的文本格式传输数据,易于实现和理解。
- 轻量级:相比其他实时通信技术,SSE的开销较小,不需要额外的握手和协议。
应用场景
SSE适用于需要实时数据更新的应用场景,如股票行情、即时消息、实时交通信息等。它可以与其他Web技术(如HTML5、JavaScript)结合使用,为用户提供更好的交互体验。
样例1:基于webflux下的SSE 实现ChatGPT消息推送
@GetMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> gptFlux(){
WebClient.Builder builder = WebClient.builder();
return builder.baseUrl("https://chatgpt1.nextweb.fun/api/proxy/v1")
.defaultHeader("Authorization", "Bearer " + openKey)
.defaultHeader("Content-Type", "application/json")
.build()
.post()
.uri("/chat/completions")
.bodyValue("""
{
"model": "gpt-3.5-turbo",
"messages": [
{"role": "user", "content": "在干嘛!"},
{"role": "assistant","content": "作为一名AI语言模型,我在这里等待人类用户的提问和交流,帮助他们解决问题、获取信息或提供娱乐。请问有什么可以帮到您的吗?"},
{"role":"user", "content":"我刚刚说了什么?"}],
"temperature": 0.7
}
""")
.retrieve()
.bodyToFlux(String.class)
.timeout(Duration.ofSeconds(60L));
}
样例二:基于webMVC下的SSE 实现ChatGPT消息推送
@GetMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter gptFlux(){
SseEmitter emitter = new SseEmitter(0L);
WebClient.Builder builder = WebClient.builder();
builder.baseUrl("https://chatgpt1.nextweb.fun/api/proxy/v1")
.defaultHeader("Authorization", "Bearer " + openKey)
.defaultHeader("Content-Type", "application/json")
.build()
.post()
.uri("/chat/completions")
.bodyValue("""
{
"model": "gpt-3.5-turbo",
"messages": [
{"role": "user", "content": "在干嘛!"},
{"role": "assistant","content": "作为一名AI语言模型,我在这里等待人类用户的提问和交流,帮助他们解决问题、获取信息或提供娱乐。请问有什么可以帮到您的吗?"},
{"role":"user", "content":"我刚刚说了什么?"}],
"temperature": 0.7
}
""")
.retrieve()
.bodyToFlux(String.class)
.map(content -> {
if (content.equals("[DONE]")) {
return SseEmitter.event().name("DONE").data(-1);
}
GptResponseDto.Choices choices = JsonUtils.readFromJsonString(content, GptResponseDto.class).getChoices().get(0);
//由于单独传输容易丢失空格
return SseEmitter.event().name("chat")
.data(Objects.requireNonNull(JsonUtils.writeToJson(choices)), MediaType.TEXT_PLAIN);
})
.subscribe(objectServerSentEvent -> {
try {
emitter.send(objectServerSentEvent);
} catch (IOException e) {
log.error("错误数据:{}", objectServerSentEvent);
throw ChatExceptionEnum.SSE_SEND_ERROR.getChatException();
}
}, error -> {
if(!(error instanceof ChatExceptionEnum.ChatException)){
userProvider.addFrequency(uuid.split(":")[1], 1);
}
log.error("获取消息异常:{}", error.getMessage());
try {
emitter.send(SseEmitter.event().name("DONE").data("服务器连接异常"));
} catch (IOException e) {
throw ChatExceptionEnum.SSE_SEND_ERROR.getChatException();
}
}, emitter::complete);
return emitter;
}
提示
- webflux 和 webmvc 两种web框架 并不能共存 如果使用了webmvc 那么基于flux的webflux接口就会失效
- SseEmitter这个对象 在创建是需要设置超时时间应该设置为0L 不然 如果gpt消息过长,那么浏览器会在 30秒之内自动断开sse
- 一定要设置sse的出口 浏览器会一直访问 sse 的接口导致消息重复