当前位置: 首页 > article >正文

CPP集群聊天服务器开发实践(一):用户注册与登录

目录

1 客户端用户注册与登录

1.1 主要思想

1.2 网络层

1.3 业务层

1.4 数据层

1.5 测试结果


1 客户端用户注册与登录

1.1 主要思想

实现网络层、业务层、数据层的解耦,提高系统的可维护性。

网络层:主要实现对客户端连接、客户端读写请求的捕获与回调,将其分发到多个线程中执行。

业务层:主要实现客户端读写请求回调的具体操作,当前阶段主要包含:登录业务、注册业务、用户异常退出业务

数据层:主要实现数据库中表的CUAD操作(增删改查)

1.2 网络层

利用muduo网络库实现epoll+线程池模式的网络模式。此模式具有模板化的特性,使用时可以原封不动的照搬。其原理可以查看前面关于muduo网络库以及epoll原理讲解的文章。其主要分为以下几个部分:

(1)组合Tcpserver对象以及Eventloop对象

(2)public下定义构造函数,实现上述对象的初始化以及处理连接的onConnection和处理读写的onMessage回调函数的注册

(3)private下定义onConnection和onMessage回调函数的具体实现

其中,在onConnection中:主要实现用户的连接以及用户异常关闭的情况,用户的连接直接调用muduo库的connected()方法;用户异常关闭则调用业务层定义的方法。

在onMessage中,为了实现网络层和业务层的解耦,通过定义哈希map _msgHandlerMap 将用户操作(msgid表征)和对应的回调处理进行一对一映射。即根据用户发来的msgid来获取对应的handler,获取到对应的handler后执行相应的业务。

整体来说由于muduo库强大的功能,实现比较简单,具体源码如下:

#include"chatserver.hpp"
#include"json.hpp"
#include<functional>
#include<string>
#include"chatservice.hpp"
using namespace std;
using namespace placeholders;
using json=nlohmann::json;ChatServer::ChatServer(EventLoop *loop,const InetAddress &listenAddr,const string &nameArg) : _server(loop, listenAddr, nameArg), _loop(loop)
{//注册连接回调_server.setConnectionCallback(std::bind(&ChatServer::onConnection,this,_1));//注册读写回调_server.setMessageCallback(std::bind(&ChatServer::onMessage,this,_1,_2,_3));//设置线程数量_server.setThreadNum(4);
}void ChatServer::start()
{_server.start();
}// 上报连接相关信息的回调函数
void ChatServer::onConnection(const TcpConnectionPtr &conn)
{//客户端断开连接if (!conn->connected()){//客户端异常关闭ChatService::instance()->clientCloseException(conn);conn->shutdown();}
}
// 上报读写事件相关信息的回调函数
void ChatServer::onMessage(const TcpConnectionPtr &conn,Buffer *buffer,Timestamp time)
{string buf=buffer->retrieveAllAsString();//数据的反序列化json js=json::parse(buf);//实现网络模块和业务模块的解耦:通过hs["msgid"]调取对应业务handler(回调业务代码),可以将conn,js,time传给handler,执行回调//解耦操作一般两种:1.面向基类2.回调操作//js["msgid"].get<int>():利用get方法将json对象转换为int类型,get方法提供的是模板auto msgHanndler=ChatService::instance()->getHandler(js["msgid"].get<int>());//回调消息绑定好的事件处理器,来执行相应的业务处理msgHanndler(conn,js,time);
}

1.3 业务层

上文提到,网络层和业务层解耦的关键就是回调操作。网络层获取到用户发来的msgid后,根据_msgHandlerMap找到对应的handler,每个handler都和对应的login或者register操作进行绑定,找到对应的handler后即执行相应的操作。其主要分为以下几个部分:

(1)构造函数中注册消息(msgid)以及对应的handler回调操作

(2)处理登录业务:根据id找到对应的user对象,验证用户是否已经登录,之后验证password是否正确,登录成功修改用户状态并进行响应

(3)处理注册业务:根据用户name 和 password进行注册,将自增的id作为账号返回给用户

(4)处理用户异常退出业务:定义关于用户id和状态信息的映射_userConnMap,从哈希表中获取异常关闭的用户id,修改数据库的状态,并删除哈希表中对应条目

业务层的实现主要靠回调操作,同时涉及单例模式、互斥锁等一些知识,具体可以查看专栏前面分享的文章。

具体源码如下:

#include"chatservice.hpp"
#include"public.hpp"
//利用muduo库的封装好的日志输出
#include<muduo/base/Logging.h>
using namespace muduo;//获取单例对象的接口函数
ChatService* ChatService::instance()
{static ChatService service;return &service;
}
// 注册消息以及对应的Handler回调操作
ChatService::ChatService()
{_msgHandlerMap.insert({LOGIN_MSG,std::bind(&ChatService::login,this,_1,_2,_3)});_msgHandlerMap.insert({REG_MSG,std::bind(&ChatService::reg,this,_1,_2,_3)});
}MsgHandler ChatService::getHandler(int msgid)
{//记录错误日志,msgid没有对应的事件处理回调auto it = _msgHandlerMap.find(msgid);if (it == _msgHandlerMap.end()){return [=](const TcpConnectionPtr &conn, json &js, Timestamp time){LOG_ERROR << "msgid: " << msgid << "can not find handler";};}else{return _msgHandlerMap[msgid];}
}// 处理登录业务 id+pwd 验证pwd
void ChatService::login(const TcpConnectionPtr &conn, json &js, Timestamp time)
{LOG_INFO<<"DO LOGIN SERVICE!";int id=js["id"];string pwd=js["password"];//查询id对应的user对象User user = _userModel.query(id);if(user.getId()==id&&user.getPwd()==pwd){if(user.getState()=="online"){//该用户已经登录,不允许重复登录json response;response["msgid"]=LOGIN_MSG_ACK;//响应error number如果为0,表示业务成功response["errno"] = 2;response["errmsg"] = "该账号已经登录,请重新输入新账号";//组装好json通过网络返回给客户端conn->send(response.dump());//数据序列化}else{//登录成功,记录用户连接信息/*群组聊天时,onMessage会被多线程调用,同时这个记录用户连接的map也会被多线程调用并且这个map会不断发生变化,需要考虑 线程安全 的问题*/{lock_guard<mutex> lock(_connMutex);_userConnMap.insert({id,conn});}//登录成功,更新用户state->onlineuser.setState("online");_userModel.updateState(user);json response;response["msgid"]=LOGIN_MSG_ACK;//响应error number如果为0,表示业务成功response["errno"] = 0;response["id"]=user.getId();response["name"]=user.getName();//组装好json通过网络返回给客户端conn->send(response.dump());//数据序列化}}else{//用户不存在或者用户存在密码错误,登录失败json response;response["msgid"]=LOGIN_MSG_ACK;//响应error number如果为0,表示业务成功response["errno"] = 1;response["errmsg"] = "用户名或者密码错误";//组装好json通过网络返回给客户端conn->send(response.dump());//数据序列化}
}
// 处理注册业务 
void ChatService::reg(const TcpConnectionPtr &conn, json &js, Timestamp time)
{LOG_INFO << "DO REGISTER SERVICE!";string name = js["name"];string pwd = js["password"];User user;user.setName(name);user.setPwd(pwd);bool state = _userModel.insert(user);if (state){// 注册成功,返回客户端json消息,并把自增的id返回给用户当作账号json response;response["msgid"]=REG_MSG_ACK;//响应error number如果为0,表示业务成功response["errno"] = 0;response["id"]=user.getId();//组装好json通过网络返回给客户端conn->send(response.dump());//数据序列化}else{// 注册失败json response;response["msgid"]=REG_MSG_ACK;//响应error number如果为1,表示业务不成功response["errno"] = 1;//组装好json通过网络返回给客户端conn->send(response.dump());//数据序列化}
}//处理客户端异常退出
void ChatService::clientCloseException(const TcpConnectionPtr &conn)
{User user;//做两件事:1.用户数据库state->offline 2._userConnMap中用户信息删除{lock_guard<mutex> lock(_connMutex);//遍历_userConnMapfor(auto it=_userConnMap.begin();it!=_userConnMap.end();++it){if(it->second==conn){user.setId(it->first);//从map表删除用户的连接信息_userConnMap.erase(it);break;}}}//更新用户的状态信息if(user.getId()!=-1){user.setState("offline");_userModel.updateState(user);}
}

1.4 数据层

数据层与业务层解耦的关键在于ORM架构的实现,可以有效防止操作数据库过程中重复的sql代码,通过将数据库的信息封装为对象进行操作,其核心主要包含dp.cpp(数据库连接、数据库更新、数据库查询功能);user.hpp(数据表的对象封装);usermodel.cpp(对user对象的增删改查操作)。主要包含以下内容:

(1)dp.cpp:可以理解为数据库的底层操作,主要依赖于mysql库提供的方法,实现数据库的连接、更新、查询操作

(2)user.hpp:主要是对user表的对象实例化封装,根据表的字段定义变量(私有),并提供get、set的公有化方法

(3)usermodel.cpp:这一层与业务层紧密相关,根据业务需要对user对象提供插入、查询、更新状态的方法,并调用底层的mysql库方法(dp.cpp中的内容)。具体步骤包含:a. 组装sql语句 b. 连接数据库 c.调用底层操作。

具体源码如下:

dp.cpp:

#include"db.h"
#include<muduo/base/Logging.h>
//数据库配置信息
static string server = "127.0.0.1";
static string user = "root";
static string password = "123456";
static string dbname = "chat";//初始化数据库连接,为连接开辟资源
MySQL::MySQL()
{_conn = mysql_init(nullptr);
}//释放数据库连接资源
MySQL::~MySQL()
{if(_conn!=nullptr){mysql_close(_conn);}
}//连接数据库
bool MySQL::connect()
{MYSQL *p = mysql_real_connect(_conn, server.c_str(), user.c_str(), password.c_str(),dbname.c_str(), 3306, nullptr, 0);if (p != nullptr){// C和C++代码默认的编码字符是ASCII,如果不设置,从Mysql上拉取的数据不支持汉字mysql_query(_conn, "set names gbk");LOG_INFO << "connect mysql success!";}else{LOG_INFO << "connect mysql fail!";}return p;
}//更新操作
bool MySQL::update(string sql)
{if (mysql_query(_conn, sql.c_str())){LOG_INFO << __FILE__ << ":" << __LINE__ << ":"<< sql << "更新失败!";return false;}return true;
}//查询操作
MYSQL_RES* MySQL::query(string sql)
{if (mysql_query(_conn, sql.c_str())){LOG_INFO << __FILE__ << ":" << __LINE__ << ":"<< sql << "查询失败!";return nullptr;}return mysql_use_result(_conn);
}//获取连接
MYSQL *MySQL::getConnection()
{return _conn;
}

user.hpp:

#ifndef USER_H
#define USER_H#include<string>
using namespace std;
/*
mysql> SHOW COLUMNS FROM user;
+----------+--------------------------+------+-----+---------+----------------+
| Field    | Type                     | Null | Key | Default | Extra          |
+----------+--------------------------+------+-----+---------+----------------+
| id       | int                      | NO   | PRI | NULL    | auto_increment |
| username | varchar(50)              | NO   | UNI | NULL    |                |
| password | varchar(50)              | NO   |     | NULL    |                |
| state    | enum('online','offline') | YES  |     | offline |                |
+----------+--------------------------+------+-----+---------+----------------+
*/
//定义数据库对象,将数据库信息整合为一个对象提交给业务层
//匹配User表的ORM类
class User{
public:User(int id = -1, string name = "", string pwd = "", string state = "offline"){this->id = id;this->name = name;this->password = pwd;this->state = state;}void setId(int id){this->id = id;}void setName(string name){this->name = name;}void setPwd(string pwd){this->password = pwd;}void setState(string state){this->state = state;}int getId(){return this->id;}string getName(){return this->name;}string getPwd(){return this->password;}string getState(){return this->state;}
private:int id;string name;string password;string state;
};
#endif

usermodel.cpp:

#include"usermodel.hpp"
#include"db.h"
#include<iostream>
#include<muduo/base/Logging.h>
using namespace std;
//User表的增加方法
bool UserModel::insert(User &user)
{//1. 组成sql语句char sql[1024] = {0};sprintf(sql, "insert into user(username,password,state) values('%s', '%s', '%s')",user.getName().c_str(), user.getPwd().c_str(), user.getState().c_str());LOG_INFO<<sql;//2.连接数据库MySQL mysql;if (mysql.connect()){if(mysql.update(sql)){//获取插入成功的用户数据生成的主键iduser.setId(mysql_insert_id(mysql.getConnection()));return true;}}return false;
}//根据用户号码查询用户信息
User UserModel::query(int id)
{//1. 组成sql语句char sql[1024] = {0};sprintf(sql, "select * from user where id = %d",id);//2.连接数据库MySQL mysql;if (mysql.connect()){MYSQL_RES* res= mysql.query(sql);//res不为空,查询成功if(res!=nullptr){//返回查到的行,得到的是字符串,可以用[ ]取值MYSQL_ROW row = mysql_fetch_row(res);if(row!=nullptr){User user;//Convert a string to an integer.user.setId(atoi(row[0]));user.setName(row[1]);user.setPwd(row[2]);user.setState(row[3]);//res使用指针动态分配资源,需要释放,防止内存泄漏mysql_free_result(res);return user;}}}//返回默认user,匿名对象return User();
}//更新用户的状态信息
bool UserModel::updateState(User user)
{//1. 组成sql语句char sql[1024] = {0};sprintf(sql, "update user set state='%s' where id = %d",user.getState().c_str(), user.getId());MySQL mysql;if(mysql.connect()){if(mysql.update(sql)){return true;}}return false;
}

1.5 测试结果

主要针对用户注册、登录、用户异常退出业务的测试。

可以看到,客户端发送json字符串可以实现响应的业务,同时用户异常退出时数据库相应的状态也会变为offline。

相关文章:

CPP集群聊天服务器开发实践(一):用户注册与登录

目录 1 客户端用户注册与登录 1.1 主要思想 1.2 网络层 1.3 业务层 1.4 数据层 1.5 测试结果 1 客户端用户注册与登录 1.1 主要思想 实现网络层、业务层、数据层的解耦&#xff0c;提高系统的可维护性。 网络层&#xff1a;主要实现对客户端连接、客户端读写请求的捕获…...

C++ Attribute 属性说明符

目录 属性说明符 Attribute编译警告相关[[deprecated]][[maybe_unused]][[fallthrough]][[nodiscard]] 可能触发编译优化[[noreturn]][[likely]]、[[unlikely]][[assume]][[carries_dependency]][[no_unique_address]] 属性说明符 Attribute 属性说明符Attribute自C11起&#…...

Elasticsearch去分析目标服务器的日志,需要在目标服务器上面安装Elasticsearch 软件吗

Elasticsearch 本身并不直接收集目标服务器的日志&#xff0c;它主要用于存储、搜索和分析数据。要收集目标服务器的日志&#xff0c;通常会借助其他工具&#xff0c;并且一般不需要在目标服务器上安装 Elasticsearch 软件&#xff0c;常见的日志收集方案&#xff1a; Filebeat…...

学JDBC 第二日

数据库连接池 作用 使数据库连接达到重用的效果&#xff0c;较少的消耗资源 原理 在创建连接池对象时&#xff0c;创建好指定个数的连接对象 之后直接获取连接对象使用即可&#xff0c;不用每次都创建连接对象 从数据库连接池中获取的对象的close方法真的关闭连接对象了吗…...

Android双屏异显Presentation接口使用说明

在点餐、收银、KTV等场景,对于双屏异显的需求是非常多的,首先可以节省硬件成本。而现在的智能板卡很多运行Android系统,从Android4.2开始支持WiFi Display(Miracast)功能后,就开始支持双屏异显Presentation这套应用层接口了,下面以Android5.1系统来说明这套接口的使用要…...

【Uniapp-Vue3】z-paging插件组件实现触底和下拉加载数据

一、下载z-paing插件 注意下载下载量最多的这个 进入Hbuilder以后点击“确定” 插件的官方文档地址&#xff1a; https://z-paging.zxlee.cn 二、z-paging插件的使用 在文档中向下滑动&#xff0c;会有使用方法。 使用z-paging标签将所有的内容包起来 配置标签中的属性 在s…...

JDK 9新特性学习大纲

第1部分&#xff1a;引言与背景 第1章&#xff1a;JDK 9的诞生与目标 1.1 JDK 9的核心目标与设计哲学 1.2 JDK 9的重要更新概览 1.3 兼容性与升级策略 第2部分&#xff1a;模块化系统&#xff08;Project Jigsaw&#xff09; 第2章&#xff1a;模块化基础 2.1 模块化的背景…...

【C语言标准库函数】三角函数

目录 一、头文件 二、函数简介 2.1. 正弦函数&#xff1a;sin(double angle) 2.2. 余弦函数&#xff1a;cos(double angle) 2.3. 正切函数&#xff1a;tan(double angle) 2.4. 反正弦函数&#xff1a;asin(double value) 2.5. 反余弦函数&#xff1a;acos(double value)…...

Redisson全面解析:从使用方法到工作原理的深度探索

文章目录 写在文章开头详解Redisson基本数据类型基础配置字符串操作列表操作映射集阻塞队列延迟队列更多关于Redisson详解Redisson 中的原子类详解redisson中的发布订阅模型小结参考写在文章开头 Redisson是基于原生redis操作指令上进一步的封装,屏蔽了redis数据结构的实现细…...

声明式导航,编程式导航,导航传参,下拉刷新

1.页面导航 1.声明式导航 1.1跳转到tabBar页面 1.2跳转到非tabBar页面 1.2后退导航 、 2.编程式导航 2.1跳转到tabBar页面 2.1跳转到非tabBar页面 2.3后退导航 3.导航传参 3.1声名式导航传参 3.2编程式导航传参 3.3在onLoad中接受参数 4.下拉刷新 4.1回顾下拉刷新…...

金和OA C6 DownLoadBgImage任意文件读取漏洞

金和OA C6 DownLoadBgImage任意文件读取漏洞 漏洞描述 金和C6数据库是一款针对企业信息化管理而设计的高级数据库管理系统&#xff0c;主要应用于企业资源规划&#xff08;ERP&#xff09;、客户关系管理&#xff08;CRM&#xff09;以及办公自动化&#xff08;OA&#xff09…...

激活函数篇 03 —— ReLU、LeakyReLU、ELU

本篇文章收录于专栏【机器学习】 以下是激活函数系列的相关的所有内容: 一文搞懂激活函数在神经网络中的关键作用 逻辑回归&#xff1a;Sigmoid函数在分类问题中的应用 整流线性单位函数&#xff08;Rectified Linear Unit, ReLU&#xff09;&#xff0c;又称修正线性单元&a…...

UdpServer

Udp服务端&#xff1a; using System; using System.Collections.Generic; using System.Linq; using System.Net.Sockets; using System.Net; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Diagnostics; using System.IO; using …...

PromptSource安装报错

一、现象 运行命令&#xff1a;streamlit run promptsource/app.py 报错&#xff1a; streamlit run promptsource/app.py Traceback (most recent call last): File "/usr/local/bin/streamlit", line 5, in <module> from streamlit.cli import main File …...

前端学习-页面尺寸事件以及阻止默认行为(三十三)

目录 前言 页面尺寸事件 语法 检测屏幕宽度 获取宽高 元素尺寸的位置 总结 示例代码 阻止默认行为 阻止冒泡 语法 阻止冒泡如何做 阻止元素默认行为如何做 总结 前言 晚上好各位 页面尺寸事件 会在窗口尺寸改变的时候触发条件 语法 window.addEventListener(…...

Kafka 入门与实战

一、Kafka 基础 1.1 创建topic kafka-topics.bat --bootstrap-server localhost:9092 --topic test --create 1.2 查看消费者偏移量位置 kafka-consumer-groups.bat --bootstrap-server localhost:9092 --describe --group test 1.3 消息的生产与发送 #生产者 kafka-cons…...

5 计算机网络

5 计算机网络 5.1 OSI/RM七层模型 5.2 TCP/IP协议簇 5.2.1:常见协议基础 一、 TCP是可靠的&#xff0c;效率低的&#xff1b; 1.HTTP协议端口默认80&#xff0c;HTTPSSL之后成为HTTPS协议默认端口443。 2.对于0~1023一般是默认的公共端口不需要注册&#xff0c;1024以后的则需…...

【华为OD机考】华为OD笔试真题解析(1)--AI处理器组合

一、题目描述 某公司研发了一款高性能AI处理器&#xff0c;每台物理设备具备8颗AI处理器&#xff0c;编号分别为0、1、2、3、4、5、6、7。 编号0~3的处理器处于同一链路中&#xff0c;编号4~7的处理器处于另外一个链路中&#xff0c;不同链路中的处理器不能通信&#xff0c;如…...

程序员也可以这样赚钱

最近有朋友和我交流了关于程序员副业的想法&#xff0c;我想借这个机会对目前软件开发常用的兼职平台做一个梳理。 以下是程序员接副业的靠谱平台推荐&#xff0c;结合政策合规性、平台口碑及实际操作性整理&#xff0c;覆盖国内外主流选择&#xff1a; 一、国内综合型平台 程序…...

VMware虚拟机安装、创建Ubuntu虚拟机及汉化设置全流程详细教程

一、安装VMware Workstation 下载VMware 访问官网&#xff1a;https://www.vmware.com 选择适合的版本&#xff08;如 Workstation Pro 或 VMware Player&#xff0c;后者免费&#xff09;。完成下载后运行安装程序。 网盘下载&#xff1a; 链接: https://pan.baidu.com/s/1MQ…...

HTTP协议学习大纲

第一阶段&#xff1a;HTTP基础概念 互联网与Web基础 理解Web工作原理&#xff1a;客户端-服务器模型URL与URI的结构及区别端口、协议、域名概念 HTTP协议概览 HTTP的作用与特点&#xff08;无状态、无连接、可扩展&#xff09;HTTP协议版本演进&#xff08;0.9 → 1.0 → 1.1 …...

系统URL整合系列视频四(需求介绍补充)

视频 系统URL整合系列视频四&#xff08;需求补充说明&#xff09; 视频介绍 &#xff08;全国&#xff09;大型分布式系统Web资源URL整合需求&#xff08;补充&#xff09;讲解。当今社会各行各业对软件系统的web资源访问权限控制越来越严格&#xff0c;控制粒度也越来越细。…...

21.2.7 综合示例

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 【例 21.7】【项目&#xff1a;code21-007】填充职员表并打印。 本例使用到的Excel文件为&#xff1a;职员信息登记表.xlsx&#x…...

【大模型】DeepSeek与chatGPT的区别以及自身的优势

目录 一、前言二、核心技术对比2.1 模型架构设计2.1.1 ChatGPT的Transformer架构2.1.2 DeepSeek的混合架构 2.2 训练数据体系2.2.1 ChatGPT的数据特征2.2.2 DeepSeek的数据策略 三、应用场景对比3.1 通用场景表现3.1.1 ChatGPT的强项领域3.2.2 DeepSeek的专项突破 3.3 响应效率…...

burpsuite抓取html登陆和上传数据包

一、burpsuite抓取html登陆数据包 1、先写一个html格式的登陆页面 <!doctype html> <html lang"en"> <head><meta charset"UTF-8"><title>这是标签</title></head> <body> <hr><!-- 登陆表单 …...

python 使用OpenAI Whisper进行显卡推理语音翻译

目录 一、Whisper简介 二、模型资料 三、实操案例 3.1 默认使用CPU进行推理 3.2 使用GPU进行推理 四、性能分析 一、Whisper简介 Whisper由 OpenAI 开发的这款强大模型,能轻松将语音转化为文字,并且支持多种语言(如英语、中文、西班牙语等),让全球沟通无障碍。 不仅…...

余数相同问题(信息学奥赛一本通-1080)

【题目描述】 已知三个正整数a&#xff0c;b&#xff0c;c。现有一个大于1的整数x&#xff0c;将其作为除数分别除a&#xff0c;b&#xff0c;c&#xff0c;得到的余数相同。请问满足上述条件的x的最小值是多少&#xff1f;数据保证x有解。 【输入】 一行&#xff0c;三个不大于…...

用 Python 给 Excel 表格截图(20250207)

我搜索了网络上的方案&#xff0c;感觉把 Excel 表格转换为 HTML 再用 platwright 截图是比较顺畅的路径&#xff0c;因为有顺畅的工具链。如果使用的是 Windows 系统则不需要阅读此文&#xff0c;因为 win32com 库更方便。这篇文章中 Excel 转 HTML 的方案&#xff0c;主要弥补…...

Linux 安装 Ollama

1、下载地址 Download Ollama on Linux 2、有网络直接执行 curl -fsSL https://ollama.com/install.sh | sh 命令 3、下载慢的解决方法 1、curl -fsSL https://ollama.com/install.sh -o ollama_install.sh 2、sed -i s|https://ollama.com/download/ollama-linux|https://…...

使用Ollama本地部署deepseek

1、下载安装Ollama 前往下载页面 https://ollama.com/download下载好安装包&#xff0c;如同安装软件一样&#xff0c;直接安装即可 win中默认为C盘&#xff0c;如果需要修改到其他盘&#xff0c;查找具体教程 运行list命令&#xff0c;检查是否安装成功 2、修改模型下载的…...