Java封装讯飞星火大模型历险记

问题描述与分析

现状描述与目标

        在使用讯飞星火大模型API的过程中,API的返回结果在可以在其他线程中进行分次打印,但是在main方法中直接打印返回结果,显示为空。这种情况下不利于二次封装,希望在main方法中获取完整的API返回结果,便于进一步的封装。
 

问题分析

        讯飞的API采用WebSocket进行通信,这是一个异步通信机制,当main 方法发送 WebSocket 请求,并不能立即得到回应。因此,即使 onMessage 方法最终接收到了回应并处理了它,这个处理结果也不会反映在main方法的totalannswer中。简而言之,这是由于多线程执行顺序的问题导致的。在代码中,WebSocket的响应处理是异步的,而打印语句是同步执行的。

       

解决方案

方案一:使用同步机制

        使用像 CountDownLatch 这样的同步辅助类来等待WebSocket响应处理完成。让当前线程等待,直到 latch 的计数器减到零,或者等待时间达到 30 秒。如果计数器在 30 秒内减到零,则等待结束,线程继续执行;如果 30 秒内计数器没有减到零,则等待时间到后线程同样继续执行。

    public static String getFullMessage(){try {latch.await(30, TimeUnit.SECONDS); // 等待最后一条消息或超时} catch (InterruptedException e) {Thread.currentThread().interrupt();}return totalAnswer;}

        由于无法确定每个线程的操作数据,所以每次都需要等待超时。在这种情况下,该方案能够解决问题,但是延时较高。

方案二:标志变量协调协调线程间的操作

        本项目中,一个线程负责处理WebSocket消息,而另一个线程负责其他任务(例如用户输入或程序逻辑)。wsCloseFlag 可以作为一个同步机制,帮助这些线程协调它们的操作。例如,接收线程可以检查这个标志以决定是否继续等待新消息,或者在发送线程决定关闭连接后停止处理。

private static volatile Boolean wsCloseFlag;while (! wsCloseFlag){}System.out.println("totalAnswer:"+totalAnswer);

        注意,必须使用volatile关键字,否则程序将陷入死循环。

        在Java多线程环境中,每个线程都可能有自己的本地内存(线程栈),并且线程可能会将共享变量的副本缓存在本地内存中。这意味着一个线程对共享变量的修改可能不会立即反映到其他线程的本地副本中。在代码中,wsCloseFlag是一个共享变量,它在不同的线程中被读取和修改。如果不使用volatile关键字,线程会继续使用本地缓存中过时的wsCloseFlag值,导致循环不能正确结束。

相关知识

WebSocket

        WebSocket 是一种网络通信协议,提供了在单个 TCP 连接上进行全双工通信的能力。它最初被设计为 Web 浏览器和服务器之间的持久连接,但现在已被多种客户端和服务器应用程序广泛采用。

WebSocket主要特点
  1. 全双工通信: WebSocket 允许服务器和客户端之间进行双向实时数据传输。一旦建立了 WebSocket 连接,服务器和客户端都可以随时发送数据。

  2. 持久连接: 与传统的 HTTP 请求不同,WebSocket 在客户端和服务器之间建立一个持久的连接,该连接会保持打开状态,直到由客户端或服务器主动关闭。

  3. 低延迟: WebSocket 通过减少数据包的头部信息和避免频繁建立连接的开销,能够提供较低的通信延迟。

  4. 兼容性: WebSocket 使用 HTTP 协议进行初始握手,因此与现有的 Web 基础设施兼容。

WebSocket 工作原理
  1. 握手: WebSocket 通信首先通过 HTTP 请求进行握手。客户端发送一个特殊的 HTTP 请求,称为 "WebSocket 握手请求"。如果服务器接受这个升级请求,它会返回一个相应的响应头,从而升级连接到 WebSocket。

  2. 数据帧: 一旦握手成功,数据交换就通过 WebSocket 连接进行。WebSocket 数据通过帧的形式发送,这些帧可以包含文本或二进制数据。

  3. 关闭连接: 客户端或服务器可以通过发送一个关闭帧来主动关闭 WebSocket 连接。

Spring WebSocket 支持

        Spring 框架提供了对 WebSocket 的支持,允许在 Spring 应用中轻松地创建 WebSocket 服务器和客户端。Spring 的 WebSocket 支持集成了更高级的消息传递子协议,例如 STOMP。

  • Spring WebSocket 配置:可以通过实现 WebSocketConfigurer 接口来配置 WebSocket。使用 @EnableWebSocket 注解启用 WebSocket 支持,并在配置类中注册 WebSocket 端点。

  • 消息处理:Spring 还支持使用消息代理来处理基于 WebSocket 的通信,这对于构建复杂的消息传递应用程序特别有用。

  • STOMP 支持:Spring 通过集成 STOMP 协议,提供了一个更高级别的抽象,使得创建基于订阅/发布模式的实时应用程序变得更简单。

通信类型

        在计算机科学和数据通信领域中,根据数据传输的特性,通信可以分为同步通信(Synchronous Communication)和异步通信(Asynchronous Communication)两种类型。这两种通信方式在实现数据交换的机制和适用场景上有所不同。

同步通信

        在同步通信中,发送方发送请求后必须等待接收方处理完成并返回响应,才能继续执行后续操作。同步通信的特点是:

  • 阻塞:在等待响应的过程中,发送方通常处于阻塞状态,无法执行其他任务。
  • 直接性:发送方直接等待接收方的响应,直到收到回应后才继续执行。
  • 顺序性:通信过程中的操作顺序严格遵循请求-响应的模式。

同步通信的例子包括传统的 HTTP 请求、数据库查询等。

异步通信

        异步通信允许发送方在发送请求后不必等待响应就可以继续执行其他任务。异步通信的特点是:

  • 非阻塞:发送方发送请求后可以立即执行其他操作,不需等待响应。
  • 间接性:发送方不直接等待响应,而是在响应准备好后通过回调、事件、消息队列等机制接收。
  • 灵活性:异步通信允许更加灵活地处理多任务并发,提高系统的响应能力和效率。

        异步通信的例子包括 WebSocket 通信、消息队列处理、事件驱动的编程等。

多线程环境中标志变量的线程安全性
  1. 使用volatile关键字:您可以将Flag声明为volatile。这将确保每次访问Flag时都是从主内存中进行的,而不是从线程的本地缓存。这样可以确保一个线程对这个变量的修改对其他线程是可见的。

    private static volatile Boolean Flag;
  2. 使用同步块:使用synchronized关键字同步访问Flag。这不仅可以保证可见性,还可以保证操作的原子性。

  3. 使用AtomicBoolean:可以使用java.util.concurrent.atomic.AtomicBoolean代替Boolean。这个类提供了一种无锁的方式来操作一个boolean值,同时确保跨线程的可见性。

附录

原始代码

package com.day;import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.gson.Gson;
import okhttp3.*;import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.*;public class XunfeiModelV1 extends WebSocketListener {// 地址与鉴权信息  https://spark-api.xf-yun.com/v1.1/chat   1.5地址  domain参数为general// 地址与鉴权信息  https://spark-api.xf-yun.com/v2.1/chat   2.0地址  domain参数为generalv2public static final String hostUrl = "https://spark-api.xf-yun.com/v2.1/chat";public static final String appid = "81*******9";public static final String apiSecret = "ZTd************************mVk";public static final String apiKey = "99d8350*******************e6cea";public static List<RoleContent> historyList=new ArrayList<>(); // 对话历史存储集合public static String totalAnswer=""; // 大模型的答案汇总// 环境治理的重要性  环保  人口老龄化  我爱我的祖国public static  String NewQuestion = "";public static final Gson gson = new Gson();// 个性化参数private String userId;private Boolean wsCloseFlag;private static Boolean totalFlag=true; // 控制提示用户是否输入// 构造函数public XunfeiModelV1(String userId, Boolean wsCloseFlag) {this.userId = userId;this.wsCloseFlag = wsCloseFlag;}// 主函数public static void main(String[] args) throws Exception {// 个性化参数入口,如果是并发使用,可以在这里模拟while (true){if(totalFlag){Scanner scanner=new Scanner(System.in);System.out.print("我:");totalFlag=false;NewQuestion=scanner.nextLine();// 构建鉴权urlString authUrl = getAuthUrl(hostUrl, apiKey, apiSecret);OkHttpClient client = new OkHttpClient.Builder().build();String url = authUrl.toString().replace("http://", "ws://").replace("https://", "wss://");Request request = new Request.Builder().url(url).build();for (int i = 0; i < 1; i++) {totalAnswer="";WebSocket webSocket = client.newWebSocket(request, new XunfeiModelV1(i + "",false));}}else{Thread.sleep(200);}}}public static boolean canAddHistory(){  // 由于历史记录最大上线1.2W左右,需要判断是能能加入历史int history_length=0;for(RoleContent temp:historyList){history_length=history_length+temp.content.length();}if(history_length>12000){historyList.remove(0);historyList.remove(1);historyList.remove(2);historyList.remove(3);historyList.remove(4);return false;}else{return true;}}// 线程来发送音频与参数class MyThread extends Thread {private WebSocket webSocket;public MyThread(WebSocket webSocket) {this.webSocket = webSocket;}public void run() {try {JSONObject requestJson=new JSONObject();JSONObject header=new JSONObject();  // header参数header.put("app_id",appid);header.put("uid",UUID.randomUUID().toString().substring(0, 10));JSONObject parameter=new JSONObject(); // parameter参数JSONObject chat=new JSONObject();chat.put("domain","generalv2");chat.put("temperature",0.5);chat.put("max_tokens",4096);parameter.put("chat",chat);JSONObject payload=new JSONObject(); // payload参数JSONObject message=new JSONObject();JSONArray text=new JSONArray();// 历史问题获取if(historyList.size()>0){for(RoleContent tempRoleContent:historyList){text.add(JSON.toJSON(tempRoleContent));}}// 最新问题RoleContent roleContent=new RoleContent();roleContent.role="user";roleContent.content=NewQuestion;text.add(JSON.toJSON(roleContent));historyList.add(roleContent);message.put("text",text);payload.put("message",message);requestJson.put("header",header);requestJson.put("parameter",parameter);requestJson.put("payload",payload);// System.err.println(requestJson); // 可以打印看每次的传参明细webSocket.send(requestJson.toString());// 等待服务端返回完毕后关闭while (true) {// System.err.println(wsCloseFlag + "---");Thread.sleep(200);if (wsCloseFlag) {break;}}webSocket.close(1000, "");} catch (Exception e) {e.printStackTrace();}}}@Overridepublic void onOpen(WebSocket webSocket, Response response) {super.onOpen(webSocket, response);System.out.print("大模型:");MyThread myThread = new MyThread(webSocket);myThread.start();}@Overridepublic void onMessage(WebSocket webSocket, String text) {// System.out.println(userId + "用来区分那个用户的结果" + text);JsonParse myJsonParse = gson.fromJson(text, JsonParse.class);if (myJsonParse.header.code != 0) {System.out.println("发生错误,错误码为:" + myJsonParse.header.code);System.out.println("本次请求的sid为:" + myJsonParse.header.sid);webSocket.close(1000, "");}List<Text> textList = myJsonParse.payload.choices.text;for (Text temp : textList) {System.out.print(temp.content);totalAnswer=totalAnswer+temp.content;}if (myJsonParse.header.status == 2) {// 可以关闭连接,释放资源System.out.println();System.out.println("*************************************************************************************");if(canAddHistory()){RoleContent roleContent=new RoleContent();roleContent.setRole("assistant");roleContent.setContent(totalAnswer);historyList.add(roleContent);}else{historyList.remove(0);RoleContent roleContent=new RoleContent();roleContent.setRole("assistant");roleContent.setContent(totalAnswer);historyList.add(roleContent);}wsCloseFlag = true;totalFlag=true;}}@Overridepublic void onFailure(WebSocket webSocket, Throwable t, Response response) {super.onFailure(webSocket, t, response);try {if (null != response) {int code = response.code();System.out.println("onFailure code:" + code);System.out.println("onFailure body:" + response.body().string());if (101 != code) {System.out.println("connection failed");System.exit(0);}}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}// 鉴权方法public static String getAuthUrl(String hostUrl, String apiKey, String apiSecret) throws Exception {URL url = new URL(hostUrl);// 时间SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);format.setTimeZone(TimeZone.getTimeZone("GMT"));String date = format.format(new Date());// 拼接String preStr = "host: " + url.getHost() + "\n" +"date: " + date + "\n" +"GET " + url.getPath() + " HTTP/1.1";// System.err.println(preStr);// SHA256加密Mac mac = Mac.getInstance("hmacsha256");SecretKeySpec spec = new SecretKeySpec(apiSecret.getBytes(StandardCharsets.UTF_8), "hmacsha256");mac.init(spec);byte[] hexDigits = mac.doFinal(preStr.getBytes(StandardCharsets.UTF_8));// Base64加密String sha = Base64.getEncoder().encodeToString(hexDigits);// System.err.println(sha);// 拼接String authorization = String.format("api_key=\"%s\", algorithm=\"%s\", headers=\"%s\", signature=\"%s\"", apiKey, "hmac-sha256", "host date request-line", sha);// 拼接地址HttpUrl httpUrl = Objects.requireNonNull(HttpUrl.parse("https://" + url.getHost() + url.getPath())).newBuilder().//addQueryParameter("authorization", Base64.getEncoder().encodeToString(authorization.getBytes(StandardCharsets.UTF_8))).//addQueryParameter("date", date).//addQueryParameter("host", url.getHost()).//build();// System.err.println(httpUrl.toString());return httpUrl.toString();}//返回的json结果拆解class JsonParse {Header header;Payload payload;}class Header {int code;int status;String sid;}class Payload {Choices choices;}class Choices {List<Text> text;}class Text {String role;String content;}class RoleContent{String role;String content;public String getRole() {return role;}public void setRole(String role) {this.role = role;}public String getContent() {return content;}public void setContent(String content) {this.content = content;}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/194450.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux——基本指令(一)

写在前面&#xff1a; 我们云服务器搭建的Linux系统&#xff0c;使用的镜像版本CentOS 7.6,使用的Xshell远程连接云服务器 前面我们使用超级管理员root账号登录&#xff0c;一般我们使用普通用户登录&#xff0c;那么如何创建新用户呢&#xff1f; 1.创建新用户 &#xff08…

Ubuntu22.04无需命令行安装中文输入法

概要&#xff1a;Ubuntu22.04安装完成后&#xff0c;只需在设置中点点点即可完成中文输入法的安装&#xff0c;无需命令行。 一、安装中文语言包 1、点击屏幕右上角&#xff0c;如下图所示。 2、点击设置 3、选择地区与语言&#xff0c;点击管理已安装的语言 4、点击安装 5、输…

KALI LINUX附录

预计更新 第一章 入门 1.1 什么是Kali Linux&#xff1f; 1.2 安装Kali Linux 1.3 Kali Linux桌面环境介绍 1.4 基本命令和工具 第二章 信息收集 1.1 网络扫描 1.2 端口扫描 1.3 漏洞扫描 1.4 社交工程学 第三章 攻击和渗透测试 1.1 密码破解 1.2 暴力破解 1.3 漏洞利用 1.4 …

docker: Error response from daemon: network hm-net not found.

在使用Docker部署RabbitMQ的时候出现错误&#xff1a;docker: Error response from daemon: network hm-net not found. docker run \-e RABBITMQ_DEFAULT_USERuser \-e RABBITMQ_DEFAULT_PASS123456 \-v mq-plugins:/plugins \--name mq \--hostname mq \-p 15672:15672 \-p 5…

java对象的创建过程是怎样的?

Java对象的创建过程主要分为五个步骤&#xff1a; 1. 加载类信息&#xff1a;当我们使用new关键字来创建一个对象时&#xff0c;首先会去检查这个类的信息是否已经被加载到内存中。如果没有加载&#xff0c;就会先加载。 2. 分配内存空间&#xff1a;在JVM的堆内存中为新的对…

ElasticSearch之Analyze index disk usage API

本API用于分析、统计指定index当前占用的存储空间。 考虑到本特性目前仍然处于预览状态&#xff0c;因此使用方法、参数等可能会发生变化&#xff0c;或者未来也许会被删除。 本API暂时不建议在生产系统中使用。 命令样例如下&#xff1a; curl -X POST "https://localh…

目标检测YOLO系列从入门到精通技术详解100篇-【图像处理】图像识别

目录 知识储备 OpenCV中的图像形态学 基于图像识别的水位测量 目标图像的提取和预处理

PHP常见错误

初学者在编程时&#xff0c;经常会遇到各种错误&#xff0c;那么如何 正确的处理错误则是可以提高开发效率。 一&#xff1a;错误&#xff08;Error&#xff09; 1.1 什么是错误及错误的级别 错误是指在开发阶段中由一些失误引起的程序问题&#xff0c;根据其出现在编程过程…

❀My学习Linux命令小记录(10)❀

目录 ❀My学习Linux命令小记录&#xff08;10&#xff09;❀ 36.fold指令 37.expr指令 38.iperf指令 39.telnet指令 40.ssh指令 ❀My学习Linux命令小记录&#xff08;10&#xff09;❀ 36.fold指令 功能说明&#xff1a;控制文件内容输出时所占用的屏幕宽度&#xff0c…

上门按摩APP小程序,抓住机遇创新服务新模式;

上门按摩APP小程序&#xff1a;抓住机遇&#xff0c;创新服务新模式&#xff1b; 随着现代人对生活质量要求的提高&#xff0c;上门按摩服务正成为一种新的、受欢迎的生活方式。通过APP小程序&#xff0c;用户可以轻松预约按摩服务&#xff0c;解决身体疲劳问题&#xff0c;享受…

Python程序员入门指南:学习时间和方法

文章目录 标题Python程序员入门指南&#xff1a;学习时间、方法和就业前景学习方法建议学习时间 标题 Python程序员入门指南&#xff1a;学习时间、方法和就业前景 Python是一种流行的编程语言&#xff0c;它具有简洁、易读和灵活的特点。Python可以用于多种领域&#xff0c;如…

设计模式基础(1)

目录 一、设计模式的定义 二、设计模式的三大类别 三、设计模式的原则 四、主要设计模式目录 4.1 创建型模式&#xff08;Creational Patterns&#xff09; 4.2 结构型模式&#xff08;Structural Patterns&#xff09; 4.3 行为型模式&#xff08;Behavioral Patterns&…

重启路由器可以解决N多问题?

为什么重启始终是路由器问题的首要解决方案? 在日常的工作学习工作中,不起眼的路由器是一种相对简单的设备,但这仍然是我们谈论的计算机。 这种廉价的塑料外壳装有 CPU、随机存取存储器 (RAM)、只读存储器 (ROM) 和许多其他组件。 该硬件运行预装的软件(或固件)来管理连接…

牛客算法心得——kotori和素因子(dfs)

大家好&#xff0c;我是晴天学长&#xff0c;传智杯的题&#xff0c;一个经典的全排列找最小的问题&#xff0c;需要的小伙伴可以关注支持一下哦&#xff01;后续会继续更新的。&#x1f4aa;&#x1f4aa;&#x1f4aa; 1) .kotori和素因子 链接&#xff1a;https://ac.nowcod…

使用Redis构建简易社交网站(1)-创建用户与动态界面

目的 本文目的&#xff1a;实现简易社交网站中创建新用户和创建新动态功能。&#xff08;完整代码附在文章末尾&#xff09; 相关知识 本文将教会你掌握&#xff1a;1.redis基本命令&#xff0c;2.python基本命令。 redis基本命令 hget&#xff1a;从哈希中获取指定域的值…

Leetcode2661. 找出叠涂元素

Every day a Leetcode 题目来源&#xff1a;2661. 找出叠涂元素 解法1&#xff1a;哈希 题目很绕&#xff0c;理解题意后就很简单。 由于矩阵 mat 中每一个元素都不同&#xff0c;并且都在数组 arr 中&#xff0c;所以首先我们用一个哈希表 hash 来存储 mat 中每一个元素的…

vue el-select封装及使用

基于Element UI的el-select组件进行封装的。该组件实现了一个下拉选择框&#xff0c;具有许多可配置的属性和事件 创建组件index.vue (src/common-ui/select/index.vue) <template><el-selectref"select"v-model"hValue":allow-create"allo…

03-MyBatis中动态的给SQL语句赋值方式,详解占位符${}和#{}的区别和应用场景

动态的给SQL语句赋值方式 实际开发中SQL语句的参数值是不能写死到配置文件中的,应该由前端发起的请求中包含的请求参数中的数据决定 <insert id"insertCar">insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)values(null,1003,丰田霸道…

基于Intel® AI Analytics Toolkits的智能视频监控系统

【oneAPI DevSummit & OpenVINODevCon联合黑客松】 跳转链接&#xff1a;https://marketing.csdn.net/p/d2322260c8d99ae24795f727e70e4d3d 目录 1方案背景 2方案描述 3需求分析 4技术可行性分析 5详细设计5.1数据采集 5.2视频解码与帧提取 5.3人脸检测 5.4行为识别…

4、Schema与数据类型优化

良好的逻辑设计和物理设计是高性能的基石&#xff0c;应该根据系统将要执行的查询语句来设计schema&#xff0c;这往往需要权衡各种因素。例如&#xff0c;反范式的设计可以加快某些类型的查询&#xff0c;但同时可能使另一些类型的查询变慢。比如添加计数表和汇总表时一种很好…