查看原文
其他

为什么ChatGPT采用SSE协议而不是WebSocket?

路条编程 路条编程
2024-09-05



为什么ChatGPT采用SSE协议而不是WebSocket?

在ChatGPT官网我们可以看到,对话的方式仅仅只有一个post请求,而没有使用IM中使用的websocket链接。同时我们可以看到与普通的post请求不一样的是,返回信息Response没有了,取而代之的是EventStream

若想获取项目完整代码以及其他文章的项目源码,且在代码编写时遇到问题需要咨询交流,欢迎加入下方的知识星球。

那么这个EventStream是什么东西?一通查证后,发现这个是Web API中的通过SSE协议返回的数据。在现代Web应用开发中,实时通信技术尤为重要。Server-Sent Events(SSE)和WebSocket是两种常见的实现方式。虽然WebSocket能够提供双向通信,但在特定的场景下,SSE协议更适合一些实际需求。本文将结合代码示例深入探讨为什么 ChatGPT 采用SSE协议而不是 WebSocket。下面 SSE 和 Websocket 协议的介绍和优缺点:

Server-Sent Events(SSE)协议

1. SSE协议简介

  • Server-Sent Events (SSE) 是基于HTTP协议的一种单向通信协议,允许服务器通过单个HTTP连接持续向客户端发送更新。SSE最常用于向浏览器推送实时数据,例如实时通知、消息更新、股票行情等。

  • 与WebSocket不同,SSE连接是单向的,客户端无法向服务器发送消息。服务器推送的数据通常以纯文本格式发送,并使用标准的HTTP协议传输。

2. SSE的工作原理

  • 连接建立: 客户端通过HTTP GET请求向服务器请求数据流。服务器响应并保持连接打开,不断发送数据。服务器发送的数据使用特定的格式,包括事件名(event)、数据(data)和事件标识符(id)等字段。

  • 数据推送: 服务器向客户端推送数据时,按照文本流的形式发送,浏览器端的JavaScript通过EventSource对象接收和处理这些数据。

  • 连接管理: SSE具有自动重连机制,客户端在连接断开时会自动尝试重新连接,并且可以从上次中断的地方继续接收数据。

3. SSE的优点

  • 简单性: SSE基于标准的HTTP协议,易于实现和管理,不需要特别的握手过程,也不需要客户端和服务器之间的复杂协议切换。

  • 自动重连和断点续传: SSE内置了自动重连机制,并支持断点续传(利用事件标识符),提高了连接的可靠性,尤其在网络不稳定的环境中表现良好。

  • 广泛的HTTP支持: SSE能够更好地与现有的HTTP基础设施兼容,如负载均衡器、代理服务器和防火墙。

4. SSE的缺点

  • 单向通信: SSE仅支持服务器向客户端发送数据,客户端无法主动向服务器发送消息,因此不适合需要双向通信的场景。

  • 消息格式限制: SSE仅支持文本格式的数据推送,无法直接发送二进制数据,虽然可以通过Base64编码实现,但增加了复杂性。

WebSocket协议

1. WebSocket协议简介

  • WebSocket 是一种全双工通信协议,允许客户端和服务器之间建立持久的双向连接。WebSocket协议在传统的HTTP协议基础上扩展,通过升级请求将连接从HTTP切换为WebSocket,从而实现更高效的实时数据传输。

  • 在WebSocket连接建立后,客户端和服务器可以相互发送消息,而无需再次发起HTTP请求。这种持久连接避免了传统HTTP请求中的“请求-响应”延迟,适用于需要频繁双向通信的场景。

2. WebSocket的工作原理

  • 连接建立: WebSocket连接始于客户端发送的HTTP升级请求(Upgrade: websocket),服务器响应101状态码表示切换协议成功,随后连接切换为WebSocket协议。

  • 消息交换: 连接建立后,客户端和服务器可以通过这个单一的连接自由地发送和接收消息。消息可以是文本或二进制数据。

  • 连接关闭: 任一方可以随时关闭连接,连接关闭后需要重新建立才能恢复通信。

3. WebSocket的优点

  • 低延迟: WebSocket的全双工通信能够显著降低通信延迟,适合对实时性要求较高的应用,如实时聊天、在线游戏、股票交易等。

  • 高效的数据传输: 由于WebSocket省略了HTTP请求头等冗余数据,相比HTTP请求,WebSocket的数据传输更加高效。

4. WebSocket的缺点

  • 复杂性: WebSocket协议的实现和管理相对复杂,特别是在需要维护大量连接的场景中,管理连接状态和处理错误需要额外的逻辑。

  • 防火墙和代理问题: WebSocket在穿越防火墙和代理服务器时可能遇到问题,因为它不是标准的HTTP流量,需要额外的配置。

SSE与WebSocket的适用场景

在实际应用中,SSE和WebSocket有各自的适用场景:

  • WebSocket适用场景: WebSocket适合需要频繁的双向通信、低延迟和高吞吐量的应用场景,例如在线游戏、股票交易、协同编辑等。这些场景需要服务器和客户端之间保持持续的双向通信。

  • SSE适用场景: SSE则适合需要服务器单向推送数据的应用,例如实时通知、新闻更新、社交媒体信息流等。这些场景通常只需要服务器向客户端发送数据,并且对低延迟的要求没有WebSocket那么高。

为什么选择SSE协议?

  1. 简化服务器设计

  • SSE只允许服务器向客户端发送数据,这使得服务器的设计更加简化。在很多情况下,如聊天应用或实时通知系统,客户端主要需要接收服务器推送的信息,使用SSE可以避免不必要的双向通信带来的复杂性。

  • 对于ChatGPT这种以文本流为主的应用,服务器主要向客户端推送生成的内容,使用SSE即可满足需求。

  • 更好的HTTP集成

    • SSE基于HTTP协议之上,使用标准的HTTP协议进行传输,能够更好地集成现有的HTTP基础设施,比如代理服务器和负载均衡器。

    • WebSocket虽然也基于HTTP建立连接,但它并不是HTTP协议的扩展,因此在某些场景下需要额外的配置来支持WebSocket的通信。

  • 自动重连

    • SSE内置了断线重连机制,能够在连接断开后自动重连,并且还可以支持重连后的断点续传功能。对于网络环境不稳定的场景,SSE提供了更好的用户体验。

  • 低开销

    • 对于大量客户端连接的场景,SSE的服务器开销较小。WebSocket虽然提供了更强大的功能,但其双向通信和保持连接的特性会带来额外的资源消耗。SSE的单向通信和简单的消息格式更适合大规模的客户端连接场景。

    项目配置和代码示例

    下面我们结合一个简单的 Spring Boot3.3 项目,展示如何使用 SSE 协议推送数据,并通过 Thymeleaf 模板引擎和JavaScript在前端实时显示数据。

    运行效果:

    Maven项目文件(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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>3.3.2</version>
    <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.icoderoad</groupId>
    <artifactId>chatgptsse</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>chatgptsse</name>
    <description>Demo chatgptsse project for Spring Boot</description>

    <properties>
    <java.version>17</java.version>
    </properties>
    <dependencies>
    <!-- Spring Web -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!-- Thymeleaf -->
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    </dependencies>

    <build>
    <plugins>
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
    </plugins>
    </build>

    </project>

    Spring Boot配置(application.yml)
    server:
    port: 8080

    spring:
    thymeleaf:
    cache: false
    后端代码示例

    应用启动类:

    package com.icoderoad.chatgptsse;

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;

    @SpringBootApplication
    public class ChatgptsseApplication {

    public static void main(String[] args) {
    SpringApplication.run(ChatgptsseApplication.class, args);
    }

    }

    ChatGptRestController 类

    package com.icoderoad.chatgptsse.controller;

    import org.springframework.http.MediaType;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

    import java.io.IOException;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;

    @RestController
    public class ChatGptRestController {

    @GetMapping("/stream")
    public SseEmitter stream() {
    // 创建一个SseEmitter对象,超时时间为30秒
    SseEmitter emitter = new SseEmitter(30000L);

    ExecutorService executor = Executors.newSingleThreadExecutor();
    executor.execute(() -> {
    try {
    for (int i = 0; i < 10; i++) {
    // 模拟服务器端的事件推送
    emitter.send("消息编号: " + i, MediaType.TEXT_PLAIN);
    Thread.sleep(1000); // 模拟延时
    }
    // 结束推送
    emitter.complete();
    } catch (IOException | InterruptedException e) {
    // 出现异常时,发送错误信息并完成推送
    emitter.completeWithError(e);
    }
    });
    executor.shutdown();

    return emitter;
    }
    }

    视图显示类

    package com.icoderoad.chatgptsse.controller;

    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.GetMapping;

    @Controller
    public class IndexController {

    @GetMapping("/")
    public String index(Model model) {
    return "index";
    }

    }
    前端代码示例
    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
    <meta charset="UTF-8">
    <title>SSE示例</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <style>
    /* 使用nth-child选择器为每一行设置不同的背景颜色 */
    .list-group-item:nth-child(odd) {
    background-color: #f8f9fa; /* 浅灰色 */
    }

    .list-group-item:nth-child(even) {
    background-color: #e9ecef; /* 更浅的灰色 */
    }

    .list-group-item:nth-child(3n) {
    background-color: #dee2e6; /* 另一种灰色 */
    }
    </style>
    </head>
    <body>
    <div class="container">
    <h1 class="mt-5">实时消息</h1>
    <ul id="messages" class="list-group mt-3"></ul>
    </div>

    <script>
    // 创建一个新的EventSource实例,指向后端的SSE流
    const eventSource = new EventSource('/stream');

    // 监听消息事件,当服务器推送消息时触发
    eventSource.onmessage = function(event) {
    const message = event.data;
    const messageList = document.getElementById('messages');

    // 创建一个新的li元素并添加到消息列表中
    const newMessage = document.createElement('li');
    newMessage.textContent = message;
    newMessage.classList.add('list-group-item');

    messageList.appendChild(newMessage);
    };

    // 处理连接关闭的情况
    eventSource.onopen = function() {
    console.log("连接已建立");
    };

    eventSource.onerror = function() {
    console.log("连接出现错误或已关闭");
    eventSource.close();
    };
    </script>
    </body>
    </html>

    结论

    通过以上的代码示例,我们可以看出SSE协议在实现实时数据推送时具有其独特的优势,特别是在服务器需要向大量客户端推送数据的场景中。与WebSocket相比,SSE的实现更加简单,开销更低,并且更好地与HTTP生态系统集成。因此,对于ChatGPT这种主要需要服务器向客户端推送数据的场景,SSE协议无疑是一个更为合适的选择。

    在未来的Web应用开发中,开发者应根据实际需求选择合适的通信协议,以充分发挥协议的优势,实现高效的实时通信。


    今天就讲到这里,如果有问题需要咨询,大家可以直接留言或扫下方二维码来知识星球找我,我们会尽力为你解答。


    AI资源聚合站已经正式上线,该平台不仅仅是一个AI资源聚合站,更是一个为追求知识深度和广度的人们打造的智慧聚集地。通过访问 AI 资源聚合网站 https://ai-ziyuan.techwisdom.cn/,你将进入一个全方位涵盖人工智能和语言模型领域的宝藏库。


    作者:路条编程(转载请获本公众号授权,并注明作者与出处)
    继续滑动看下一个
    路条编程
    向上滑动看下一个

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存