锋盈数科-知识库 Logo
首页
软件开发
计算机基础
Hello Halo
新手必读
关于本知识库
登录 →
锋盈数科-知识库 Logo
首页 软件开发 计算机基础 Hello Halo 新手必读 关于本知识库
登录
  1. 首页
  2. 软件开发
  3. JAVA
  4. Spring Boot集成sse实现chatgpt流式交互

Spring Boot集成sse实现chatgpt流式交互

0
  • JAVA
  • 发布于 2024-08-13
  • 0 次阅读
黄健
黄健

本文由 简悦 SimpRead 转码, 原文地址 blog.csdn.net

 ​

博客主页:     南来_北往

系列专栏:Spring Boot 实战

什么是 sse?

SSE 可以指代两种不同的概念:一是指 “服务器发送事件”(Server-Sent Events),另一种是指英特尔的 “因特网数据流单指令序列扩展”(Streaming SIMD Extensions)。下面将逐一解释这两种概念:

  1. 服务器发送事件:
    • SSE 是一种基于 HTTP 协议的服务器向客户端实时推送数据的机制。利用 HTTP 的长连接特性,服务器可以不断地向客户端发送数据,从而实现实时通信。
    • SSE 与传统的 HTTP 请求响应模型不同,它允许服务器主动向客户端发送消息,无需客户端发起请求。这一机制适用于实时更新、在线聊天、股票价格提醒等场景。
    • SSE 与 WebSocket 在某些功能上相似,但更轻量级和简单。相比于 WebSocket 的双向通信,SSE 仅支持单向(从服务器到客户端)的消息传递,因此在一些只需要服务器推送的场景下更为适用。
  2. 因特网数据流单指令序列扩展:
    • SSE 是英特尔在其计算机芯片 Pentium III 中引入的一种指令集,用于提升多媒体和图形应用的性能。该技术在保持 MMX 指令的同时,增加了 70 条新指令,加快浮点运算并提高内存使用效率。
    • SSE 通过提供新的寄存器、数据类型和指令,结合单指令多数据(SIMD)技术,显著提高了应用程序的效率。这种提升对 3D 几何运算、图形处理、视频编辑等领域尤为明显。
    • SSE 包含 8 个 128 位的通用寄存器(XMM0~XMM7),支持紧缩的 128 位浮点数操作。这使得程序设计人员可以开发混合使用 SSE 和 MMX 指令的算法。

综上所述,SSE 既可以指服务器向客户端实时推送数据的 “服务器发送事件” 技术,也可以指英特尔为提升多媒体处理性能而开发的 “因特网数据流单指令序列扩展” 指令集。这两种技术虽然应用领域不同,但都极大地提升了相应领域的效率和体验。

SSE 和 WebSocket 

SSE(Server-Sent Events) 和 WebSocket 都是实现实时通信的重要技术,但它们在设计理念、实现方式和适用场景上存在显著差异。

SSE 是一种基于 HTTP 协议的单向通信技术,允许服务器主动向客户端推送数据。相比之下,WebSocket 提供了一个全双工通信通道,支持客户端和服务器之间的双向数据交换。下面从多个角度详细对比这两种技术:

  1. 技术原理与实现方式
    • SSE:
      • SSE 通过 HTTP 协议工作,利用持久连接实现服务器向客户端的数据推送。
      • 客户端使用 EventSource 接口监听服务器发送的消息,无需重复请求。
      • 每次数据传递完毕,连接暂时关闭,需要时重新连接。
    • WebSocket:
      • WebSocket 基于独立的 TCP 连接,并使用自定义的协议进行双向通信。
      • 一旦建立连接,该连接一直保持打开状态,直到手动关闭。
      • 支持文本和二进制数据的传输。
  2. 应用场景与优势
    • SSE:
      • 适用于只需要服务器向客户端实时推送数据的场景,如新闻更新、股票行情等。
      • 优势在于简单易用、依赖标准 HTTP 协议,浏览器兼容性好。
      • 局限性在于仅支持单向通信。
    • WebSocket:
      • 适用于需要快速、双向交互的应用,如在线游戏、实时聊天等。
      • 优势在于支持全双工通信,低延迟。
      • 局限性在于实现较为复杂,需特定服务器支持。
  3. 性能特征与复杂性
    • SSE:
      • 协议简单,易于实现,自动支持断线重连。
      • 只能发送文本数据,无法发送二进制数据。
    • WebSocket:
      • 支持更复杂的应用场景,可以发送任何类型的数据。
      • 实现复杂,可能需要额外的服务器资源和代码来处理连接和消息。
  4. 调试与测试工具
    • SSE:
      • 可以通过设置 HTTP 类型项目和使用时间线视图监控数据流。
      • Apifox 等工具提供便利的调试支持。
    • WebSocket:
      • 使用专门接口配置,发送和接收消息,查看响应结果。
      • 同样可以利用 Apifox 等工具进行有效的调试。

综上所述,如果应用只需服务器单向推送数据且要求简单实现,SSE 是理想选择;而对于需要高度互动、双向数据传输的场景,WebSocket 则更具优势。在选择实时通信技术时,应根据具体需求来决定采用 SSE 还是 WebSocket,从而更好地满足项目需求和提升用户体验。

代码工程

实现 chatgpt 流式交互 

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springboot-demo</artifactId>
        <groupId>com.et</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
 
    <artifactId>sse</artifactId>
 
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
 
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
 
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-autoconfigure</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
 
        <!-- java基础工具包 -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.9</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
 
    </dependencies>
</project>

 controller

package com.et.sse.controller;
 
import cn.hutool.core.util.IdUtil;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
 
import java.io.IOException;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
 
 
@Controller
@RequestMapping("/chat")
public class ChatController {
 
    Map<String, String> msgMap = new ConcurrentHashMap<>();
 
    /**
     * send meaaage
     * @param msg
     * @return
     */
    @ResponseBody
    @PostMapping("/sendMsg")
    public String sendMsg(String msg) {
        String msgId = IdUtil.simpleUUID();
        msgMap.put(msgId, msg);
        return msgId;
    }
 
    /**
     * conversation
     * @param msgId mapper with sendmsg
     * @return
     */
    @GetMapping(value = "/conversation/{msgId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter conversation(@PathVariable("msgId") String msgId) {
        SseEmitter emitter = new SseEmitter();
        String msg = msgMap.remove(msgId);
 
        //mock chatgpt response
        new Thread(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    ChatMessage  chatMessage =  new ChatMessage("test", new String(i+""));
                    emitter.send(chatMessage);
                    Thread.sleep(1000);
                }
                emitter.send(SseEmitter.event().name("stop").data(""));
                emitter.complete(); // close connection
            } catch (IOException | InterruptedException e) {
                emitter.completeWithError(e); // error finish
            }
        }).start();
 
        return emitter;
    }
}

chat.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>ChatGpt test</title>
    <link rel="stylesheet" href="lib/element-ui/index.css">
 
    <style type="text/css">
        body{
            background-color:white;
        }
 
        #outputCard{
            height: 300px;
            overflow:auto;
        }
 
        #inputCard{
            height: 100px;
            overflow:auto;
        }
 
        #outputBody{
            line-height:30px;
        }
 
        .cursor-img{
            height:24px;
            vertical-align: text-bottom;
        }
 
 
 
    </style>
 
    <script src="lib/jquery/jquery-3.6.0.min.js"></script>
    <script src="lib/vue/vue.min.js"></script>
    <script src="lib/element-ui/index.js"></script>
</head>
<body>
<h1 align="center">ChatGpt Test</h1>
 
<div id="chatWindow">
    <el-row id="outputArea">
        <el-card id="inputCard">
            <div id="inputTxt">
            </div>
        </el-card>
        <el-card id="outputCard">
            <div id="outputBody">
                <span id="outputTxt"></span>
                <img v-if="blink" class="cursor-img" src="img/cursor-text-blink.gif" v-show="cursorImgVisible">
                <img v-if="!blink" class="cursor-img" src="img/cursor-text-black.png" v-show="cursorImgVisible">
            </div>
        </el-card>
    </el-row>
    <el-row id="inputArea">
        <el-col :span="21">
            <el-input id="sendTxt" v-model="input" placeholder="input content" @keyup.native="keyUp"></el-input>
        </el-col>
        <el-col :span="3">
            <el-button id="sendBtn" type="primary" :disabled="sendBtnDisabled" @click="sendMsg">send</el-button>
        </el-col>
    </el-row>
</div>
 
</body>
<script type="text/javascript">
 
    var app = new Vue({
      el: '#chatWindow',
      data: {
          input: '',
          sendBtnDisabled: false,
          cursorImgVisible: false,
          blink: true
      },
      mounted: function(){
 
      },
      methods: {
         keyUp: function(event){
            if(event.keyCode==13){
               this.sendMsg();
            }
         },
         sendMsg: function(){
             var that = this;
 
             //init
             $('#outputTxt').html('');
             var sendTxt = $('#sendTxt').val();
             $('#inputTxt').html(sendTxt);
             $('#sendTxt').val('');
             that.sendBtnDisabled = true;
             that.cursorImgVisible = true;
 
             //send request
             $.ajax({
                type: "post",
                url:"/chat/sendMsg",
                data:{
                    msg: sendTxt
                },
                contentType: 'application/x-www-form-urlencoded',
                success:function(data){
                     var eventSource = new EventSource('/chat/conversation/'+data)
                     eventSource.addEventListener('open', function(e) {
                        console.log("EventSource连接成功");
                     });
 
                     var blinkTimeout = null;
                     eventSource.addEventListener("message", function(evt){
                        var data = evt.data;
                        var json = JSON.parse(data);
                        var content = json.content ? json.content : '';
                        content = content.replaceAll('\n','<br/>');
                        console.log(json)
                        var outputTxt = $('#outputTxt');
                        outputTxt.html(outputTxt.html()+content);
                        var outputCard = $('#outputCard');
                        var scrollHeight = outputCard[0].scrollHeight;
                        outputCard.scrollTop(scrollHeight);
 
                        //cusor blink
                        that.blink = false;
                        window.clearTimeout(blinkTimeout);
 
                        //200ms blink=true
                        blinkTimeout = window.setTimeout(function(){
                            that.blink = true;
                        }, 200)
                    });
                    eventSource.addEventListener('error', function (e) {
                        console.log("EventSource error");
                        if (e.target.readyState === EventSource.CLOSED) {
                          console.log('Disconnected');
                        } else if (e.target.readyState === EventSource.CONNECTING) {
                          console.log('Connecting...');
                        }
                    });
 
                    eventSource.addEventListener('stop', e => {
                        console.log('EventSource连接结束');
                        eventSource.close();
                        that.sendBtnDisabled = false;
                        that.cursorImgVisible = false;
                    }, false);
                },
                error: function(){
                     that.sendBtnDisabled = false;
                     that.cursorImgVisible = false;
                }
             });
         }
      }
    })
 
 
 
 
</script>
</html>

 以上只是一些关键代码,具体根据自己项目进行编码

标签: #JAVA 991
相关文章

Spring 实现 3 种异步接口 2024-10-18 09:07

大家好,我是苏三~ 如何处理比较耗时的接口? 这题我熟,直接上异步接口,使用 Callable、WebAsyncTask 和 DeferredResult、CompletableFuture等均可实现。 但这些方法有局限性,处理结果仅返回单个值。在某些场景下,如果需要接口异步处理的同时,还持续不断地

重学SpringBoot3-集成Redis(五)之布隆过滤器 2024-10-08 11:24

更多SpringBoot3内容请关注我的专栏:《SpringBoot3》 期待您的点赞👍收藏⭐评论✍ 重学SpringBoot3-集成Redis(五)之布隆过滤器 1. 什么是布隆过滤器? * 基本概念 适用场景 2. 使用 Redis 实现布隆过滤器 * 项目依赖 Redis 配置

SpringBoot整合异步任务执行 2024-10-08 11:24

同步任务: 同步任务是在单线程中按顺序执行,每次只有一个任务在执行,不会引发线程安全和数据一致性等 并发问题 同步任务需要等待任务执行完成后才能执行下一个任务,无法同时处理多个任务,响应慢,影响用 户体验 异步任务: 异步任务是在多线程中同时执行,多个任务可以并发执行,同时处理多个请求,响应快,资源

springboot kafka多数据源,通过配置动态加载发送者和消费者 2024-10-08 11:24

前言 最近做项目,需要支持kafka多数据源,实际上我们也可以通过代码固定写死多套kafka集群逻辑,但是如果需要不修改代码扩展呢,因为kafka本身不处理额外逻辑,只是起到削峰,和数据的传递,那么就需要对架构做一定的设计了。 准备test kafka本身非常容易上手,如果我们需要单元测试,引入ja

SpringBoot 集成 Redis 2024-10-08 11:24

一:SpringBoot 集成 Redis ①Redis是一个 NoSQL(not only)数据库, 常作用缓存 Cache 使用。 ②Redis是一个中间件、是一个独立的服务器;常用的数据类型: string , hash ,set ,zset , list ③通过Redis客户端可以使用多种语

SpringBoot整合QQ邮箱 2024-10-08 11:24

SpringBoot可以通过导入依赖的方式集成多种技术,这当然少不了我们常用的邮箱,现在本章演示SpringBoot整合QQ邮箱发送邮件…. 下面按步骤进行: 1.获取QQ邮箱授权码 1.1 登录QQ邮箱 1.2 开启SMTP服务 找到下图中的SMTP服务区域,如果当前账号未开启的话自己手动开启。

目录

IT 外包服务商

  • 意见投递
  • zyf6619

软件开发应用

主菜单

  • 首页
  • 软件开发
  • 计算机基础
  • Hello Halo
  • 新手必读
  • 关于本知识库
Copyright © 2024 your company All Rights Reserved. Powered by Halo.