49 序列化和反序列化

news2024/11/20 8:44:30

本章重点

理解应用层的作用,初识http协议
理解传输层的作用,深入理解tcp的各项特性和机制
对整个tcp/ip协议有系统的理解
对tcp/ip协议体系下的其他重要协议和技术有一定的了解
学会使用一些网络问题的工具和方法

目录

1.应用层
2.协议概念
3. 网络计算器
4. 序列化和反序列化
5. 协议定制
6. 数据处理
7. 网络函数封装
8. 服务端
9. 客户端
10.结果示例
11. json序列化
12. 添加条件选项
13.再看七层模型

1. 应用层

实际解决问题,满足日常需求的网络程序都在应用层

2. 协议概念

协议是一种“约定”,socket api的接口,在读写数据时,都是按“字符串”的方式来发送接收的,如果我们要传输一些结构化的数据,怎么办?

tcp也称作传输控制协议(什么时候发,发多少,出错了怎么办),传输层是在os内部实现的,是os网络模块部分,将数据交给tcp实际上就是交给os,由于os决定数据的发送,那么收上来的数据就不能完全确定了,有可能是完整的,也有可能是多个报文,或者一部分。所以为了成功的发送和解析报文,应用层就需要协议约定好数据的格式,确定数据的完整性,如果长度不符,就不处理

3. 网络计算器

需要实现一个服务器的计算器,把客户端两个数发过去,然后由服务器计算,最后把结果返回给客户端

约定方案

约定方案一:
客户端发送一个形如“1+1”的字符串
这个字符串有两个操作数,都是整形
两个数字之间会有一个字符是运算符
数字和运算符之间没有空格
。。。
这种情况如果一次性发送了四五组数据,无法区分是一个还是几个报文

约定方案二:
定义结构体表示需要交互的信息
发送数据时将这个结构体按照一个规则转换成字符串,接收到数据的时候再按照相同的规则将把字符串串转换回结构体

// proto.h 定义通信的结构体
typedef struct Request {
 int a;
 int b;
} Request;
typedef struct Response {
 int sum;
} Response;
// client.c 客户端核心代码
Request request; 
Response response; 
 
scanf("%d,%d", &request.a, &request.b);
write(fd, request, sizeof(Request));
read(fd, response, sizeof(Response)); 
// server.c 服务端核心代码
Request request;
read(client_fd, &request, sizeof(request));
Response response;
response.sum = request.a + request.b;
write(client_fd, &response, sizeof(response));

直接发送结构体,结构体两边类型一样,这种方法可以,但是不同的设备结构体的对齐方式可能不一样,导致同一个结构体大小不一样。出问题后非常难调试,都是二进制的。如果一出性发好多个,也不好区分一个个报文,不过os内部是这样实现的,各种情况都考虑了

只要保证一端发送,另一端能够正确解析,这种约定就是应用层协议。为了更稳定,可以定下面的约定

4. 序列化和反序列化

上面的约定方式都有不足之处。最终的约定方式以qq消息举例,需要的结构体包含消息内容,昵称,发送时间,将这三个字符串组合为一个字符串发送给客户,客户收到后又重新转换为结构体,解析为发送时间+昵称+内容的结构,双方的内容结构是相同的。也实现了简单的分层,上面负责结构的规划组成序列,下面负责将序列化的数据完整发送和接收,为了方便解析和发送,还需要规定报文之间的分隔。这样序列化和反序列化方便网络收发

在这里插入图片描述

5. 协议定制

协议分两个类,一个是请求类,一个是响应类,请求方用计算类,生成计算式,两个操作数一个操作符,所以成员变量两个int为操作数,一个char操作符。将数据序列化为“x 操作符 y”的结构发送,对方收到后还要提供反序列化为计算类来计算结果
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

结算结果放到结果类,两个成员变量,一个是int的结果,一个是int的返回码,表明结果可不可信。也要提供序列化和反序列化功能,将结果和返回码改变为“结果 返回码”的字符串格式

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

上面的数据可以用来发送了,但如果客户一次性发了很多个数据,或者一个报文也不满足。如何区分每个报文?所以必须为发出的数据添加报头,报头格式定为“长度\n报文\n”,有添加报头也要解析报头
在这里插入图片描述
在这里插入图片描述

protocol.hpp

#pragma once
#include <string>

//分隔符
const std::string black_sep = " ";
const std::string protocol_sep = "\n";

//解决报文外部格式
 //len\n正文\n
    std::string encode(std::string& message)
    {
        std::string package = std::to_string(message.size());
        package += protocol_sep;
        package += message;
        package += protocol_sep;

        return package;
    }

    //len\na + b\n
    bool decode(std::string& message, std::string* content)
    {
        std::size_t pos = message.find(protocol_sep);
        if (pos == std::string::npos)
        {
            return false;
        }

        std::string len_str = message.substr(0, pos);
        std::size_t len = std::stoi(len_str);
        std::size_t total_len = len_str.size() + len + 2;

        //检查长度
        if (message.size() < total_len)
        {
            return false;
        }

        *content = message.substr(pos + 1, len);
        //earse 移除报文
        message.erase(0, total_len);

        return true;
    }

class Request
{
public:
    Request(){}
    Request(int a, int b, char oper)
    {
        _num1 = a;
        _num2 = b;
        _op = oper;
    }
    //a + b
    bool serialize(std::string* out)
    {
        //构建报文有效载荷
        std::string str;
        str += std::to_string(_num1);
        str += black_sep;
        str += _op;
        str += black_sep;
        str += std::to_string(_num2);

        *out = str;
        return true;
    }

    //a + b
    bool deserialize(std::string& in)
    {
        //a
        std::size_t left = in.find(black_sep);
        if (left == std::string::npos)
        {
            return false;         
        }
        std::string part_a = in.substr(0, left);
        // b
        std::size_t right = in.rfind(black_sep);
        if (right == std::string::npos)
        {
            return false;     
        }
        std::string part_b = in.substr(right + 1);
        //+
        if (left + 2 != right)
        {
            return false;
        }

        _op = in[left+1];
        _num1 = std::stoi(part_a);
        _num2 = std::stoi(part_b);

        return true;
    }

    void debugprint()
    {
        cout << "新请求构建完成:" << _num1 << _op << _num2 << endl;
    }

public:
    int _num1;
    int _num2;
    char _op;
};

class Response
{
public:
    Response(){}
    Response(int res, int cod)
    {
        _result = res;
        _code = cod;
    }

    //1000 0
    bool serialize(std::string* out)
    {
        string str = std::to_string(_result);
        str += black_sep;
        str += std::to_string(_code);
        *out = str;

        return true;
    }

    //1000 0
    bool deserialize(std::string& in)
    {
        std::size_t pos = in.find(black_sep);
        if (pos  == std::string::npos)
        {
            return false;
        }

        std::string left = in.substr(0, pos);
        std::string right = in.substr(pos + 1);

        _result = std::stoi(left);
        _code = std::stoi(right);

        return true;
    }

    void debugprint()
    {
        cout << "结果响应完成,result:" << _result << ",code:" << _code << endl;
    }

public:
    int _result;
    int _code;   //0可信,否则表明对应的错误
};


6. 数据处理

有了协议就可以实现数据处理,计算结果并封装的类

枚举各种计算错误的情况,操作符等其他问题用OTHER
在这里插入图片描述

计算函数传入上面的请求类,返回结果响应类。根据操作符进行不同的运算
在这里插入图片描述
数据处理函数将收到的字符串内容转换为请求类,调用计算函数得到结果,并对结果封包返回字符串用来发送
在这里插入图片描述

servercal.hpp

#pragma once
#include "protocol.hpp"

enum
{
    DIVZERO = 1,
    MODZERO,
    OTHER_OPER
};

class ServerCal
{
public:
    ServerCal()
    {

    }

    Response CalculatorHelp(const Request& req)
    {
        Response res(0, 0);
        switch (req._op)
        {
        case '+':
            res._result = req._num1 + req._num2;
            break;
        case '-':
            res._result = req._num1 - req._num2;
            break;
        case '*':
            res._result = req._num1 * req._num2;
            break;
        case '/':
            if (req._num2 == 0)
            {
                res._code = DIVZERO;
            }
            else
            {
                res._result = req._num1 / req._num2;
            }
            break;
        case '%':
            if (req._num2 == 0)
            {
                res._code = MODZERO;
            }
            else
            {
                res._result = req._num1 % req._num2;
                break;
            }
            
        default:
            res._code = OTHER_OPER;
            break;
        }

        return res;
    }

    std::string Calcluator(std::string& package)
    {
        std::string content;
        bool r = decode(package, &content);
        if (!r)
        {
            return "";
        }
        Request req;
        r = req.deserialize(content);
        if (!r)
        {
            return "";
        }
        req.debugprint();
        content = "";
        Response res = CalculatorHelp(req);
        res.debugprint();
        res.serialize(&content);
        content = encode(content); // len\n正文\n

        return content;
    }

    ~ServerCal()
    {

    }
};


7. 网络函数封装

将服务器的socket常用功能封装为scoke类,成员sockfd为socket函数的返回值,提供返回sockfd的函数

Socket.hpp

#pragma once
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include "log.hpp"

enum
{
    SOCKERR = 1,
    BINDERR,
    LISERR
};

Log lg;
const int backlog = 5;
class Sock
{
public:
    Sock()
    {

    }

    void Socket()
    {
        _sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_sockfd < 0)
        {
            lg.logmessage(fatal, "socket error");
            exit(SOCKERR);
        }
    }

    void Bind(uint16_t port)
    {
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_addr.s_addr = INADDR_ANY;
        local.sin_port = htons(port);

        int bret = bind(_sockfd, (const struct sockaddr*)&local, sizeof(local));
        if (bret < 0)
        {
            lg.logmessage(fatal, "bind error");
            exit(BINDERR);
        }
    }

    void Listen()
    {
        int lret = listen(_sockfd, backlog);
        if (lret < 0)
        {
            lg.logmessage(fatal, "listen error");
            exit(LISERR);
        }
    }
    
    int Accept(string* clientip, uint16_t* clientport)
    {
        sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int newfd = accept(_sockfd, (sockaddr*)&peer, &len);
        if (newfd < 0)
        {
            lg.logmessage(warning, "accept error");
            return -1;
        }

        char ipstr[64];
        inet_ntop(AF_INET, &peer.sin_addr, ipstr, sizeof(ipstr));
        *clientip = ipstr;
        *clientport = ntohs(peer.sin_port);

        return newfd;
    }

    bool Connect(const string ip, const uint16_t port)
    {
        sockaddr_in peer;
        memset(&peer, 0, sizeof(peer));
        peer.sin_family = AF_INET;
        inet_pton(AF_INET, ip.c_str(), &peer.sin_addr);
        peer.sin_port = htons(port);

        int cret = connect(_sockfd, (const struct sockaddr*)&peer, sizeof(peer));
        if (cret == -1)
        {
            lg.logmessage(warning, "connect error");
            return false;
        }

        return true;
    }

    void Close()
    {
        close(_sockfd);
    }

    int Fd()
    {
        return _sockfd;
    }
    ~Sock()
    {

    }
public:
    int _sockfd;
};

8. 服务端

服务端和通用服务器一样,accept收到连接请求后,创建子进程提供服务,因为数据可能不是一次性接收完的,所以recbuff不断加上读到的内容。同时有可能有多个报文,所以收到数据后进入循环处理,将字符串交给函数模板对象,就是上面的数据处理函数。
在这里插入图片描述

数据处理函数的返回值
在这里插入图片描述

因为可能收到的数据不满足一个报文,所以这个函数里多条判断,报文解析不成功都会返回空,调用得到的字符串内容为空时跳出继续读取。解析成功后返回结果将内容发送给客户端
在这里插入图片描述
解析报文时有多条判断,先找\n,找到说明有数据的长度,然后根据长度获取内容,检查数据长度和计算的长度符不符合。不满足上面条件的不予处理,否则说明数据长度符合,移除解析了的内容

server.hpp

#pragma once
#include <string>
#include <signal.h>
#include <functional>
#include "log.hpp"
#include "Socket.hpp"

using namespace std;
using func_t = std::function<std::string(std::string &package)>;
class server
{
public:
    server(uint16_t port, func_t fun)
    :_port(port), _fun(fun)
    {
    }

    void init()
    {
        //创建套接字
        _listensocket.Socket();
        _listensocket.Bind(_port);
        _listensocket.Listen();

        lg.logmessage(info, "init server done");
    }

    void start()
    {
        signal(SIGCHLD, SIG_IGN);
        signal(SIGPIPE, SIG_IGN);
        int cnt = 1;
        while (true)
        {
            string ip;
            uint16_t port;

            int sockfd = _listensocket.Accept(&ip, &port);
            if (sockfd < 0)
            {
                continue;
            }

            lg.logmessage(info, "get a new link %d", sockfd);

            if (fork() == 0)
            {
                _listensocket.Close();
                string inbuff_stream;
                // 提供服务
                while (true)
                {
                    char buff[1280];
                    ssize_t n = read(sockfd, buff, sizeof(buff));
                    if (n > 0)
                    {
                        buff[n] = 0;
                        lg.logmessage(debug, "\n%s", buff);
                        inbuff_stream += buff;
                        while (true)
                        {
                            string echo = _fun(inbuff_stream);
                            if (echo.empty())
                            {
                                break;
                            }

                            lg.logmessage(debug, "缓冲区\n%s", inbuff_stream.c_str());
                            lg.logmessage(debug, "结果\n%s", echo.c_str());
                            cout << "次数:" << cnt++ << endl;
                            write(sockfd, echo.c_str(), echo.size());
                        }
                       
                    }
                    else if (n == 0)
                    {
                        break;
                    }
                    else
                    {
                        break;
                    }
                }
                exit(0);
            }

            close(sockfd);
        }
    }

    ~server()
    {

    }
private:
    Sock _listensocket;
    uint16_t _port;
    func_t _fun;
};

server.cc

#include <unistd.h>
#include "server.hpp"
#include "servercal.hpp"
//#include "protocol.hpp"

int main()
{
    ServerCal cal;
    uint16_t port = 8000;
    server *tsvp = new server(port, std::bind(&ServerCal::Calcluator, &cal, std::placeholders::_1));
    tsvp->init();
    daemon(0, 0);
    tsvp->start();
    return 0;
}

std::bind绑定函数和第一个参数

9. 客户端

客户端创建连接,成功生成5次随机的数字和运算符,赋值给请求类,封装后发送,收到内容后用结果类解析
在这里插入图片描述
client.cc

#include <time.h>
#include <unistd.h>
#include <assert.h>
#include "Socket.hpp"
#include "protocol.hpp"

int main()
{
    srand(time(NULL));

    uint16_t port = 8000;
    string ip = "106.54.46.147";
    struct sockaddr_in server;
    bzero(&server, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(ip.c_str());
    server.sin_port = htons(port);

    const string opers = "+-*/%";

    Sock socket;
    socket.Socket();
    bool r = socket.Connect(ip, port);
    if (!r)
        return 1;

    int cnt = 1;
    while (cnt <= 5)
    {
        cout << "=============第" << cnt << "次测试...." << "============" << endl;
        string package;
        int x = rand() % 100;
        int y = rand() % 100 + 1;
        char op = opers[rand() % opers.size()];

        Request req(x, y, op);
        req.debugprint();

        req.serialize(&package);
        package = encode(package);
        write(socket._sockfd, package.c_str(), package.size());

        char buff[1024];
        int n = read(socket._sockfd, buff, sizeof(buff));

        string inbuff_stream;
        if (n > 0)
        {
            buff[n] = 0;
            inbuff_stream += buff;

            string content;
            bool r = decode(inbuff_stream, &content);
            assert(r);

            Response resp;
            r = resp.deserialize(content);
            assert(r);

            resp.debugprint();
        }

        cout << "=======================================" << endl;
        sleep(1);
        cnt++;
    }
}

日志

#pragma once
#include <stdarg.h>
#include <iostream>
#include <stdio.h>
#include <cstring>
#include <time.h>
#include <cerrno>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

using namespace std;

#define info 0
#define debug 1
#define warning 2
#define ERROR 3
#define fatal 4

#define screen 1
#define onefile 2
#define classfile 3

#define path "log.txt"

class Log
{
public:

    Log(int style = screen)
    {
        printstyle = style;
        dir = "log/";
    }

    void enable(int method)
    {
        printstyle = method;
    }

    const char *leveltostring(int level)
    {
        switch (level)
        {
        case 0:
            return "info";
            break;
        case 1:
            return "debug";
            break;
        case 2:
            return "warning";
            break;
        case 3:
            return "error";
            break;
        case 4:
            return "fatal";
            break;
        default:
            return "none";
            break;
        }
    }

    void printlog(int level, const string &logtxt)
    {
        switch (printstyle)
        {
        case screen:
            cout << logtxt;
            break;
        case onefile:
            printonefile(path, logtxt);
            break;
        case classfile:
            printclassfile(level, logtxt);
            break;
        }
    }

    void logmessage(int level, const char *format, ...)
    {
        time_t t = time(0);
        tm *ctime = localtime(&t);
        char leftbuff[1024];
        sprintf(leftbuff, "[%s]%d-%d-%d %d:%d:%d:", leveltostring(level), ctime->tm_year + 1900,
                ctime->tm_mon + 1, ctime->tm_mday, ctime->tm_hour, ctime->tm_min, ctime->tm_sec);

        char rightbuff[1024];
        va_list s;
        va_start(s, format);
        vsprintf(rightbuff, format, s);
        va_end(s);
        char logtext[2048];
        sprintf(logtext, "%s %s\n", leftbuff, rightbuff);
        //printf(logtext);
        printlog(level, logtext);
    }
 

    void printonefile(const string& logname, const string& logtxt)
    {
        int fd = open(logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666);  
        if (fd < 0)
        {
            return;
        }

        write(fd, logtxt.c_str(), logtxt.size());
        close(fd);
    }

    void printclassfile(int level, const string &logtxt)
    {
        //log.txt.info
        string filename = dir + path;
        filename += ".";
        filename += leveltostring(level);
        printonefile(filename, logtxt);
    }

    ~Log(){};

private:
    int printstyle;
    string dir; //分类日志,放入目录中
};

// int sum(int n, ...)
// {
//     int sum = 0;
//     va_list s;
//     va_start(s, n);

//     while (n)
//     {
//         sum = sum + va_arg(s, int);
//         n--;
//     }

//     return sum;
// }

10. 结果示例

在这里插入图片描述
可以加入守护进程,在初始化服务器后开启守护
在这里插入图片描述

11. json序列化

上面的序列化和反序列化功能都是字符串处理,比较麻烦。有一种数据交换格式json,有序列化和反序列化的功能,可以用json代替。用条件编译试试json发送数据

如果没有先安装

sudo yum install -y jsoncpp-devel

序列化
先创建json里的通用变量,Json::Value,以键值对的方式赋值,key和value
在这里插入图片描述
再用一个通用变量嵌套一个value类
在这里插入图片描述
调用序列化功能生成字符串打印,有两种风格,style内容换行易读在这里插入图片描述
反序列化
创建value变量,创建read对象,调用parse功能解析内容
在这里插入图片描述
利用key-value格式提取内容,嵌套类型定义value变量获取,再提取一次
在这里插入图片描述
输出结果
在这里插入图片描述

修改protocol文件
用#ifdef #else #endif的格式条件编译,json方式写在else的情况里

Request

		Json::Value root;
        root["x"] = _num1;
        root["y"] = _num2;
        root["op"] = _op;
        Json::FastWriter w;
        *out = w.write(root);

        return true;
		Json::Value root;
        Json::Reader r;
        r.parse(in, root);
        _num1 = root["x"].asInt();
        _num2 = root["y"].asInt();
        _op = root["op"].asInt();

        return true;

Response

		Json::Value root;
        root["res"] = _result;
        root["code"] = _code;
        Json::FastWriter w;
        *out = w.write(root);

        return true;
        Json::Value root;
        Json::Reader r;
        r.parse(in, root);
        _result = root["res"].asInt();
        _code = root["code"].asInt();

        return true;

示例
在这里插入图片描述
序列化的工具还有protobuf,二进制,更注重效率,可读性不如json,适用于内部使用

12. 添加条件选项

在这里插入图片描述
$号可以引入定义的常量,#会注释后面的内容

13. 再看七层模型

在这里插入图片描述
会话层的维护是通过server创建子进程提供服务的,接到链接就创建一个会话
表示层就是上面定义的协议,就是结构体字符串等固有的数据格式,网络标准格式就是序列化添加报头这些动作后的数据
应用层就是处理数据的计算器功能
所以表示层和会话层应用层很难在os实现,根据不同的场景有不同的格式和功能,内容也会有文字声音图像等都有可能,无法在os全部实现

如果客户端连上一直不发数据,会占用资源,可以对时间进行判断,超时直接挂掉

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

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

相关文章

awesome-ai4s 现已开源!超全 AI for Science 学术论文与数据资源汇总,持续更新ing

2018 年中国科学院院士鄂维南提出「AI for Science」概念&#xff0c;强调利用 AI 学习科学原理、创造科学模型来解决实际问题。同年&#xff0c;AlphaFold 崭露头角&#xff0c;从 43 种蛋白质中准确预测出了 25 种蛋白质结构。2021 年&#xff0c;AlphaFold 2 开源并预测了 9…

缓存降级

当Redis缓存出现问题或者无法正常工作时,需要有一种应对措施,避免直接访问数据库而导致整个系统瘫痪。缓存降级就是这样一种机制。 主要的缓存降级策略包括: 本地缓存降级 当Redis缓存不可用时,可以先尝试使用本地进程内缓存,如Guava Cache或Caffeine等。这样可以减少对Redis…

OpenLayers中实现对ImageStatic图层的扩展以支持平铺WrapX功能

地图平铺技术概述 地图平铺&#xff08;Tiling&#xff09;是一种将大尺寸地图数据分割成小块&#xff08;瓦片&#xff09;的技术&#xff0c;这在地图服务中非常常见。它使得地图数据能高效加载和展示&#xff0c;尤其适合网络环境。通过仅加载当前视图窗口所需的地图瓦片&a…

Qt官方示例---opengl

文件相对路径&#xff1a;Examples\Qt-5.9.1\opengl 2dpainting cube computegles31 contextinfo hellogl2 hellowindow paintedwindow qopenglwidget qopenglwindow textures threadedqopenglwidget

Rabbitmq 搭建使用案例 [附源码]

Rabbitmq 搭建使用案例 文章目录 RabbitMQ搭建docker 代码golang生产者消费者 可视化消费进度 RabbitMQ搭建 docker docker run -d --hostname rabbitmq --name rabbitmq -e RABBITMQ_DEFAULT_USERadmin -e RABBITMQ_DEFAULT_PASSadmin -e RABBITMQ_DEFAULT_VHOSTmy_vhost -e…

重组蛋白表达系统优缺点对比|卡梅德生物

重组蛋白是现代生物技术中不可或缺的一部分&#xff0c;它们广泛应用于药物开发、研究工具和工业酶的生产。根据目标蛋白的特性和所需的修饰&#xff0c;可以选择不同的表达系统。下文罗列一下四个主要蛋白表达系统的优缺点&#xff1a; 1. 原核表达系统&#xff08;如大肠杆菌…

【QT实战】汇总导航

✨Welcome 大家好&#xff0c;欢迎来到瑾芳玉洁的博客&#xff01; &#x1f611;励志开源分享诗和代码&#xff0c;三餐却无汤&#xff0c;顿顿都被噎。 &#x1f62d;有幸结识那个值得被认真、被珍惜、被捧在手掌心的女孩&#xff0c;不出意外被敷衍、被唾弃、被埋在了垃圾堆…

深度学习之基于Tensorflow卷积神经网络(CNN)实现猫狗识别

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 一、项目背景与意义 在人工智能和深度学习的热潮中&#xff0c;图像识别是一个备受关注的领域。猫狗识别作为图像识…

Milvus的内存索引

简介&#xff1a; 这篇文章主要介绍milvus支持的各种内存索引&#xff0c;以及它们最适用的场景&#xff0c;还有用户为了获得更好的搜索性能可以配置的参数。 索引是有效组织数据的过程&#xff0c;它的主要角色是在大的数据集中显著的加速耗时的查询从而有效的进行相似搜索…

【制作100个unity游戏之28】花半天时间用unity复刻童年4399经典小游戏《黄金矿工》(附带项目源码)

最终效果 文章目录 最终效果前言素材模拟绳子钩子来回摆动发射回收钩子方法发射钩子回收钩子勾取物品随机生成物品其他源码完结 前言 在游戏发展史上&#xff0c;有些游戏以其简单而耐玩的特性&#xff0c;深深地烙印在了玩家的记忆中。《黄金矿工》就是其中之一&#xff0c;它…

SpringBootWeb 篇-深入了解 Mybatis 删除、新增、更新、查询的基础操作与 SQL 预编译解决 SQL 注入问题

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 Mybatis 的基础操作 2.0 基础操作 - 环境准备 3.0 基础操作 - 删除操作 3.1 SQL 预编译 3.2 SQL 预编译的优势 3.3 参数占位符 4.0 基础操作 - 新增 4.1 主键返回…

每周节省7800万工时!ChatGPT等成美国降本增效利器

5月23日&#xff0c;全球最大教育、商业出版社之一的Pearson plc在官网发布了&#xff0c;ChatGPT等生成式AI如何帮助人们提升工作效率节省时间的深度研究报告。 该报告一共分析了美国、英国、澳大利亚、巴西和印度5个国家。到2026年&#xff0c;美国节省的时间最多&#xff0…

面试准备-项目【面试准备】

面试准备-项目【面试准备】 面试准备自我介绍&#xff1a;项目介绍&#xff1a; 论坛项目功能总结简介数据库表设计注册功能登录功能显示登录信息功能发布帖子评论私信点赞功能关注功能通知搜索网站数据统计热帖排行缓存 论坛项目技术总结Http的无状态cookie和session的区别为什…

GIS竞赛指南

全国大学生GIS应用技能大赛 全国大学生GIS应用技能大赛 主办单位:中国地理信息产业协会、中国地理学会 协办单位&#xff1a;广州大学(2023年) 参赛要求&#xff1a;每个学校一支队伍&#xff0c;限在读本科生组队不超过4人&#xff0c;指导老师不超过2人&#xff0c;一般学…

三磷酸肌醇(IP3)为细胞内信号转导分子 在医药、科研领域应用前景广阔

三磷酸肌醇&#xff08;IP3&#xff09;为细胞内信号转导分子 在医药、科研领域应用前景广阔 三磷酸肌醇又称为肌醇三磷酸&#xff0c;简称InsP3或IP3&#xff0c;是一种小信号分子&#xff0c;由磷脂酶C催化磷脂酰肌醇-4,5-二磷酸水解产生的&#xff0c;水解后剩下的甘油二酯停…

异众比率(variation ratio)

异众比率&#xff08;variation ratio&#xff09;是指非众数组的频数占总频数的比率&#xff0c;其计算公式为: 其中&#xff0c;为众数组的频数。 异众比率主要用于衡量众数对一组数据的代表程度。异众比率越大&#xff0c;说明非众数组的频数占总频数的比重越大&#xff0…

甭管几岁退休,都要做纵横驰骋的疯狂老太太

点击文末“阅读原文”即可参与节目互动 剪辑、音频 / 卷圈 运营 / SandLiu 卷圈 监制 / 姝琦 封面 / 姝琦Midjourney 产品统筹 / bobo 场地支持 / &#xff08;新&#xff09;声湃轩北京录音间 对许多年轻人来说&#xff0c;退休是此生最大的“延迟满足”。 手握不用干活…

高通 Android 12/13冻结屏幕

冻结屏幕很多第一次听到以为是Android一种异常现象&#xff0c;实则不然&#xff0c;就是防止用户在做一些非法操作导致问题防止安全漏洞问题。 1、主要通过用户行为比如禁止下拉状态栏和按键以及onTouch事件拦截等&#xff0c;不知道请看这篇文章&#xff08;Touch事件传递流…

html+css绘制自定义样式输入框

效果&#xff1a; 代码&#xff1a; html部分&#xff1a; <div class"box"> <div class"newbox"><input type"text" required><div class"name">Username</div></div> </div>css部分 …