这是KIMI的api接口,可以注册账号后获取APIKey在这里进行相应的测试
curl -X POST https://api.moonshot.cn/v1/chat/completions \
-H "Authorization: Bearer 你的KimiAPIKey" \
-H "Content-Type: application/json" \
-d "{\"model\": \"moonshot-v1-8k\", \"messages\": [{\"role\": \"user\", \"content\": \"Hello, Kimi!\"}], \"max_tokens\": 150}"
上面的shell命令中已经包含了我们需要请求的api接口和需要的APIKey
之后就是我们传递至api接口的json串样式
{ "model": "moonshot-v1-8k", "messages": [ {"role": "system", "content": "你是 Kimi,由 Moonshot AI 提供的人工智能助手,你更擅长中文和英文的对话..."}, ],"temperature": 0.3 }
通过官方文档中的示例请求我们可以知道该api接口所需要的json串样式如上所示
知道这些后如果我们想制作一个类似chatgpt交流页面的应用,我们就必须得使用上有关通信的协议
我们知道在通信的协议中我们已经有了Http协议,那我们为什么还要使用WebSocket呢?
想要了解有关通信的可以看看相关文章
web通讯的四种方式,短轮询、长轮询(comet)、长连接(SSE)、WebSocket
HTTP协议是非持久化的,单向的网络协议,在建立连接后只允许浏览器向服务器发出请求后,服务器才能返回相应的数据。当需要即时通讯时,通过轮询在特定的时间间隔(如1秒),由浏览器向服务器发送Request请求,然后将最新的数据返回给浏览器。这样的方法最明显的缺点就是需要不断的发送请求,而且通常HTTP request的Header是非常长的,为了传输一个很小的数据 需要付出巨大的代价,是很不合算的,占用了很多的宽带。
轮询与长轮询都是基于HTTP的,两者本身存在着缺陷:轮询需要更快的处理速度;长轮询则更要求处理并发的能力;两者都是“被动型服务器”的体现:服务器不会主动推送信息,而是在客户端发送ajax请求后进行返回的响应。而理想的模型是”在服务器端数据有了变化后,可以主动推送给客户端”,这种”主动型”服务器是解决这类问题的很好的方案。
短轮询
短轮询是服务器收到请求不管是否有数据都直接响应请求
受到响应隔一段时间在发送同样的请求查询是否有数据;
缺点:
实时性低
长轮询
长轮询是服务器收到请求后如果有数据, 立刻响应请求;
如果没有数据就会 hold 一段时间,这段时间内如果有数据立刻响应请求;
如果时间到了还没有数据, 则响应 http 请求;浏览器受到 http 响应后立在发送一个同样http 请求查询是否有数据;
缺点:
浏览器端对统一服务器同时连接有最大限制, 最好同一用户只存在一个长轮询;
服务器端没有数据 hold 住连接时会造成浪费, 容易产生服务器瓶颈;
如果说这个是长轮询的话,短轮询会进行更多的http请求,会浪费更多的资源。
短连接
所谓短连接,及连接只保持在数据传输过程,请求发起,连接建立,数据返回,连接关闭。它适用于一些实时数据请求,配合轮询来进行新旧数据的更替。
长连接
长连接便是在连接发起后,在请求关闭连接前客户端与服务端都保持连接,实质是保持这个通信管道,之后便可以对其进行复用。
它适用于涉及消息推送,请求频繁的场景(直播,流媒体)。连接建立后,在该连接下的所有请求都可以重用这个长连接管道,避免了频繁了连接请求,提升了效率。
多个 http 请求共用一个 tcp 连接; 这样可以减少多次临近 http 请求导致 tcp建立关闭所产生的时间消耗.
http 1.1 中在请求头和相应头中用 connection
字段标识是否是 http长连接, connection: keep-alive, 表明是 http 长连接; connection:closed, 表明服务器关闭 tcp 连接
与 connection 对应的一个字段是 keep-live
, http 响应头中出现, 他的格式是 timeout=30,max=5
, timeout 是两次 http 请求保持的时间(s), , max 是这个 tcp 连接最多为几个 http请求重用
以上描述的短连接、长连接、短轮询、长轮询
缺点:会导致过多不必要的请求,浪费流量和服务器资源,每一次请求、应答,都浪费了一定流量在相同的头部信息上
然而WebSocket的出现可以弥补这一缺点。在WebSocket中,只需要服务器和浏览器通过HTTP协议进行一个握手的动作,然后单独建立一条TCP的通信通道进行数据的传送。
WebSocket与HTTP的关系
相同点
1.都是一样基于TCP的,都是可靠性传输协议。
2.都是应用层协议。
不同点
1.WebSocket是双向通信协议,模拟Socket协议,可以双向发送或接受信息。HTTP是单向的。
2.WebSocket是需要握手进行建立连接的
WebSocket的加护机制如图所示
相对于传统 HTTP 每次请求-应答都需要客户端与服务端建立连接的模式,WebSocket 是类似 Socket 的 TCP 长连接的通讯模式,一旦 WebSocket 连接建立后,后续数据都以帧序列的形式传输。在客户端断开 WebSocket 连接或 Server 端断掉连接前,不需要客户端和服务端重新发起连接请求。
使用WebSocket建立相应的客户端和服务器
客户端的建立可以使用vue等前端去实现
而服务器我们可以使用springboot去建立隧道
现在我们主要来说一下使用vue建立客户端
当建立客户端的时候为了考虑websocket的断线重连
通过查阅资料了解到 nginx 代理的 websocket 转发,无消息连接会出现超时断开问题。网上资料提到解决方案两种,一种是修改nginx配置信息,第二种是websocket发送心跳包。
websocket超时没有消息自动断开连接,应对措施:这时候我们就需要知道服务端设置的超时时长是多少,在小于超时时间内发送心跳包,有2中方案:一种是客户端主动发送上行心跳包,另一种方案是服务端主动发送下行心跳包。
<script>
export default {
data() {
return {
ws: null, // WebSocket 实例
retryCount: 0, // 当前重试次数
maxRetries: 10, // 最大重试次数
isConnected: false // WebSocket 连接状态
};
},
methods: {
createWebSocket() {
this.ws = new WebSocket('ws://your-websocket-url');
this.ws.onopen = () => {
console.log('Connected to WebSocket');
this.isConnected = true;
this.retryCount = 0; // 成功连接时重置重试计数
};
this.ws.onclose = () => {
this.isConnected = false;
console.log('WebSocket closed');
if (this.retryCount < this.maxRetries) {
this.retryCount++;
console.log(`Attempting to reconnect... Retry #${this.retryCount}`);
setTimeout(this.createWebSocket, 5000); // 重连延迟5秒
} else {
console.log('Max retry attempts reached. Giving up.');
}
};
this.ws.onerror = (error) => {
console.error('WebSocket error: ', error);
this.ws.close();
};
this.ws.onmessage = (message) => {
console.log('Received message: ', message.data);
};
}
},
mounted() {
this.createWebSocket(); // 在组件挂载时创建 WebSocket 连接
},
beforeDestroy() {
if (this.ws) {
this.ws.close(); // 在组件销毁时关闭 WebSocket 连接
}
}
};
</script>
据代码所示:解决方案为客户端主动发送上行心跳包并当重新连接至一定次数后直至主动断开连接
这也仅仅只是完成了客户端的构建,我们还需要一个WebSocket服务器去承载客户端的请求并获取大模型的返回值,这里就要用到一开始咱们提到的api接口和request请求体(指将数据格式获取后以统一格式返回给大模型获取输出值)
在完成websocket的服务器搭建前,我们也得配置一些有关的信息,例如我们springboot所需要的依赖
还有我们需要指定的yml,方便我们打印日志并且返回具体信息
server:
web:
allowed-origins: "http://localhost:8080"
port: 8080 # 设置应用的端口号
spring:
websocket:
# 如果需要设置特定的 WebSocket 配置
enabled: true
logging:
level:
org.springframework.web.socket: DEBUG # 启用 WebSocket 的日志级别(可选)
org.springframework: DEBUG
org.apache.ibatis: DEBUG
之后便是配置具体的kimi的响应URL和APIKey
并将请求体的格式配置出来,再利用前端传递过来的参数进行替换,将最后得出的请求体用来访问api接口
为了允许前端对后端api的调用我们也得设置一下配置类WebSocketConfig,使得其接收所有来源的请求
这里的后端处理运用了大量的WebSocket提供的方法类,可以带大家看一下并了解有关方法的实现及作用
afterConnectionEstablished
作用:在 WebSocket 连接建立之后被调用。通常用于在客户端连接建立后执行一些初始化操作,如认证、注册、资源分配等。
参数:WebSocketSession表示当前的 WebSocket 会话,它包含了有关连接的详细信息。
异常:如果出现异常,方法可以抛出 Exception,比如连接错误等
handleMessage
作用:处理客户端发送的消息。根据消息的类型(文本、二进制、Ping/Pong等),具体的消息处理逻辑会被执行。实现该方法时,需要根据不同消息类型(如 TextMessage、BinaryMessage等)来处理不同的消息内容。
参数:
session:当前的 WebSocket 会话对象,包含关于客户端连接的所有信息。
message:客户端发送的 WebSocket 消息,它可以是文本消息、二进制消息或其他类型的消息。
文本消息(TextMessage)
文本消息用于传输字符串数据,WebSocket允许通过UTF-8编码的文本消息交换数据。通常用于传输JSON数据、HTML内容、聊天信息。
TextMessage textMessage = new TextMessage("Hello,how are you?");
二进制消息(BinaryMessage)
二进制消息用于传输二进制数据(例如图片、文件、音频等)。WebSocket协议通过ByteBuffer或byte[]来传递二进制数据。二进制消息可以传输更复杂的数据类型,比如图像、视频、音频文件等。
byte[] imageData = Files.readAllBytes(Path.get("path/to/image.jpg"));
BinaryMessage binaryMessage = new BinaryMessage(imageData);
服务器收到后,会调用handleBinaryMessage方法来处理这个二进制消息。
Ping/Pong消息(PingMessage和PongMessage)
Ping/Pong消息用于WebSocket连接的心跳机制。PingMessage由客户端发送,PongMessage由服务器响应。它们用于检查连接是否仍然活跃,以避免连接超时。
Ping:客户端可以定期发送PingMessage,询问服务器是否还在线。
Pong:服务器响应PingMessage,以表明它仍然可用。
PingMessage:客户端向服务器发送一个PingMessage:
PingMessage pingMessage = new PingMessage();
PongMessage:服务器响应客户端的PingMessage。返回PongMessage:
PongMessage pongMessage = new PongMessage();
void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception;
根据方法进行 logger.info 第一行是session,第二行是TextMessage
WebSocketHandler 中处理不同类型的消息
在官方的AbstractWebSocketHandler类中提供了继承WebSocketHandler的方法去处理不同类型的信息
handleTransportError
void handleTransportError(WebSocketSession session, Throwable exception) throws Exception;
作用:处理 WebSocket 连接中的错误,如网络错误或协议错误等。这个方法通常用于捕获 WebSocket 连接中的传输错误。
参数:
session
:当前 WebSocket 会话对象,包含了连接信息。
exception
:具体的错误或异常信息。
异常:如果出现异常,方法可以抛出 Exception
,例如在处理错误时发生的其他问题。
这里我们重写的话就将网络的错误请求打印至日志和控制台即可
afterConnectionClosed
void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception;
作用:在 WebSocket 连接关闭之后被调用,通常用于执行连接关闭后的清理工作,如释放资源、注销用户、更新状态等。
参数:
session:当前 WebSocket 会话对象。
closeStatus:关闭状态,提供了关闭连接时的一些信息(如是否正常关闭)。
异常:如果出现异常,方法可以抛出 Exception
,例如在连接关闭后进行清理时发生错误。
supportsPartialMessages
boolean supportsPartialMessages();
作用:指示 WebSocket 是否支持处理部分消息。通常,WebSocket 消息是完整的,但有时消息可能会分成多个小部分传输(例如,发送大文件时)。该方法返回 true 表示支持部分消息,false 表示不支持。
返回值:
true:支持处理部分消息。
false:不支持部分消息
@Override
public boolean supportsPartialMessages() {
return true; // 表示支持部分消息
}
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
if (message instanceof BinaryMessage) {
BinaryMessage binaryMessage = (BinaryMessage) message;
// 处理二进制数据
System.out.println("Received binary data, length: " + binaryMessage.getPayload().length);
// 假设这是一个分段上传的大文件
byte[] fileData = binaryMessage.getPayload().array();
// 将文件数据拼接(假设这是分段上传过程中的一部分)
// 假设有一个持久化存储,用于将不同段的数据拼接成完整文件
appendToFileStorage(session.getId(), fileData);
}
}
如果 supportsPartialMessages
返回 true
,WebSocket 会支持将大消息分成多个小部分进行传输,适用于大文件传输或数据流式处理。
如果返回 false
,WebSocket 只能一次性传输完整的消息,不支持将消息拆分成多个部分。
完成代码后的界面如图所示,可以完整实现与ai的交流通道
如果需要源代码可以关注公众号,公众号搜索 “念小云” 回复“Kimi”即可。