Linux与HTTP中的Cookie和Session

news2025/3/26 16:50:23

HTTP中的Cookie和Session

本篇介绍

前面几篇已经基本介绍了HTTP协议的大部分内容,但是前面提到了一点「HTTP是无连接、无状态的协议」,那么到底有什么无连接以及什么是无状态。基于这两个问题,随后解释什么是Cookie和Session,以及二者是如何通过代码实现的

本篇的代码部分都是基于上一节的HttpServer

什么是无连接和无状态

HTTP最大的特点就是无连接和无状态

所谓的无连接指的是HTTP请求服务器时不需要HTTP协议自己与服务器建立连接,由于HTTP是基于TCP的,所以整个连接工作交给了TCP,这一点在前面实现HttpServer时也有所体现

所谓的无状态指的是HTTP本身不会保存任何用户信息,举个例子,如果当前网站需要用户登录以进行后续操作,那么默认情况下,一旦这个用户登录成功后切换到其他页面就依旧会出现需要登录的情况

HTTP中的Cookie

基本介绍

既然HTTP是无状态的,那么正常情况下就会出现下面的情况:

在这里插入图片描述

上面图中这种情况的发送就会给用户带来糟糕的交互体验,也会给服务器带来比较大的开销,所以为了解决这个问题,就需要使用Cookie,但是因为HTTP本身是无连接的,所以Cookie本身并不是一种让HTTP从无连接变成有连接的技术,而是利用HTTP请求会携带报头的特点从而让HTTP每次请求时自动携带着Cookie

基于上面的情景,Cookie实际上实现的功能常见的就是保持用户的状态、记录用户偏好等

工作原理

Cookie工作方式与正常写入和读取HTTP请求报头属性比较类似:

  1. 当用户第一次访问网站时,服务器会在响应的HTTP头中设置Set-Cookie字段,用于发送Cookie到用户的浏览器
  2. 浏览器在接收到Cookie后,会将其保存在本地(通常是按照域名进行存储)在之后的请求中,浏览器会自动在HTTP请求头中携带Cookie字段,将之前保存的Cookie信息发送给服务器
  3. 在之后的请求中,浏览器会自动在HTTP请求头中携带Cookie字段,将之前保存的Cookie信息发送给服务器

Cookie的分类

Cookie一般情况下分为两种:

  1. 会话Cookie(Session Cookie):在浏览器关闭时失效
  2. 持久Cookie(Persistent Cookie):带有明确的过期日期或持续时间,可以跨多个浏览器会话存在

如果Cookie是一个持久性的,那么它其实就是浏览器特定目录下的一个文件。但直接查看这些文件可能会看到乱码或无法读取的内容,因为Cookie文件通常以二进制或sqlite格式存储。所以一般想查看Cookie都只能通过浏览器提供的入口进行查看

HttpServer中实现Cookie功能

前置知识

要想在HttpServer中实现给客户端添加Cookie的功能就需要用到一个响应报头属性:Set-Cookie,其值基本形式如下:

Set-Cookie: Cookie-name=Cookie-value

其中,Cookie-name代表的就是需要写入的Cookie的名称,Cookie-value对应的就是Cookie的值

除了上面的基本形式外,Cookie还有一种完整形式:

Set-Cookie: Cookie-name=Cookie-value; expires=Week, Day Month Year Hour:Minutes:Seconds UTC/GMT; path=Request-path; domain=domain-name; secure; Httponly

在上面的完整形式中,每一个字段的含义如下:

字段含义
Cookie-name=Cookie-valueCookie的名称和对应的值,是Set-Cookie中唯一必需的部分
expires指定Cookie的过期时间,格式为标准HTTP日期格式。如果不设置,则为会话Cookie(浏览器关闭即失效),例如expires=Wed, 21 Oct 2025 07:28:00 GMT
path指定Cookie生效的路径范围。例如:path=/admin/表示Cookie只在访问/admin/及其子路径时有效,默认为当前文档路径
domain指定Cookie生效的域名范围。例如:domain=example.com允许Cookie在该域名及其子域名下有效,默认为当前域名(不含子域名)
secure标记Cookie只能通过HTTPS安全连接传输,无值,存在即生效
HttpOnly禁止JavaScript通过document.cookie访问此Cookie,防止XSS攻击窃取Cookie,无值,存在即生效

需要注意的是,如果需要给客户端发送多个Cookie,则不可以直接在同一个Cookie后方拼接,而是需要另起一个Set-Cookie属性

因为Cookie有简单形式和完整形式两种,所以下面先演示简单形式,再扩展到完整形式,但是完整形式中不会演示所有属性,而是针对expirespath这两个属性进行解释

简单形式
写入Cookie

根据Cookie的工作原理第一条,首先需要在客户端第一次请求服务端时向客户端写入一条Cookie值,所以需要在响应报头中添加一条Set-Cookie属性,因为在上一节已经实现了一个登录函数:

void login(HttpRequest &req, HttpResponse &resp)
{
    LOG(LogLevel::INFO) << "进入登录模块";
    req.getParamKv();
}

所以本次考虑将添加Set-Cookie属性放到当前函数中,方式很简单,只需要调用HttpResponse类中的insertRespHead函数即可:

!!! note

本次为了方便就不处理是否是第一次添加或者之前添加的Cookie是否存在
void login(HttpRequest &req, HttpResponse &resp)
{   
    // ...

    // 添加Cookie
    resp.insertRespHead("Set-Cookie", "testCookie=newCookie");
}

添加完Cookie之后就需要向响应报头中写入这一条属性,所谓的写入就是进行序列化,所以接下来需要调用HttpResponse类中的serialize方法以及发送对应的响应报头给客户端,但是这一步实际上已经在处理HTTP请求函数handleHttpRequest做过了,所以就不需要再重复做了

完成上面的步骤后,接下来进行测试,需要注意,要让服务器执行login函数就必须请求/login方法,可以考虑直接在浏览器地址栏中请求/login并携带部分参数,此时使用的就是GET请求方式,也可以考虑走完整的步骤,先请求主页,再请求登录页面,通过登录请求/login资源。本次选择前者,但是为了能够看到具体的效果,需要考虑给响应体写入一点内容,确保浏览器可以停止在该页面,所以还需要调用一个设置响应行和响应体的方法,这里在HttpResponse中提供一个setRespLinesetRespBody方法:

=== “设置响应行”

// 设置响应行
void setRespLine(std::string& line)
{
    _resp_line = line;
}

=== “设置响应体”

void setRespBody(std::string& body)
{
    _resp_body = body;
}

接着,再在login函数中通过resp调用该方法设置响应行和响应体:

void login(HttpRequest &req, HttpResponse &resp)
{
    // ...

    // 设置响应行
    std::string line = default_http_ver + " " + std::to_string(200) + resp.setStatusCodeDesc(200);
    resp.setRespLine(line);

    // 设置响应体
    std::string body = "<h1>Hello Linux</h1>";
    resp.setRespBody(body);
}

测试结果如下:

在这里插入图片描述

但是,上面的方法只能实现发送一个Cookie给客户端,因为对于哈希表来说,key相同,新值会覆盖旧值,当前插入的key都是Set-Cookie,所以会出现新的Cookie键值对覆盖旧的Cookie键值对,为了解决这个问题,可以考虑使用一个vector单独保存每一条设置Cookie的字符串

所以,首先需要一个vector成员用于存储设置Cookie的字符串,接着在设置Cookie时,应该以一个完整的字符串存储到vector中,所以实际上需要下面的方法:

void insertRespCookies(const std::string &cookie_str)
{
    _cookies.push_back(cookie_str);
}

接着,在序列化时直接对每一个设置Cookie的字符串添加换行符:

// 序列化
bool serialize(std::string &out_str)
{
    // ...
    
    // 单独为Cookie添加\r\n
    std::for_each(_cookies.begin(), _cookies.end(), [&](std::string s){
        s += default_sep;
        out_str += s;
    });

    // ...

    return true;
}

最后,修改设置Cookie的login函数:

void login(HttpRequest &req, HttpResponse &resp)
{
    // ...

    // 添加Cookie
    // resp.insertRespHead("Set-Cookie", "testCookie=newCookie");
    resp.insertRespCookies("Set-Cookie: testCookie=newCookie");
    resp.insertRespCookies("Set-Cookie: testCookie1=newCookie1");
    resp.insertRespCookies("Set-Cookie: testCookie2=newCookie2");

    // ...
}

编译运行上面的代码即可看到多条Cookie:

在这里插入图片描述

读取Cookie

当前,服务端已经可以向客户端写入Cookie信息,但是光写入还并不够,服务端还需要读取到对应的Cookie并对Cookie进行解析,因为客户端发送的Cookie形式为:

Cookie: testCookie=newCookie

所以需要根据这个字段格式进行解析

在前面实现HttpRequest中已经实现了读取请求报头中的数据函数getPairFromReqHead,所以在该类中存在一个成员keyCookievaluetestCookie=newCookie的键值对

首先根据key找到对应的value,再将value根据拆分出每一个Cookie

需要注意,尽管服务端是分开设置多个Cookie,但是在客户端给服务端发送Cookie时是合并在一个Cookie中,每一个Cookie以;<space>进行分隔,例如:

```
Cookie: testCookie=newCookie; testCookie1=newCookie1
```

接下来的问题就是如何拆分Cookie,拆分Cookie的思路和拆分请求体参数的方式非常类似,而因为要存储每一个Cookie键值对,所以还需要一个哈希表,所以基本结构如下:

// 键值对分隔符
const std::string default_kv_sep = "=";
// Cookie分隔符
const std::string default_cookie_sep = "; ";

// HTTP请求
class HttpRequest
{
public:
    // ...

    // 获取Cookie键值对
    bool getCookiePairFromReqHead()
    {

    }
    // ...

private:
    // ...

    std::unordered_map<std::string, std::string> _cookie_kv; // Cookie键值对

    // ...
};

接着,根据前面实现从请求体中获取参数键值对的思路实现获取Cookie键值对:

// 获取Cookie键值对
bool getCookiePairFromReqHead()
{
    // 先提取到Cookie
    auto keyPos = _kv.find("Cookie");
    if (keyPos == _kv.end())
        return false;

    // 在对应的value中找到Cookie值
    std::string cookie_value = _kv["Cookie"];

    auto pos = size_t(-1);

    while (true)
    {
        // 找到;
        auto pos1 = cookie_value.find(default_cookie_sep, pos + 1);
        if (pos1 == std::string::npos)
        {
            // 最后一个参数键值对
            auto pos_t = cookie_value.find(default_kv_sep, pos + 1);
            if (pos_t == std::string::npos)
                return false;

            std::string key = cookie_value.substr(pos + 1, pos_t - pos - 1);
            std::string value = cookie_value.substr(pos_t + 1);

            _cookie_kv.insert({key, value});
            break;
        }

        // 找到=
        auto pos2 = cookie_value.find(default_kv_sep, pos + 1);
        if (pos2 == std::string::npos)
            return false;

        std::string key = cookie_value.substr(pos + 1, pos2 - pos - 1);
        std::string value = cookie_value.substr(pos2 + 1, pos1 - pos2 - 1);

        _cookie_kv.insert({key, value});

        // 修改起始位置
        pos = pos1 + 1;
    }

    return true;
}

在上面的代码中,除了函数开始的逻辑与前面获取POST请求参数的逻辑不一样以外,还有更新pos的逻辑,因为不同的Cookie之间的分隔符是两个字符,而不是一个字符

接下来,为了可以看到拆分后的结果,提供一个打印函数:

void getCookieKv()
{
    std::for_each(_cookie_kv.begin(), _cookie_kv.end(), [&](std::pair<std::string, std::string> kv)
                { std::cout << kv.first << ":" << kv.second << std::endl; });
}

有了上面的函数后,接下来就是在指定位置调用该函数:

// 反序列化
bool deserialize(std::string &in_str)
{
    // ...
    // 获取Cookie
    getCookiePairFromReqHead();
    // 打印获取到的Cookie
    getCookieKv();

    // ...

    return true;
}

编译运行上面的代码,观察结果:

在这里插入图片描述

从上面的结果可以看到,所有设置到客户端的Cookie都可以正常获取

完整形式
介绍

上面已经基本实现了简单形式下Cookie的写入和读取,但是有时简单的Cookie并不能满足实际的需求,所以除了完成简单形式以外,还需要实现完整形式的Cookie

虽然是完整形式,但是客户端最后拿到的Cookie会被浏览器进行分析,并将相关的属性填充到浏览器的相关属性中,而客户端第二次以及后面请求服务器时,携带的Cookie也只是上面简单形式的Cookie

基于上面的原因,在完整形式部分只需要实现形成某些字段的函数即可

本次只是实现完整形式中的expirespath,其他字段不考虑

实现expires

在实现expires字段之前,先了解其格式:

expires=Week, Day Month Year Hour:Minutes:Seconds UTC/GMT;

第一个值为星期,第二个值为年月日,第三个值为时分秒,最后一个代表时区,UTC代表协调世界时,GMT代表格林威治平均时间,对于前面的三个字段来说都可以通过系统调用进行获取,唯独最后一个字段需要手动指定,那么选择UTC还是GMT?推荐UTC,其次再是GMT,原因如下:

UTC全称为Coordinated Universal Time (协调世界时),是现代国际社会使用的主要时间标准:基于高精度的原子钟,不受地球自转不规则性的影响,是国际标准化组织ISO推荐的官方时间标准。是现在全球互联网通信和大多数计算机系统的时间基准,格式精确统一,适合跨系统通信

GMT全称为Greenwich Mean Time (格林威治平均时间):历史上的时间标准,基于英国伦敦格林威治天文台的太阳时,其标准基于地球自转,存在微小的不规则性,在科学和技术领域已逐渐被UTC替代,但在日常交流中仍被广泛使用

在Cookie的expires字段中:推荐使用UTC:更精确,是现代网络协议的标准做法。GMT仍然被广泛支持,大多数浏览器能正确处理。实际上两者时间差异极小(不到1秒),但从标准化和兼容性考虑,优先选择UTC

有了知识基础,接下来就是实现获取时间函数,在前面命名管道与共享内存和日志系统已经实现或者使用了一个获取时间的函数,本次实现的思路和该函数基本一致,但是当时使用的是localtime或者localtime_r函数,这两个函数都会自动包含时区,而实际上在设置expires时不需要设置时区,当客户端获取到时间后会自动根据当前浏览器所处的地区设置时区,所以这里推荐使用另外一个系统调用gmtime或者其可重入版本gmtime_r。下面以gmtime为例,函数原型如下:

struct tm *gmtime(const time_t *timep);

使用方式与localtime一样,此处不再介绍。根据格式Wed, 21 Oct 2025 07:28:00 GMT,首先需要两个额外的函数,根据月份数字获取月份字符串和根据星期数字获取星期字符串,接着再使用这两个函数获取一个完整的expires时间:

=== “根据星期数字获取星期字符串”

std::string getWeekStrFromNumber(int w)
{
    // 第一个元素最好是星期天,因为gmtime以星期天为第一天
    const char *weekdays[7] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};

    if (w < 0 || w > 12)
        return std::string();

    return weekdays[w];
}

=== “根据月份数字获取月份字符串”

std::string getMonthStrFromNumber(int m)
{
    const char *months[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};

    if (m < 0 || m > 12)
        return std::string();

    return months[m];
}

=== “获取expires函数”

 std::string getCookieExpiresTime(int last)
 {
     // 获取指定的时间之后的时间戳
     time_t t = time(NULL) + last;
     struct tm *timer = gmtime(&t);
 
     char buffer[1024] = {0};
     // Wed, 21 Oct 2025 07:28:00 GMT
     // 注意,涉及到数字的都需要两位,确保一位数可以有前导0
     snprintf(buffer, sizeof(buffer), "%s, %02d %s %d %02d:%02d:%02d UTC", 
                 getWeekStrFromNumber(timer->tm_wday).c_str(),
                 timer->tm_mday,
                 getMonthStrFromNumber(timer->tm_year).c_str(),
                 timer->tm_year + 1900,
                 timer->tm_hour,
                 timer->tm_min,
                 timer->tm_sec);
 
     return buffer;
 }

接着,在login函数中创建一个带有expires的Cookie,过期时间设置为1分钟:

void login(HttpRequest &req, HttpResponse &resp)
{
    // ...

    std::string cookie_val = "testCookie=newCookie; expires=" + resp.getCookieExpiresTime(60);
    resp.insertRespCookies("Set-Cookie: " + cookie_val);

    // ...
}

编译运行上面的代码,观察结果:

在这里插入图片描述

在控制台打印出实际获取到的时间如下:

在这里插入图片描述

可以看到,这个时间虽然没有时区,但是在浏览器显示时已经会转换成当地时区

实现path

path的实现很简单,只需要在login中设置Cookie时传递path即可:

void login(HttpRequest &req, HttpResponse &resp)
{
    // ...

    resp.insertRespCookies("Set-Cookie: testCookie=newCookie; path=/login.html");

    // ...
}

先设置Cookie:

在这里插入图片描述

此时,testCookie=newCookie一旦被设置,下一层就只会在客户端访问login.html时才会携带该Cookie:

=== “访问主页”

在这里插入图片描述

=== “访问login.html

在这里插入图片描述

Cookie的安全性

在上面的测试中可以发现,不论Cookie是从服务端到客户端,还是客户端到服务端,都是可以在客户端以明文的方式看到的,如果用户输入的是账户名和密码或者其他隐私信息,那么一旦这个Cookie被劫持,就会出现用户信息泄露的问题,这对用户来说是较大的损失,所以Cookie本身是不安全的

HTTP中的Session

基本介绍

上面提到Cookie是不安全的,为了尽可能保证安全这个问题,在现代HTTP中除了使用Cookie外还会使用Session

Session与Cookie不同,Session是存储在服务器端的,当用户第一次请求服务端时,服务端会将用户的信息存储到一个特定的位置,再向客户端写入一个值为sessionID的Cookie,当用户第二次请求服务器时就会携带这个值sessionID的Cookie访问服务器,服务器会根据这个SessionID查找用户的信息,从而实现用户状态的保持

HttpServer中实现Session

要在HttpServer中实现Session,首先可以考虑创建一个Session类,这个类用于存储一个用户的相关信息,相当于之前包含用户信息的Cookie:

class Session
{
public:
    Session(const std::string &username, const std::string &status)
    :_username(username), _status(status)
    {
        _create_time = time(nullptr); // 获取时间戳就行了,后面实际需要,就转化就转换一下
    }
    ~Session()
    {}
public:
    std::string _username;
    std::string _status;
    uint64_t _create_time;
};

因为服务器获取到的肯定不止一个Session,所以还需要一个类对Session类对象进行管理,此处可以考虑创建一个SessionManager类,因为sessionID是一个随机数,并且一般不能出冲突情况,所以这里考虑使用boost库中获取UUID的函数generator

using session_ptr = std::shared_ptr<Session>;

class SessionManager
{
public:
    SessionManager()
    {
    }
    std::string addSession(session_ptr s)
    {
        // 使用boost库生成uuid
        // 创建一个随机数生成器
        boost::uuids::random_generator generator;

        // 生成一个随机 UUID
        boost::uuids::uuid id = generator();
        std::string sessionid = boost::uuids::to_string(id);
        _sessions.insert(std::make_pair(sessionid, s));
        return sessionid;
    }
    session_ptr getSession(const std::string sessionid)
    {
        if (_sessions.find(sessionid) == _sessions.end())
            return nullptr;
        return _sessions[sessionid];
    }
    ~SessionManager()
    {
    }

private:
    std::unordered_map<std::string, session_ptr> _sessions;
};

首先考虑在HttpRequest中创建一个SessionManager类对象指针,并使用SessionManager类对象进行初始化。接着在反序列化时查找出Cookie中是否有sessionid,如果有就根据这个sessionid查找是否已经存在于SessionManager中,如果不存在就创建一个Session对象并插入到SessionManager

但是,在上面创建Session对象时需要使用用户名创建,而这个用户名来自请求参数,如果没有请求参数就不需要创建Session对象,所以需要对前面获取参数的函数进行修改,使其有返回值:

bool getReqParams()
{
    if (_req_method == "GET")
    {
        // 处理GET请求中的参数
        if (getReqParamsFromReqLine())
        {
            _hasArgs = true;
            return true;
        }
    }
    else if (_req_method == "POST")
    {
        // 处理POST请求中的参数
        if (getReqParamsFromBody())
        {
            _hasArgs = true;
            return true;
        }
    }

    return false;
}

接着,在HttpRequest类中添加一个成员_sessionid,并在deserialize函数中补充逻辑:

bool deserialize(std::string &in_str)
{
    // ...

    // 获取参数内容
    if(getReqParams()) 
    {
        // 判断制定的sessionid是否存在,不存在则创建
        if (!_sm->getSession(getSessionIdFromCookiePair()))
        {
            std::string username = _param_kv["username"];
            // 根据参数的username创建Session对象,并处于活跃状态
            std::shared_ptr<Session> s = std::make_shared<Session>(username, "active");
            // 插入到SessionManager中
            _sessionid = _sm->addSession(s);
        }
    }

    return true;
}

接着,在用户请求/login时,创建一个Cookie,这个Cookie的值即为sessionid,所以此处还需要在HttpRequest中提供一个获取_sessiondid的函数:

// 获取_sessionid
std::string getSessionId()
{
    return _sessionid;
}

修改login函数如下:

void login(HttpRequest &req, HttpResponse &resp)
{
    // ...

    std::string sessionid = req.getSessionId();
    std::string cookie_val = "sessionid=" + sessionid;
    resp.insertRespCookies("Set-Cookie: " + cookie_val);

    // ...
}

编译上面的代码,观察结果:

在这里插入图片描述

Session的安全性

Session相比Cookie具有明显的安全优势。由于Session将用户敏感数据存储在服务器端,而不是客户端,只通过SessionID标识用户,大大降低了信息泄露风险。客户端仅存储一个不包含实际用户数据的SessionID,即使这个ID被截获,攻击者也无法直接获取用户的实际信息,如密码和个人资料

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

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

相关文章

linux 备份工具,常用的Linux备份工具及其备份数据的语法

在Linux系统中&#xff0c;备份数据是确保数据安全性和完整性的关键步骤。以下是一些常用的Linux备份工具及其备份数据的语法&#xff1a; 1. tar命令 tar命令是Linux系统中常用的打包和压缩工具&#xff0c;可以将多个文件或目录打包成一个文件&#xff0c;并可以选择添加压…

C++核心语法快速整理

前言 欢迎来到我的博客 个人主页:北岭敲键盘的荒漠猫-CSDN博客 本文主要为学过多门语言玩家快速入门C 没有基础的就放弃吧。 全部都是精华&#xff0c;看完能直接上手改别人的项目。 输出内容 std::代表了这里的cout使用的标准库&#xff0c;避免不同库中的相同命名导致混乱 …

使用HAI来打通DeepSeek的任督二脉

一、什么是HAI HAI是一款专注于AI与科学计算领域的云服务产品&#xff0c;旨在为开发者、企业及科研人员提供高效、易用的算力支持与全栈解决方案。主要使用场景为&#xff1a; AI作画&#xff0c;AI对话/写作、AI开发/测试。 二、开通HAI 选择CPU算力 16核32GB&#xff0c;这…

【day2】数据结构刷题 栈

一 有效的括号 给定一个只包括 (&#xff0c;)&#xff0c;{&#xff0c;}&#xff0c;[&#xff0c;] 的字符串 s &#xff0c;判断字符串是否有效。 有效字符串需满足&#xff1a; 左括号必须用相同类型的右括号闭合。左括号必须以正确的顺序闭合。每个右括号都有一个对应的…

第16章:基于CNN和Transformer对心脏左心室的实验分析及改进策略

目录 1. 项目需求 2. 网络选择 2.1 UNet模块 2.2 TransUnet 2.2.1 SE模块 2.2.2 CBAM 2.3 关键代码 3 对比试验 3.1 unet 3.2 transformerSE 3.3 transformerCBAM 4. 结果分析 5. 推理 6. 下载 1. 项目需求 本文需要做的工作是基于CNN和Transformer的心脏左心室…

云上 Redis 迁移至本地机房

文章目录 摘要在 IDC 搭建读写分离 redis 集群一、环境准备二、部署主从架构1. 安装Redis2. 配置主节点3. 配置从节点4. 所有 Redis 节点设置开机自启动三、部署代理层(读写分离)1. 安装Twemproxy2. 配置Twemproxy3. 配置开机自启动四、高可用配置(哨兵模式)1. 配置哨兵节点…

SQL Server——表数据的插入、修改和删除

目录 一、引言 二、表数据的插入、修改和删除 &#xff08;一&#xff09;方法一&#xff1a;在SSMS控制台上进行操作 1.向表中添加数据 2.对表中的数据进行修改 3.对表中的数据进行删除 &#xff08;二&#xff09;方法二&#xff1a;使用 SQL 代码进行操作 1.向表中添…

deepSeek-SSE流式推送数据

1、背景 DeepSeek作为当前最火的AI大模型&#xff0c; 使用的时候用户在输入框输入问题&#xff0c;大模型进行思考回答你&#xff0c;然后会有一个逐步显示的过程效果&#xff0c;而不是一次性返回整个答案给前端页面进行展示&#xff0c;为了搞清楚其中的原理&#xff0c;我们…

【北京迅为】iTOP-RK3568开发板OpenHarmony系统南向驱动开发UART接口运作机制

瑞芯微RK3568芯片是一款定位中高端的通用型SOC&#xff0c;采用22nm制程工艺&#xff0c;搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码&#xff0c;支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU&#xff0c;可用于轻量级人工…

C#实现自己的Json解析器(LALR(1)+miniDFA)

C#实现自己的Json解析器(LALR(1)miniDFA) Json是一个用处广泛、文法简单的数据格式。本文介绍如何用bitParser&#xff08;拥有自己的解析器&#xff08;C#实现LALR(1)语法解析器和miniDFA词法分析器的生成器&#xff09;迅速实现一个简单高效的Json解析器。 读者可在&#xf…

机器学习——KNN数据均一化

在KNN&#xff08;K-近邻&#xff09;算法中&#xff0c;数据均一化&#xff08;归一化&#xff09;是预处理的关键步骤&#xff0c;用于消除不同特征量纲差异对距离计算的影响。以下是两种常用的归一化操作及其核心要点&#xff1a; 质押 一 、主要思想 1. 最值归一化&#…

异步编程与流水线架构:从理论到高并发

目录 一、异步编程核心机制解析 1.1 同步与异步的本质区别 1.1.1 控制流模型 1.1.2 资源利用对比 1.2 阻塞与非阻塞的技术实现 1.2.1 阻塞I/O模型 1.2.2 非阻塞I/O模型 1.3 异步编程关键技术 1.3.1 事件循环机制 1.3.2 Future/Promise模式 1.3.3 协程&#xff08;Cor…

哈尔滨工业大学DeepSeek公开课人工智能:大模型原理 技术与应用-从GPT到DeepSeek|附视频下载方法

导 读INTRODUCTION 今天继续哈尔滨工业大学车万翔教授带来了一场主题为“DeepSeek 技术前沿与应用”的报告。 本报告深入探讨了大语言模型在自然语言处理&#xff08;NLP&#xff09;领域的核心地位及其发展历程&#xff0c;从基础概念出发&#xff0c;延伸至语言模型在机器翻…

Excel处理控件Spire.XLS系列教程:C# 在 Excel 中添加或删除单元格边框

单元格边框是指在单元格或单元格区域周围添加的线条。它们可用于不同的目的&#xff0c;如分隔工作表中的部分、吸引读者注意重要的单元格或使工作表看起来更美观。本文将介绍如何使用 Spire.XLS for .NET 在 C# 中添加或删除 Excel 单元格边框。 安装 Spire.XLS for .NET E-…

Web开发-JS应用NodeJS原型链污染文件系统Express模块数据库通讯

知识点&#xff1a; 1、安全开发-NodeJS-开发环境&功能实现 2、安全开发-NodeJS-安全漏洞&案例分析 3、安全开发-NodeJS-特有漏洞 node.js就是专门运行javascript的一个应用程序&#xff0c;区别于以往用浏览器解析原生js代码&#xff0c;node.js本身就可以解析执行js代…

国产达梦(DM)数据库的安装(Linux系统)

目录 一、安装前的准备工作 1.1 导包 1.2 创建用户和组 1.3 修改文件打开最大数 1.4 目录规划 1.5 修改目录权限 二、安装DM8 2.1 挂载镜像 2.2 命令行安装 2.3 配置环境变量 2.4 启动图形化界面 三、配置实例 四、注册服务 五、启动 停止 查看状态 六、数据库客…

git的底层原理

git的底层原理 三段话总结git&#xff0c; 1. 工作原理&#xff1a;git管理是一个DAG有向无环图&#xff0c;HEAD指针指向branch或直接指向commit&#xff0c;branch指向commit&#xff0c;commit指向tree&#xff0c;tree指向别的tree或直接指向blob。 2. git所管理的一个目录…

MATLAB+Arduino利用板上的按键控制板上Led灯

几年不使用&#xff0c;之前的知识都忘掉了。需要逐步捡起来。 1 熟悉按键的使用 2熟悉灯的控制 1 电路 我们将通过 MATLAB 的 Arduino 支持包与 Arduino 板通信&#xff0c;读取按键状态并控制 LED 灯的亮灭。 按键&#xff1a;连接到 Arduino 的数字引脚&#xff08;例如…

Cocos Creator Shader入门实战(五):材质的了解、使用和动态构建

引擎&#xff1a;3.8.5 您好&#xff0c;我是鹤九日&#xff01; 回顾 前面的几篇文章&#xff0c;讲述的主要是Cocos引擎对Shader使用的一些固定规则&#xff0c;这里汇总下&#xff1a; 一、Shader实现基础是OpenGL ES可编程渲染管线&#xff0c;开发者只需关注顶点着色器和…

vue设置自定义logo跟标题

准备 Logo 图片 将自定义的 Logo 图片&#xff08;如 logo.png&#xff09;放置在项目的 public文件夹下。 使用环境变量设置 Logo 和标题&#xff08;可选&#xff09; 创建或修改 .env 文件 在项目根目录下创建或修改 .env 文件&#xff0c;添加以下内容&#xff1a; VITE_A…