章节
第一章链接: SpringBoot集成websocket(1)|(websocket客户端实现)
第二章链接: SpringBoot集成websocket(2)|(websocket服务端实现以及websocket中转实现)
第三章链接: SpringBoot集成websocket(3)|(websocket调用websocket采用回调方式实现数据互传)
第四章链接: SpringBoot集成websocket(4)|(使用okhttp3实现websocket)
第五章链接: SpringBoot集成websocket(5)|(使用OkHttpClient实现websocket以及详细介绍)
上篇文章介绍了为什么选用OkHttpClient来实现websocket,本篇章就不过多的讲述,本章主要介绍OkHttpClient实现websocket多级代理跳转中的问题优化以及springboot实现websocket的一些常见问题。
每个client对象都有自己的线程池和连接池,如果为每个请求都创建一个client对象,自然会出现内存溢出。所以官方建议OkHttpClient应该单例化,重用连接和线程能降低延迟和减少内存消耗。
官方介绍了三种创建client的方式
1、new OkHttpClient()
该方式将创建一个使用默认设置的client单例对象。
2、new OkHttpClient.Builder()
该方式允许自定义配置自己的单例client对象。配置connectionTimeout, readTimeout, writeTimeout等参数。
3、okHttpclient.newBuilder()
该方式通过已经存在的client对象,创建特殊需要的client对象。如 我们通过上个方法创建了自定义配置的单例client对象,但是针对某些场景需要调整某些参数,那么就需要使用该方法创建定制的client。新client对象与旧client对象共享连接池,线程池和其他配置。
@Bean
public OkHttpClient getOkHttpClient() {
return new OkHttpClient.Builder()
.connectTimeout(50L, TimeUnit.SECONDS) // 超时连接时间
.readTimeout(3*60L, TimeUnit.SECONDS) // 超速读取时间
.build();
}
依赖上篇文章已经介绍过现在不做介绍
springboot 服务端代码实现,用于给前端进行连接
@Component
@Data
@Slf4j
@ServerEndpoint(value = "/v1/chat")
public class DocChatServer {
// 注意 此处定义的为静态变量,原因是ServerEndpoint 是多例的,不能用springBoo直接注入容器里的对象,static前提是变量都需要是单例,多例的话会有线程安全问题
private static SparkClient4ChatBuild sparkClient4ChatBuild;
private static OkHttpClient httpClient;
@Autowired
private void setOriginMessageSender(SparkClient4ChatBuild sparkClient4ChatBuild,OkHttpClient httpClient) {
log.debug("客户端连接 A chat 初始化");
this.sparkClient4ChatBuild = sparkClient4ChatBuild;
this.httpClient = httpClient;
}
@OnOpen
public void OnOpen(Session session) {
log.debug("客户端连接 A chat 接口");
}
@OnClose
public void OnClose() {
log.debug("---客户端关闭 A chat 接口");
// log.info("结束调用cancel");
// planetClient4Chat.cancel();
}
@OnError
public void onError(Session session, Throwable t) {
log.debug("---客户端连接 A chat 接口异常");
t.printStackTrace();
}
@OnMessage
public void OnMessage(Session session, String message) {
log.debug("客户端连接 A chat 进行中");
boolean b = sendGptMessage(session, message);
log.debug("客户端连接 A chat 结束:{}", b);
}
/**
* 收到gpt型响应数据处理
*
* @param session 会话id
* @return
*/
private boolean sendGptMessage(Session session, String message) {
SparkClient4Chat planetClient4Chat = sparkClient4ChatBuild.build();
// 连接第三方ws地址
planetClient4Chat.connect(new ApiResponseObserver() {
public void onReceive(String message) {
log.debug("收到响应数据:{}", message);
JsonParse jsonMessage = JSON.parseObject(message).toJavaObject(JsonParse.class);
if (jsonMessage.header.status == 2) {
try {
log.info("运行中调用cancel");
planetClient4Chat.cancel();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
public void onError(Throwable throwable) {
log.debug("会话异常:{}", throwable.getMessage());
}
public void onCompleted() {
log.error("回调收到结束");
}
});
//String messages2 = "{\"讲一个故事\"}";
planetClient4Chat.send(message);
return true;
}
}
注意:websocket 是多对象的,每个用户的聊天客户端对应 java 后台的一个 websocket 对象,前后台一对一(多对多)实时连接,所以 websocket 不可能像 servlet 一样做成单例的,让所有聊天用户连接到一个 websocket对象,这样无法保存所有用户的实时连接信息。可能 spring 开发者考虑到这个问题,没有让 spring 创建管理 websocket ,而是由 java 原来的机制管理websocket ,所以用户聊天时创建的 websocket 连接对象不是 spring 创建的,spring 也不会为不是他创建的对象进行依赖注入,所以如果不用static关键字,每个 websocket 对象的 service 都是 null
用于处理第三方chat连接
@Slf4j
@Getter
@Setter
public class SparkClient4Chat {
String chatUrl = "ws://xxx/v2.1/chat";
private WebSocket webSocket;
public void connect(OkHttpClient okHttpClient, ApiResponseObserver apiResponseObserver) {
Request request = new Request.Builder().url(url).build();
SparkChatServer sparkChatServer = new SparkChatServer(apiResponseObserver);
this.webSocket = okHttpClient.newWebSocket(request, sparkChatServer);
}
public void send(String message) {
log.debug("ApiRequest = {}", message);
this.webSocket.send(message);
}
public void cancel() {
log.debug("cancel begin...");
if (this.webSocket != null) {
this.webSocket.cancel(); // cancel暴力关闭(立马见效),用close是不能立马关闭的,管道中的数据还会持续发送过来
this.webSocket = null;
log.debug("cancel end.");
}
}
public void close() throws Exception {
log.debug("close begin...");
if (this.webSocket != null) {
log.debug("this.webSocket.close(1000, \"NORMAL_CLOSURE\")");
this.webSocket.close(1000, "NORMAL_CLOSURE");
this.webSocket = null;
log.debug("close end...");
}
}
}
主要用来连接第三方ws介绍
用于处理第三方chat连接 ,通过回调函数直接放回信息给客户端的连接
@Slf4j
public class SparkChatServer extends WebSocketListener {
private ApiResponseObserver apiResponseObserver;
public SparkChatServer(ApiResponseObserver apiResponseObserver) {
this.apiResponseObserver = apiResponseObserver;
}
@Override
public void onOpen(WebSocket webSocket, Response response) {
super.onOpen(webSocket, response);
log.debug("连接GPT大模型");
}
@Override
public void onMessage(WebSocket webSocket, String message) {
apiResponseObserver.onReceive(message);
}
@Override
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
apiResponseObserver.onError(t);
log.error("chat连接出现异常:{}", t.getMessage());
}
}
本文主要介绍了为什么选用OkHttpClient来实现websocket,本篇章就不过多的讲述,本章主要介绍OkHttpClient实现websocket多级代理跳转中的问题优化以及springboot实现websocket的一些常见问题。