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、输…

❀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 中每一个元素的…

Redis--15--缓存穿透 击穿 雪崩

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 缓存穿透 击穿 雪崩运行速度:1 缓存穿透问题描述:如何解决: 2 缓存击穿问题描述:如何解决: 3 缓存雪崩说明:解决方案: 缓存穿透 击穿 雪崩 问题描述: 由于海量的用…

springboot单元测试关闭日志

在logback中关闭日志 在test目录下新建文件夹resources&#xff0c;新增文件logback-test.xml文件 在logback-test.xml 文件中&#xff0c;添加内容&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <configuration><include resourc…

一缕青丝寄相思

10年8月16日七夕节男孩向女孩表白,女孩不知道那天是七夕,也没有读懂男孩的爱,女孩在9月22日中秋,向男孩打开了心门,男孩却没有懂女孩的心思.13年后的一封问候邮件,一束女孩的长发和回不去的青春 洒满阳光的午后 转眼间看到你的笑脸 微笑着你对我说 遇上你认识我真好 你说得好莫…

免费HTTPS证书

什么是HTTPS呢&#xff1f;HTTPS全称为Hyper Text Transfer Protocol Secure&#xff0c;即超文本传输安全协议。它是在HTTP的基础上加入了SSL/TLS协议&#xff0c;可以对传输的数据进行加密&#xff0c;有效防止数据被第三方截取或篡改&#xff0c;从而保障了用户的信息安全。…

实时设计#N3期训练营DONE,ComfyUI中文社区@上海

作为主办方&#xff0c;我们非常高兴能够举办这次AIGC训练营&#xff0c;重点解决Comfyui的安装和入门。活动在下午1:30开始&#xff0c;在上海永兴仓库举行。 首先&#xff0c;我们向参与者介绍了本次活动的目的和安排&#xff0c;让大家对活动有一个清晰的认识。 接着&#x…

J-Link RTT的使用(原理 + 教程 + 应用 + 代码)

MCU:STM32F407VE MDK:5.29 IAR:8.32 目录--点击可快速直达 目录 写在前面什么是RTT?RTT的工作原理RTT的性能快速使用教程高级使用教程附上测试代码2019年12月27日更新--增加打印float的功能 写在前面 本文介绍了J-Link RTT的部分使用内容&#xff0c;很多地方参考和使用…

28、DS18B20温度传感器

DS18B20介绍 DS18B20是一种常见的数字温度传感器&#xff0c;其控制命令和数据都是以数字信号的方式输入输出&#xff0c;相比较于模拟温度传感器&#xff0c;具有功能强大、硬件简单、易扩展、抗干扰性强等特点 测温范围&#xff1a;-55C 到 125C 通信接口&#xff1a;1-Wire…

DeDeCMS v5.7 SP2 正式版 前台任意用户密码修改(漏洞复现)

1.环境搭建 PHP 5.6 DeDeCMSV5.7SP2 正式版 安装phpstudy&#xff0c;https://www.xp.cn/小皮面板 先启动Apache2.4.39和MySQL5.7.26 如果他会让你下载&#xff0c;点击是就好&#xff01; 让后点击网站—>点击创建网站 域名自己创建&#xff0c;自己取 其他的不变 点击…

6-69.鸭子也是鸟

按要求完成下面的程序&#xff1a; 1、定义一个Bird类&#xff0c;包含一个void类型的无参的speak方法&#xff0c;输出“Jiu-Jiu-Jiu”。 2、定义一个Duck类&#xff0c;公有继承自Bird类&#xff0c;其成员包括&#xff1a; &#xff08;1&#xff09;私有string类型的成员na…

使用求2个字符串最短编辑距离动态规划算法实现 git diff 算法 java 实现

测试类 MyDiffTest.java&#xff1a; import java.io.BufferedReader; import java.io.FileReader; import java.util.ArrayList; import java.util.List;public class MyDiffTest {private static String path "\\xxx\\";private static List<String> lines…