java模拟GPT流式问答

news2025/1/17 13:50:50

流式请求gpt并且流式推送相关前端页面

1)java流式获取gpt答案

1、读取文件流的方式

使用post请求数据,由于gpt是eventsource的方式返回数据,所以格式是data:,需要手动替换一下值

/**
org.apache.http.client.methods
**/
@SneakyThrows
    private void chatStream(List<ChatParamMessagesBO> messagesBOList) {
        CloseableHttpClient httpclient = HttpClients.createDefault();
        HttpPost httpPost = new HttpPost("https://api.openai.com/v1/chat/completions");
        httpPost.setHeader("Authorization","xxxxxxxxxxxx");
        httpPost.setHeader("Content-Type","application/json; charset=UTF-8");

        ChatParamBO build = ChatParamBO.builder()
                .temperature(0.7)
                .model("gpt-3.5-turbo")
                .messages(messagesBOList)
                .stream(true)
                .build();
        System.out.println(JsonUtils.toJson(build));
        httpPost.setEntity(new StringEntity(JsonUtils.toJson(build),"utf-8"));
        CloseableHttpResponse response = httpclient.execute(httpPost);
        try {
            HttpEntity entity = response.getEntity();
            if (entity != null) {
                InputStream inputStream = entity.getContent();
                BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));

                String line;
                while ((line = reader.readLine()) != null) {
                    // 处理 event stream 数据

                    try {
//                        System.out.println(line);
                        ChatResultBO chatResultBO = JsonUtils.toObject(line.replace("data:", ""), ChatResultBO.class);
                        String content = chatResultBO.getChoices().get(0).getDelta().getContent();
                        log.info(content);
                        
//                        System.out.println(chatResultBO.getChoices().get(0).getMessage().getContent());
                    } catch (Exception e) {
//                        e.printStackTrace();
                    }
                }
            }
        } finally {
            response.close();
        }
    }

2、sse链接的方式获取数据

用到了okhttp

需要先引用相关maven:

        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
        </dependency>
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp-sse</artifactId>
        </dependency>
       
        // 定义see接口
        Request request = new Request.Builder().url("https://api.openai.com/v1/chat/completions")
                .header("Authorization","xxx")
                .post(okhttp3.RequestBody.create(okhttp3.MediaType.parse("application/json; charset=utf-8"),param.toJSONString()))
                .build();
        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.MINUTES)
                .readTimeout(10, TimeUnit.MINUTES)//这边需要将超时显示设置长一点,不然刚连上就断开,之前以为调用方式错误被坑了半天
                .build();

        // 实例化EventSource,注册EventSource监听器
        RealEventSource realEventSource = new RealEventSource(request, new EventSourceListener() {

            @Override
            public void onOpen(EventSource eventSource, Response response) {
                log.info("onOpen");
            }

            @SneakyThrows
            @Override
            public void onEvent(EventSource eventSource, String id, String type, String data) {
//                log.info("onEvent");
                log.info(data);//请求到的数据
            
            }

            @Override
            public void onClosed(EventSource eventSource) {
                log.info("onClosed");
//                emitter.complete();
            }

            @Override
            public void onFailure(EventSource eventSource, Throwable t, Response response) {
                log.info("onFailure,t={},response={}",t,response);//这边可以监听并重新打开
//                emitter.complete();
            }
        });
        realEventSource.connect(okHttpClient);//真正开始请求的一步

2)流式推送答案

方法一:通过订阅式SSE/WebSocket

原理是先建立链接,然后不断发消息就可以

1、websocket

创建相关配置:


import javax.websocket.Session;

import lombok.Data;

/**
 * @description WebSocket客户端连接
 */
@Data
public class WebSocketClient {

    // 与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;

    //连接的uri
    private String uri;

}


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}
配置相关service


@Slf4j
@Component
@ServerEndpoint("/websocket/chat/{chatId}")
public class ChatWebsocketService {

    static final ConcurrentHashMap<String, List<WebSocketClient>> webSocketClientMap= new ConcurrentHashMap<>();

    private String chatId;

    /**
     * 连接建立成功时触发,绑定参数
     * @param session 与某个客户端的连接会话,需要通过它来给客户端发送数据
     * @param chatId 商户ID
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("chatId") String chatId){

        WebSocketClient client = new WebSocketClient();
        client.setSession(session);
        client.setUri(session.getRequestURI().toString());

        List<WebSocketClient> webSocketClientList = webSocketClientMap.get(chatId);
        if(webSocketClientList == null){
            webSocketClientList = new ArrayList<>();
        }
        webSocketClientList.add(client);
        webSocketClientMap.put(chatId, webSocketClientList);
        this.chatId = chatId;
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message) {
        log.info("chatId = {},message = {}",chatId,message);
        // 回复消息
        this.chatStream(BaseUtil.newList(ChatParamMessagesBO.builder().content(message).role("user").build()));
//        this.sendMessage(chatId,message+"233");
    }

    /**
     * 连接关闭时触发,注意不能向客户端发送消息了
     * @param chatId
     */
    @OnClose
    public void onClose(@PathParam("chatId") String chatId){
        webSocketClientMap.remove(chatId);
    }

    /**
     * 通信发生错误时触发
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        System.out.println("发生错误");
        error.printStackTrace();
    }

    /**
     * 向客户端发送消息
     * @param chatId
     * @param message
     */
    public void sendMessage(String chatId,String message){
        try {
            List<WebSocketClient> webSocketClientList = webSocketClientMap.get(chatId);
            if(webSocketClientList!=null){
                for(WebSocketClient webSocketServer:webSocketClientList){
                    webSocketServer.getSession().getBasicRemote().sendText(message);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        }
    }

    /**
     * 流式调用查询gpt
     * @param messagesBOList
     * @throws IOException
     */
    @SneakyThrows
    private void chatStream(List<ChatParamMessagesBO> messagesBOList) {
       // TODO 和GPT的访问请求
    }
}
测试,postman建立链接

2、SSE

本质也是基于订阅推送方式

前端:
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>SseEmitter</title>
</head>

<body>
<button onclick="closeSse()">关闭连接</button>
<div id="message"></div>
</body>
<script>
    let source = null;

    // 用时间戳模拟登录用户
    //const id = new Date().getTime();
		const id = '7829083B42464C5B9C445A087E873C7D';
   
    if (window.EventSource) {

        // 建立连接
	
         source = new EventSource('http://172.28.54.27:8902/api/sse/connect?conversationId=' + id);
		setMessageInnerHTML("连接用户=" + id);
        /**
         * 连接一旦建立,就会触发open事件
         * 另一种写法:source.onopen = function (event) {}
         */
        source.addEventListener('open', function(e) {
            setMessageInnerHTML("建立连接。。。");
        }, false);

        /**
         * 客户端收到服务器发来的数据
         * 另一种写法:source.onmessage = function (event) {}
         */
        source.addEventListener('message', function(e) {
			//console.log(e);
            setMessageInnerHTML(e.data);
			
        });

		source.addEventListener("close", function (event) {
			// 在这里处理关闭事件
			console.log("Server closed the connection");
			
			// 可以选择关闭EventSource连接
			source.close();
		});

        /**
         * 如果发生通信错误(比如连接中断),就会触发error事件
         * 或者:
         * 另一种写法:source.onerror = function (event) {}
         */
        source.addEventListener('error', function(e) {
			console.log(e);
            if (e.readyState === EventSource.CLOSED) {
                setMessageInnerHTML("连接关闭");
            } else {
                console.log(e);
            }
        }, false);

    } else {
        setMessageInnerHTML("你的浏览器不支持SSE");
    }

    // 监听窗口关闭事件,主动去关闭sse连接,如果服务端设置永不过期,浏览器关闭后手动清理服务端数据
    window.onbeforeunload = function() {
        //closeSse();
    };

    // 关闭Sse连接
    function closeSse() {
        source.close();
        const httpRequest = new XMLHttpRequest();
        httpRequest.open('GET', 'http://172.28.54.27:8902/api/sse/disconnection?conversationId=' + id, true);
        httpRequest.send();
        console.log("close");
    }

    // 将消息显示在网页上
    function setMessageInnerHTML(innerHTML) {
        document.getElementById('message').innerHTML += innerHTML + '<br/>';
    }
</script>

</html>
后端:
controller


import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.util.Set;
import java.util.function.Consumer;

import javax.annotation.Resource;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

@Validated
@RestController
@RequestMapping("/api/sse")
@Slf4j
@RefreshScope  // 会监听变化实时变化值
public class SseController {

    @Resource
    private SseBizService sseBizService;


    /**
     * 创建用户连接并返回 SseEmitter
     *
     * @param conversationId 用户ID
     * @return SseEmitter
     */
    @SneakyThrows
    @GetMapping(value = "/connect", produces = "text/event-stream; charset=utf-8")
    public SseEmitter connect(String conversationId) {
        // 设置超时时间,0表示不过期。默认30秒,超过时间未完成会抛出异常:AsyncRequestTimeoutException
        SseEmitter sseEmitter = new SseEmitter(0L);
        // 注册回调
        sseEmitter.onCompletion(completionCallBack(conversationId));
        sseEmitter.onError(errorCallBack(conversationId));
        sseEmitter.onTimeout(timeoutCallBack(conversationId));
        log.info("创建新的sse连接,当前用户:{}", conversationId);
        sseBizService.addConnect(conversationId,sseEmitter);
        sseBizService.sendMsg(conversationId,"链接成功");
//        sseCache.get(conversationId).send(SseEmitter.event().reconnectTime(10000).data("链接成功"),MediaType.TEXT_EVENT_STREAM);
        return sseEmitter;
    }

    /**
     * 给指定用户发送信息  -- 单播
     */
    @GetMapping(value = "/send", produces = "text/event-stream; charset=utf-8")
    public void sendMessage(String conversationId, String msg) {
        sseBizService.sendMsg(conversationId,msg);
    }

    /**
     * 移除用户连接
     */
    @GetMapping(value = "/disconnection", produces = "text/event-stream; charset=utf-8")
    public void removeUser(String conversationId) {
        log.info("移除用户:{}", conversationId);
        sseBizService.deleteConnect(conversationId);
    }

    /**
     * 向多人发布消息   -- 组播
     * @param groupId 开头标识
     * @param message 消息内容
     */
    public void groupSendMessage(String groupId, String message) {
       /* if (!BaseUtil.isNullOrEmpty(sseCache)) {
            *//*Set<String> ids = sseEmitterMap.keySet().stream().filter(m -> m.startsWith(groupId)).collect(Collectors.toSet());
            batchSendMessage(message, ids);*//*
            sseCache.forEach((k, v) -> {
                try {
                    if (k.startsWith(groupId)) {
                        v.send(message, MediaType.APPLICATION_JSON);
                    }
                } catch (IOException e) {
                    log.error("用户[{}]推送异常:{}", k, e.getMessage());
                    removeUser(k);
                }
            });
        }*/
    }

    /**
     * 群发所有人   -- 广播
     */
    public void batchSendMessage(String message) {
        /*sseCache.forEach((k, v) -> {
            try {
                v.send(message, MediaType.APPLICATION_JSON);
            } catch (IOException e) {
                log.error("用户[{}]推送异常:{}", k, e.getMessage());
                removeUser(k);
            }
        });*/
    }

    /**
     * 群发消息
     */
    public void batchSendMessage(String message, Set<String> ids) {
        ids.forEach(userId -> sendMessage(userId, message));
    }


    /**
     * 获取当前连接信息
     */
//    public List<String> getIds() {
//        return new ArrayList<>(sseCache.keySet());
//    }

    /**
     * 获取当前连接数量
     */
//    public int getUserCount() {
//        return count.intValue();
//    }

    private Runnable completionCallBack(String userId) {
        return () -> {
            log.info("结束连接:{}", userId);
            removeUser(userId);
        };
    }

    private Runnable timeoutCallBack(String userId) {
        return () -> {
            log.info("连接超时:{}", userId);
            removeUser(userId);
        };
    }

    private Consumer<Throwable> errorCallBack(String userId) {
        return throwable -> {
            log.info("连接异常:{}", userId);
            removeUser(userId);
        };
    }
}
service


import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

@Component
@Slf4j
@RefreshScope  // 会监听变化实时变化值
public class SseBizService {
    /**
     * 
     * 当前连接数
     */
    private AtomicInteger count = new AtomicInteger(0);

    /**
     * 使用map对象,便于根据userId来获取对应的SseEmitter,或者放redis里面
     */
    private Map<String, SseEmitter> sseCache = new ConcurrentHashMap<>();


    /**
     * 添加用户
     * @author pengbin <pengbin>
     * @date 2023/9/11 11:37
     * @param
     * @return
     */
    public void addConnect(String id,SseEmitter sseEmitter){
        sseCache.put(id, sseEmitter);
        // 数量+1
        count.getAndIncrement();
    }
    /**
     * 删除用户
     * @author pengbin <pengbin>
     * @date 2023/9/11 11:37
     * @param
     * @return
     */
    public void deleteConnect(String id){
        sseCache.remove(id);
        // 数量+1
        count.getAndDecrement();
    }

    /**
     * 发送消息
     * @author pengbin <pengbin>
     * @date 2023/9/11 11:38
     * @param
     * @return
     */
    @SneakyThrows
    public void sendMsg(String id, String msg){
        if(sseCache.containsKey(id)){
            sseCache.get(id).send(msg, MediaType.TEXT_EVENT_STREAM);
        }
    }

}

方法二:SSE建立eventSource,使用完成后即刻销毁

前端:在接收到结束标识后立即销毁

/**
         * 客户端收到服务器发来的数据
         * 另一种写法:source.onmessage = function (event) {}
         */
        source.addEventListener('message', function(e) {
			//console.log(e);
            setMessageInnerHTML(e.data);
			if(e.data == '[DONE]'){
				source.close();
			}
        });

后端:
 

@SneakyThrows
    @GetMapping(value = "/stream/sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter completionsStream(@RequestParam String conversationId){
        //
        List<ChatParamMessagesBO> messagesBOList =new ArrayList();

        // 获取内容信息
        ChatParamBO build = ChatParamBO.builder()
                .temperature(0.7)
                .stream(true)
                .model("xxxx")
                .messages(messagesBOList)
                .build();

        SseEmitter emitter = new SseEmitter();
            
        // 定义see接口
        Request request = new Request.Builder().url("xxx")
                .header("Authorization","xxxx")
                .post(okhttp3.RequestBody.create(okhttp3.MediaType.parse("application/json; charset=utf-8"),JsonUtils.toJson(build)))
                .build();
        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .connectTimeout(10, TimeUnit.MINUTES)
                .readTimeout(10, TimeUnit.MINUTES)//这边需要将超时显示设置长一点,不然刚连上就断开,之前以为调用方式错误被坑了半天
                .build();

        StringBuffer sb = new StringBuffer("");

        // 实例化EventSource,注册EventSource监听器
        RealEventSource realEventSource = null;
        realEventSource = new RealEventSource(request, new EventSourceListener() {

            @Override
            public void onOpen(EventSource eventSource, Response response) {
                log.info("onOpen");
            }

            @SneakyThrows
            @Override
            public void onEvent(EventSource eventSource, String id, String type, String data) {

                log.info(data);//请求到的数据
                try {

                    ChatResultBO chatResultBO = JsonUtils.toObject(data.replace("data:", ""), ChatResultBO.class);
                    String content = chatResultBO.getChoices().get(0).getDelta().getContent();
                    sb.append(content);
                    emitter.send(SseEmitter.event().data(JsonUtils.toJson(ChatContentBO.builder().content(content).build())));

                } catch (Exception e) {
//                        e.printStackTrace();
                }
                if("[DONE]".equals(data)){
                    emitter.send(SseEmitter.event().data(data));
                    emitter.complete();
                    log.info("result={}",sb);
                }
            }

            @Override
            public void onClosed(EventSource eventSource) {
                log.info("onClosed,eventSource={}",eventSource);//这边可以监听并重新打开
//                emitter.complete();
            }

            @Override
            public void onFailure(EventSource eventSource, Throwable t, Response response) {
                log.info("onFailure,t={},response={}",t,response);//这边可以监听并重新打开
//                emitter.complete();
            }
        });
        realEventSource.connect(okHttpClient);//真正开始请求的一步
        return emitter;
    }

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

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

相关文章

如何选择适合您需求的SOCKS5代理

SOCKS5协议是最新版本的SOCKS协议&#xff0c;它带来了一系列重要特点&#xff0c;相对于SOCKS4来说引入了许多重要特性&#xff1a; 1. 更多身份验证选项&#xff1a; SOCKS5通过更完整的TCP连接和SSH隧道方法路由流量&#xff0c;支持多种身份验证方法&#xff0c;增强了安全…

竞赛选题 深度学习 机器视觉 车位识别车道线检测 - python opencv

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习 机器视觉 车位识别车道线检测 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f947;学长这里给一个题目综合评分(每项满分5分) …

干货分享|腾讯内部项目管理PPT

我是胖圆&#xff0c;欢迎大家关注留言~ 或者移步公众号【胖圆说PM】找我~

HashMap -- 调研

HashMap 调研 前言JDK1.8之前拉链法: JDK1.8之后JDK1.7 VS JDK1.8 比较优化了一下问题: HashMap的put方法的具体流程?HashMap的扩容resize操作怎么实现的? 前言 在Java中&#xff0c;保存数据有两种比较简单的数据结构:数组和链表。 数组的特点是:寻址容易&#xff0c;插入…

Java实现防重复提交,使用自定义注解的方式

目录 1.背景 2.思路 3.实现 创建自定义注解 编写拦截器 4.使用 5.验证 6.总结 1.背景 在进行添加操作时&#xff0c;防止恶意点击&#xff0c;后端进行请求接口的防重复提交 2.思路 通过拦截器搭配自定义注解的方式进行实现&#xff0c;拦截器拦截请求&#xff0c;使…

如何在 Keras 中开发具有注意力的编码器-解码器模型

link 【翻译自 &#xff1a; How to Develop an Encoder-Decoder Model with Attention in Keras 】 【说明&#xff1a;Jason Brownlee PhD大神的文章个人很喜欢&#xff0c;所以闲暇时间里会做一点翻译和学习实践的工作&#xff0c;这里是相应工作的实践记录&#xff0c;…

大数据Doris(八):启动FE步骤

文章目录 启动FE步骤 一、配置环境变量 二、​​​​​​​创建doris-mate

变分自动编码器 (VAE)02/2 PyTorch 教程

一、说明 在自动编码器中&#xff0c;来自输入数据的信息被映射到固定的潜在表示中。当我们旨在训练模型以生成确定性预测时&#xff0c;这特别有用。相比之下&#xff0c;变分自动编码器&#xff08;VAE&#xff09;将输入数据转换为变分表示向量&#xff08;顾名思义&#xf…

气象台卫星监测vr交互教学增强学生的学习兴趣和动力

对地观测是以地球为研究对象&#xff0c;依托卫星、飞船等光电仪器&#xff0c;进行各种探测活动&#xff0c;其核心是遥感技术&#xff0c;因此为了让遥感专业学员能提前熟悉对地观测规则、流程、方法及注意事项&#xff0c;借助VR虚拟现实制作的三维仿真场景&#xff0c;能让…

全新彩虹商城时光模板知识付费系统源码+内有5000多商品+易支付源码

源码简介&#xff1a; 全新彩虹商城时光模板知识付费系统源码&#xff0c;这是最新的彩虹知识付费商城系统&#xff0c;具备众多强大且实用的功能。首先&#xff0c;它支持二级分类和多级分销&#xff0c;使得商品分类更为清晰&#xff0c;销售网络更具扩展性。 其次&#xf…

机器人轨迹规划算法的研究现状

近年来&#xff0c;随着机器人技术的迅速发展&#xff0c;机器人在工业、医疗、军事等领域的应用越来越广泛。机器人轨迹规划是机器人控制的重要环节之一&#xff0c;它决定了机器人在执行任务时的运动轨迹&#xff0c;直接影响机器人的精度、速度和稳定性。因此&#xff0c;机…

【PCIE720】基于PCIe总线架构的高性能计算(HPC)硬件加速卡

PCIE720是一款基于PCI Express总线架构的高性能计算&#xff08;HPC&#xff09;硬件加速卡&#xff0c;板卡采用Xilinx的高性能28nm 7系列FPGA作为运算节点&#xff0c;在资源、接口以及时钟的优化&#xff0c;为高性能计算提供卓越的硬件加速性能。板卡一共具有5个FPGA处理节…

代码混淆界面介绍

代码混淆界面介绍 代码混淆功能包括oc&#xff0c;swift&#xff0c;类和函数设置区域。其他flutter&#xff0c;混合开发的最终都会转未oc活着swift的的二进制&#xff0c;所以没有其他语言的设置。 代码混淆功能分顶部的显示控制区域&#xff1a;显示方式&#xff0c;风险等…

python 深度学习 解决遇到的报错问题6

目录 一、解决报错HTTPSConnectionPool(hosthuggingface.co, port443): Max retries exceeded with url: /bert-base-uncased/resolve/main/vocab.txt (Caused by ConnectTimeoutError(, Connection to huggingface.co 如何从huggingface官网下载模型 二、nx.draw if cf._ax…

jupyter 切换虚拟环境

当前只有两个环kernel 我已经创建了很多虚拟环境&#xff0c;如何在notebook中使用这些虚拟环境呢&#xff1f;请看下面 比如说我要添加nlp 这个虚拟环境到notebook中 1. 切换到nlp环境 2. 安装如下模块 pip install ipykernel 3. 执行如下命令 python -m ipykernel install …

VS2019如何显示和去除控制台页面

这是控制台页面&#xff1a; 方法&#xff1a; 选中目标项目&#xff0c;右键--->属性--->配置属性--->链接器--->系统--->子系统--->(窗口/控制台)

地级市HVV | 未授权访问合集

在网站前后端分离盛行下&#xff0c;将大部分权限控制交给前端&#xff0c;导致js中隐藏未授权或者可绕过的前端鉴权。前后端分离的好处是提高开发效率&#xff0c;同时防止黑客更直接的对服务器造成危害&#xff0c;但权限控制的工作量全部交给前端会导致大量页面未授权或者后…

面试经典 150 题 1 —(双指针)— 125. 验证回文串

125. 验证回文串 方法一 class Solution { public:bool isPalindrome(string s) {string newStr "";for(int fast 0; fast < s.size(); fast){if(isalnum(s[fast]))){newStr tolower(s[fast]);}}string tmp newStr;reverse(tmp.begin(), tmp.end());if(strcm…

【计算机网络】TCP协议与UDP协议详解

文章目录 一、传输层 1、1 再次理解传输层 1、2 再次理解端口号 1、2、1 端口号范围划分 1、2、2 认识知名端口号 1、3 网络常用指令netstat 与 pidof 二、UDP协议 2、1 UDP协议的报文 2、2 UDP的特点 2、3 UDP的缓冲区 三、TCP协议 3、1 TCP协议的报文 3、2 确认应答 3、3 按…

计算机毕业设计选什么题目好?springboot 个人健康信息管理系统

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…