项目9-网页聊天室8(消息的发送和接收之websocket)

news2024/11/21 0:30:08

这是整个项目最最核心的部分.

但是这个部分的编写,需要依赖"基础设施"

包括不限于前面已经实现的 主界面,用户管理,会话管理, 好友管理, 消息管理 等等....

发送消息,和接收消息,需要"实时传输

张三 发了一条消息,李四 这边立即就能接收到,

这样的机制, 基于 HTTP 实现,有点困难~~

服务器主动发消息给李四???

按照之前的讨论,张三和李四,不能直接通信(NAT)必须是张三发消息给服务器,服务器转发给 李四.(服务器有 外网 IP,张三李四都能访问到)
张三发给服务器,张三是客户端, 聊天程序是服务器客户端主动发消息给服务器,很正常.(本来客户端就是主动发起请求的一方)
服务器把消息转发给李四,李四也是客户端,聊天程序是服务器服务器要主动发响应给客户端了???这个事情是不太寻常的!!!!

以往写 HTTP 系列的程序,都是第一种, 客户端发起请求了,服务器才返回响应.

客户端不发请求,服务器就不返回响应~~
第二种情况, HTTP 程序中还没遇到过~~ 这个情况称为"服务器推送数据给客户端

对于 HTTP 协议来说,第一种情况是主要的使用方式,第二种消息推送机制,其实在 HTTP 中不太容易实现
当然也可以使用 HTTP 模拟实现消息推送的效果~~
轮询 

轮询方式的问题
1.消耗更多的系统资源.
接收方,等待过程中,需要频繁的给服务器发起请求,这些请求里,大部分都是"空转的
2.获取到消息不够及时.
需要等到下个请求周期才能拿到数据
如果提高轮询的频率,此时获取消息就及时了,但是系统资源消耗就更多如果降低轮询的频率,此时获取消息就慢了,但是系统消耗资源减少点。

此处引入了一个更好的方案,来解决上述"消息推送问题"-Websocket

1.websocket

1.1 基础

WebSocket 协议完整解析 - 知乎 (zhihu.com)

websocket rfc 6455

RFC 6455: The WebSocket Protocol (rfc-editor.org)

 1.2 握手过程

当客户端想要使用 WebSocket 协议与服务端进行通信时, 首先需要确定服务端是否支持 WebSocket 协议, 因此 WebSocket 协议的第一步是进行握手, WebSocket 握手采用 HTTP Upgrade 机制, 客户端可以发送如下所示的结构发起握手 (请注意 WebSocket 握手只允许使用 HTTP GET 方法):

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13

在 HTTP Header 中设置 Upgrade 字段, 其字段值为 websocket, 并在 Connection 字段指示 Upgrade, 服务端若支持 WebSocket 协议, 并同意握手, 可以返回如下所示的结构:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
Sec-WebSocket-Version: 13

1.3 常见错误说法

websocket是基于HTTP实现的(该说法是错误的)

websoket和HTTP是并列的关系

如何基于 websocket 编写 代码.
在 Java 中有两种形式来使用 websocket
1.直接使用 tomcat 提供的原生 websocket api
2.使用 spring 给咱们提供的 websocket api
基于 Spring 的 websocket api, 先写个简单的 hello world

1.4 WebSocket的使用 

万字详解,带你彻底掌握 WebSocket 用法(至尊典藏版)写的不错_websocket使用-CSDN博客

1.服务器代码

2.客户端部分

// 给这个 websocket 注册上一些回调函数.
        websocket.onopen = function() {
            // 连接建立完成后, 就会自动执行到.
            console.log("websocket 连接成功!");
        }

        websocket.onclose = function() {
            // 连接断开后, 自动执行到.
            console.log("websocket 连接断开!");
        }

        websocket.onerror = function() {
            // 连接异常时, 自动执行到
            console.log("websocket 连接异常!");
        }

        websocket.onmessage = function(e) {
            // 收到消息时, 自动执行到
            console.log("websocket 收到消息! " + e.data);
        }

websocket 的客户端和服务器都是分成这四个阶段来进行处理,
1.连接建立成功之后
2.收到消息后

3.连接关闭后
4.连接异常后

/
// 操作 websocket
/

// 创建 websocket 实例
// let websocket = new WebSocket("ws://127.0.0.1:8080/WebSocketMessage");
let websocket = new WebSocket("ws://" + location.host + "/WebSoketMessage");
// let websocket = new WebSocket("ws://" + location.host + "/message/getMessage");
websocket.onopen = function() {
    console.log("websocket 连接成功!");
}

websocket.onmessage = function(e) {
    console.log("websocket 收到消息! " + e.data);
    // 此时收到的 e.data 是个 json 字符串, 需要转成 js 对象
    let resp = JSON.parse(e.data);
    if (resp.type == 'message') {
        // 处理消息响应
        handleMessage(resp);
    } else {
        // resp 的 type 出错!
        console.log("resp.type 不符合要求!");
    }
}

websocket.onclose = function() {
    console.log("websocket 连接关闭!");
}

websocket.onerror = function() {
    console.log("websocket 连接异常!");
}

2.会话的区分

1.Servlet/Spring 自带的 HttpSession在登录的时候用到了

2.MessageSession 咱们自己聊天程序中创建的"会话",业务上的会话

3.WebSocketSession websocket 里面使用的会话
会话这个词,其实是"广义"概念

3.前后端交互

张三发请求,李四获得响应,张三也要有响应

【tips】

4.客户端实现发送消息操作/展示接收的消息

/
// 实现消息发送/接收逻辑
/

function initSendButton() {
    // 1. 获取到发送按钮 和 消息输入框
    let sendButton = document.querySelector('.right .ctrl button');
    let messageInput = document.querySelector('.right .message-input');
    // 2. 给发送按钮注册一个点击事件
    sendButton.onclick = function() {
        // a) 先针对输入框的内容做个简单判定. 比如输入框内容为空, 则啥都不干
        if (!messageInput.value) {
            // value 的值是 null 或者 '' 都会触发这个条件
            return;
        }
        // b) 获取当前选中的 li 标签的 sessionId
        //页面刚刚加载时,selected未被绑定
        //同样也需要进行判断
        let selectedLi = document.querySelector('#session-list .selected');
        if (selectedLi == null) {
            // 当前没有 li 标签被选中. 
            return;
        }
        let sessionId = selectedLi.getAttribute('message-session-id');
        // c) 构造 json 数据
        let req = {
            type: 'message',
            sessionId: sessionId,
            content: messageInput.value
        };
        req = JSON.stringify(req);
        console.log("[websocket] send: " + req);
        // d) 通过 websocket 发送消息
        websocket.send(req);
        // e) 发送完成之后, 清空之前的输入框
        messageInput.value = '';
    }
}

initSendButton();

/
// 操作 websocket
/

// 创建 websocket 实例
// let websocket = new WebSocket("ws://127.0.0.1:8080/WebSocketMessage");
let websocket = new WebSocket("ws://" + location.host + "/WebSoketMessage");
// let websocket = new WebSocket("ws://" + location.host + "/message/getMessage");
websocket.onopen = function() {
    console.log("websocket 连接成功!");
}

websocket.onmessage = function(e) {
    console.log("websocket 收到消息! " + e.data);
    // 此时收到的 e.data 是个 json 字符串, 需要转成 js 对象
    let resp = JSON.parse(e.data);
    if (resp.type == 'message') {
        // 处理消息响应
        handleMessage(resp);
    } else {
        // resp 的 type 出错!
        console.log("resp.type 不符合要求!");
    }
}

websocket.onclose = function() {
    console.log("websocket 连接关闭!");
}

websocket.onerror = function() {
    console.log("websocket 连接异常!");
}

function handleMessage(resp) {
    // 把客户端收到的消息, 给展示出来. 
    // 展示到对应的会话预览区域, 以及右侧消息列表中. 

    // 1. 根据响应中的 sessionId 获取到当前会话对应的 li 标签. 
    //    如果 li 标签不存在, 则创建一个新的
    let curSessionLi = findSessionLi(resp.sessionId);
    if (curSessionLi == null) {
        // 就需要创建出一个新的 li 标签, 表示新会话. 
        curSessionLi = document.createElement('li');
        curSessionLi.setAttribute('message-session-id', resp.sessionId);
        // 此处 p 标签内部应该放消息的预览内容. 一会后面统一完成, 这里先置空
        curSessionLi.innerHTML = '<h3>' + resp.fromName + '</h3>'
            + '<p></p>';
        // 给这个 li 标签也加上点击事件的处理
        curSessionLi.onclick = function() {
            clickSession(curSessionLi);
        }
    }
    // 2. 把新的消息, 显示到会话的预览区域 (li 标签里的 p 标签中)
    //    如果消息太长, 就需要进行截断. 
    let p = curSessionLi.querySelector('p');
    p.innerHTML = resp.content;
    if (p.innerHTML.length > 10) {
        p.innerHTML = p.innerHTML.substring(0, 10) + '...';
    }
    // 3. 把收到消息的会话, 给放到会话列表最上面. 
    let sessionListUL = document.querySelector('#session-list');
    sessionListUL.insertBefore(curSessionLi, sessionListUL.children[0]);
    // 4. 如果当前收到消息的会话处于被选中状态, 则把当前的消息给放到右侧消息列表中. 
    //    新增消息的同时, 注意调整滚动条的位置, 保证新消息虽然在底部, 但是能够被用户直接看到. 
    if (curSessionLi.className == 'selected') {
        // 把消息列表添加一个新消息. 
        let messageShowDiv = document.querySelector('.right .message-show');
        addMessage(messageShowDiv, resp);
        scrollBottom(messageShowDiv);
    }
    // 其他操作, 还可以在会话窗口上给个提示 (红色的数字, 有几条消息未读), 还可以播放个提示音.  
    // 这些操作都是纯前端的. 实现也不难, 不是咱们的重点工作. 暂时不做了. 
}

5.服务器实现接收/转发消息

为了能够维护刚才所讨论的键值对映射关系,就需要知道当前 websocket 连接是哪个 userld 进行的.
在请求中虽然没有,但是在 HtpSession 中是有的!!

最初在用户登录的时候,给 HtpSession 里存了当前的 user 对象.

//通过注册这个特定的Httpsession 拦藏器,就可以把用户给Http5ession中添加的 Attribute 键值对
//往我们的 WebSocketsession 里也添加一份

package com.example.demo.component;

import com.example.demo.config.Result;
import com.example.demo.constant.Constant;
import com.example.demo.mapper.MessageMapper;
import com.example.demo.mapper.MessageSessionMapper;
import com.example.demo.model.*;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;
import java.util.List;


@Component
@EnableWebSocket
public class WebSocketApi extends TextWebSocketHandler {
    @Autowired
    private OnlineUserManager onlineUserManager;
    @Autowired
    private MessageMapper messageMapper;
    @Autowired
    private MessageSessionMapper messageSessionMapper;
    //内置的
    private ObjectMapper objectMapper=new ObjectMapper();
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        System.out.println("TestApi连接成功");
        User user=(User) session.getAttributes().get(Constant.USERINFO_SESSION_KEY);
        if(user==null){
            return;
        }
        //把键值对存起来
        onlineUserManager.online(user.getUserId(), session);
        /*if(result.getStatus()==Constant.HAVE_LOGIN){
            return result;
        }*/
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        System.out.println("TestApi收到消息"+message.toString());
        //1.获取当前用户的信息,进行消息的转发、
        User user=(User) session.getAttributes().get(Constant.USERINFO_SESSION_KEY);
        if(user==null){
            System.out.println("未登录用户,无法进行消息转发");
            return;
        }
        //2.针对请求进行解析,把json格式的字符串,转成一个Java对象
        MessageRequest messageRequest=objectMapper.readValue(message.getPayload(),MessageRequest.class);
        if(messageRequest.getType().equals("message")){
            //进行消息转发
            transferMessage(user,messageRequest);
        }else {
            System.out.println("信息有误,无法进行消息转发"+message.getPayload());
        }

    }
    //进行消息的转发
    private void transferMessage(User user, MessageRequest messageRequest) throws IOException {
        // 1. 先构造一个待转发的响应对象. MessageResponse
        /*private Integer fromId;
        private String fromName;
        private Integer sessionId;
        private String content;*/
        MessageResponse response=new MessageResponse();
        response.setFromId(user.getUserId());
        response.setFromName(user.getUsername());
        response.setSessionId(messageRequest.getSessionId());
        response.setContent(messageRequest.getContent());
        // 把这个 java 对象转成 json 格式字符串
        String respJson=objectMapper.writeValueAsString(response);
        System.out.println("[transferJson]"+respJson);
        // 2. 根据请求中的 sessionId, 获取到这个 MessageSession 里都有哪些用户. 通过查询数据库就能够知道了.
        List<Friend> friends=
                messageSessionMapper.getFriendBySessionId(messageRequest.getSessionId(), user.getUserId());
        // 此处注意!!! 上述数据库查询, 会把当前发消息的用户给排除掉. 而最终转发的时候, 则需要也把发送消息的人也发一次.
        // 把当前用户也添加到上述 List 里面
        Friend myself=new Friend();
        myself.setFriendId(user.getUserId());
        myself.setFriendName(user.getUsername());
        friends.add(myself);
        // 3. 循环遍历上述的这个列表, 给列表中的每个用户都发一份响应消息
        //    注意: 这里除了给查询到的好友们发, 也要给自己也发一个. 方便实现在自己的客户端上显示自己发送的消息.
        //    注意: 一个会话中, 可能有多个用户(群聊). 虽然客户端是没有支持群聊的(前端写起来相对麻烦), 后端无论是 API 还是 数据库
        //          都是支持群聊的. 此处的转发逻辑也一样让它支持群聊.
        for(Friend friend:friends){
            // 知道了每个用户的 userId, 进一步的查询刚才准备好的 OnlineUserManager, 就知道了对应的 WebSocketSession
            // 从而进行发送消息
            WebSocketSession webSocketSession=onlineUserManager.getSession(friend.getFriendId());
            if(webSocketSession==null){
                //如果该用户未在线,则不发送
                continue;
            }
            webSocketSession.sendMessage(new TextMessage(respJson));
        }
        // 4. 转发的消息, 还需要放到数据库里. 后续用户如果下线之后, 重新上线, 还可以通过历史消息的方式拿到之前的消息.
        //    需要往 message 表中写入一条记录.
        Message message=new Message();
        message.setSessionId(messageRequest.getSessionId());
        message.setContent(messageRequest.getContent());
        message.setFromId(user.getUserId());
        //像自增主键, 还有时间这样的属性, 都可以让 SQL 在数据库中生成
        messageMapper.add(message);
    }

    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        System.out.println("TestApi出现异常");
        User user=(User) session.getAttributes().get(Constant.USERINFO_SESSION_KEY);
        if(user==null){
            return;
        }
        onlineUserManager.offline(user.getUserId(), session);
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        System.out.println("TestApi正常关闭");
        User user=(User) session.getAttributes().get(Constant.USERINFO_SESSION_KEY);
        if(user==null){
            return;
        }
        onlineUserManager.offline(user.getUserId(), session);
    }
}
@Component
public class OnlineUserManager {
    //为了防止多线程所出现的错误,选择ConcurrentHashMap
    private ConcurrentHashMap<Integer, WebSocketSession> sessions=new ConcurrentHashMap<>();
    //1.用户上线,给这个哈希表中插入键值对
    public void online(Integer userId,WebSocketSession webSocketSession){
        if(sessions.get(userId)!=null){
            //此时说明用户已经在线了,就登录失败,不会记录这个映射关系.
            //不记录这个映射关系,后续就收不到任何消息(毕竟,咱们此处是通过映射关系来实现消息转发的)
            System.out.println(userId+"已登录,请不要重复登陆");
            return;
        }
        sessions.put(userId,webSocketSession);
        System.out.println(userId+"上线");
    }

    //2.用户下线,针对此哈希表删除元素
    public void offline(Integer userId,WebSocketSession webSocketSession){
        WebSocketSession exwebSocketSession=sessions.get(userId);
        if(exwebSocketSession==webSocketSession){
            //两个session为同一个才是真正的下线操作,否则什么也不干
            sessions.remove(userId);
            System.out.println(userId+"下线");
        }
    }

    // 3) 根据 userId 获取到 WebSocketSession
    public WebSocketSession getSession(int userId) {
        return sessions.get(userId);
    }
}

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

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

相关文章

进程间通信(下)

1. system V共享内存 共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间&#xff0c;这些进程间数据传递不再涉及到内核&#xff0c;换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据 那么这到底是为什么呢&#xff1f; 1.1 共享内存示意…

用户账户的权限管理

用户账户的权限管理 用户账号&#xff1a; 1、超级用户 &#xff1a;管理员账号 root 默认对本机拥有最高权限的账户&#xff0c;在系统中唯一。 2、普通用户&#xff1a;一般由管理员创建&#xff0c;拥有的权限是受限制的&#xff0c;一般只在自己的家目录中拥有完整的权限…

【无标题】亚马逊5月24日宣布推出2024出口跨境物流加速器计划

亚马逊中国5月24日郑重宣布启动“2024亚马逊出口跨境物流加速器计划”&#xff0c;旨在依托其世界领先的物流网络和前沿技术&#xff0c;结合本土资源&#xff0c;不断优化跨境物流服务&#xff0c;以强化中国卖家在跨境物流供应链管理方面的能力&#xff0c;进而提升整体效率&…

DI-engine强化学习入门(四)DI-ZOO强化学习——MUJOCO环境搭建

上一章我们搭建了Atari的强化学习环境&#xff0c;这一期我们来搭建MUJOCO强化学习环境 那么我们为什么要“多此一举”呢&#xff1f; 一、概述 MuJoCo和Atari对比 Atari&#xff1a; 类型&#xff1a;Atari游戏是一系列的经典视频游戏。简单性&#xff1a;Atari游戏通常有相…

Leetcode—2769. 找出最大的可达成数字【简单】

2024每日刷题&#xff08;139&#xff09; Leetcode—2769. 找出最大的可达成数字 实现代码 class Solution { public:int theMaximumAchievableX(int num, int t) {return num t * 2;} };运行结果 之后我会持续更新&#xff0c;如果喜欢我的文章&#xff0c;请记得一键三连…

深入理解python列表遍历:两种方法详解与实例

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、引言 二、使用索引遍历列表 三、直接使用元素遍历列表 四、总结 一、引言 在编程过程…

【设计模式】JAVA Design Patterns——Callback(回调模式)

&#x1f50d;目的 回调是一部分被当为参数来传递给其他代码的可执行代码&#xff0c;接收方的代码可以在一些方便的时候来调用它。 &#x1f50d;解释 真实世界例子 我们需要被通知当执行的任务结束时。我们为调用者传递一个回调方法然后等它调用通知我们。 通俗描述 回调是一…

人生苦短,我学python之数据类型(上)

个人主页&#xff1a;星纭-CSDN博客 系列文章专栏&#xff1a;Python 踏上取经路&#xff0c;比抵达灵山更重要&#xff01;一起努力一起进步&#xff01; 目录 一.元组 &#xff08;tuple&#xff09; 二.集合&#xff08;set&#xff09; 三.字典(dict) 一.元组 &#…

科技前沿:IDEA插件Translation v3.6 带来革命性更新,翻译和发音更智能!

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

【论文速读】Transformer:Attention Is All You Need

Transformer&#xff1a;Attention Is All You Need 摘要模型架构注意力模型Scaled Dot-ProductMulti-Head Attention Position-wise Feed-Forward NetworksEmbeddings and SoftmaxPositional Encoding 摘要 我们提出了一种新的简单的网络架构&#xff0c;Transformer&#xf…

OSPF多区域组网实验(思科)

华为设备参考&#xff1a;OSPF多区域组网实验&#xff08;华为&#xff09; 技术简介 OSPF多区域功能通过划分网络为多个逻辑区域来提高网络的可扩展性和管理性能。每个区域内部运行独立的SPF计算&#xff0c;而区域之间通过区域边界路由器进行路由信息交换。这种划分策略适用…

如何构建基因单倍型网络

一. 单倍型网络 单倍型网络图通常是指在遗传学和进化生物学中使用的一种图形表示法&#xff0c;用于描述不同个体或群体之间的遗传关系。这种图形通常用于研究基因或DNA序列在不同个体之间的变化和传递。在单倍型网络图中&#xff0c;节点表示不同的单倍型&#xff0c;而边表示…

自己搭建ntp服务器

1、背景 终端在使用过程中&#xff0c;发现联网后进行NTP(network time protocol&#xff0c;网络时间协议)校时过程中&#xff0c;经常出现校时失败的问题&#xff0c;特别是本地环境&#xff0c;因此需要在自己这边搭建测试环境。 2、搭建步骤 2.1 安葬服务器 sudo apt-get…

Python项目:数据可视化_下载数据【笔记】

源自《Python编程&#xff1a;从入门到实践》 作者&#xff1a; Eric Matthes 02 下载数据 2.1 sitka_weather_07-2021_simple.csv from pathlib import Path import matplotlib.pyplot as plt import csv from datetime import datetimepath Path(D:\CH16\sitka_weather_0…

今日早报 每日精选15条新闻简报 每天一分钟 知晓天下事 5月25日,星期六

每天一分钟&#xff0c;知晓天下事&#xff01; 2024年5月25日 星期六 农历四月十八 1、 气象台&#xff1a;未来三天&#xff0c;北方雷雨侵扰&#xff0c;南方暴雨大暴雨将成片出现&#xff0c;今年首个台风生成在即。 2、 人社部&#xff1a;拟增加网络主播等19个新职业&am…

“高考钉子户”唐尚珺决定再战2024年高考

“高考钉子户”唐尚珺决定在2024年再次参加高考&#xff0c;这个选择确实很特别也很有趣。十几年连续参加高考&#xff0c;他已经积累了大量的备考经验和应试技巧。这样的经验对于高考辅导机构来说无疑是非常宝贵的资源&#xff0c;他如果选择去辅导机构当老师&#xff0c;应该…

运算符重载(上)

目录 运算符重载日期类的比较判断日期是否相等判断日期大小 赋值运算符重载赋值运算符重载格式赋值运算符只能重载成类的成员函数不能重载成全局函数用户没有显式实现时&#xff0c;编译器会生成一个默认赋值运算符重载&#xff0c;以值的方式逐字节拷贝 感谢各位大佬对我的支持…

使用llama.cpp实现LLM大模型的格式转换、量化、推理、部署

使用llama.cpp实现LLM大模型的量化、推理、部署 大模型的格式转换、量化、推理、部署概述克隆和编译环境准备模型格式转换GGUF格式bin格式 模型量化模型加载与推理模型API服务模型API服务(第三方)GPU推理 大模型的格式转换、量化、推理、部署 概述 llama.cpp的主要目标是能够在…

微信小程序反编译/解包

微信小程序反编译/解包 环境与工具 操作系统&#xff1a;Windows 11 23H2 微信版本&#xff1a;3.9.10.19 Q&#xff1a;如何找到小程序文件位置&#xff1f; A&#xff1a;在微信的设置找到文件路径&#xff0c;小程序文件位于 \WeChat Files\Applet\。 Q&#xff1a;小程…

centos下yum -y install npm报没有可用软件包 npm

yum -y install npm安装报错 失败原因是因为缺少epel&#xff08;epel是社区打造的免费开源发行软件包版本库&#xff0c;系统包含大概1万多个软件包&#xff09;&#xff0c;需要先安装epel-release 解决方法&#xff1a; 1、先安装epel-release yum -y install epel-releas…