序列化/反序列化与TCP通信协议

news2025/2/12 4:21:39

深入理解序列化/反序列化与TCP通信协议

一、序列化与反序列化

1.1 基本概念

  • 序列化(Serialization): 将数据结构或对象状态转换为可存储/传输格式的过程
  • 反序列化(Deserialization): 将序列化后的数据恢复为原始数据结构的过程

示例:

// 原始数据结构
struct Person {
    string name;
    int age;
    double salary;
};

// 序列化 -> {"name":"John","age":30,"salary":5000.0}
// 反序列化 -> 重建Person对象

1.2 存在意义

  1. 数据持久化:将内存对象保存到文件/数据库
  2. 网络传输:跨进程/网络传输结构化数据
  3. 跨语言交互:通过标准格式实现不同语言系统的数据交换
  4. 数据校验:通过反序列化验证数据结构完整性

1.3 实现方式对比

格式可读性体积解析速度典型应用场景
JSON中等较快Web API、配置文件
XML企业级系统交互
Protobuf极快高性能RPC通信
MessagePack移动端数据传输

二、TCP通信关键特性

2.1 全双工通信

实现原理

// 内核数据结构示意
struct sock {
    struct sk_buff_head receive_queue;  // 接收缓冲区
    struct sk_buff_head write_queue;    // 发送缓冲区
    // ...
};
  • 每个socket维护两个独立缓冲区
  • 发送/接收操作互不阻塞
    tcp全双工实现原理

2.2 面向字节流的特点

关键问题

客户端发送:"HelloWorld"
服务端可能分两次接收:"Hello" 和 "World"

解决方案

自定义协议格式:
[4字节长度头][有效载荷]
示例:
0x0000000A{"name":"John"}

2.3 应用层协议设计

推荐格式:

#pragma pack(push, 1)
struct PacketHeader {
    uint32_t length;    // 有效载荷长度
    uint16_t version;   // 协议版本
    uint32_t checksum;  // 数据校验码
};
#pragma pack(pop)

设计要点:

  1. 固定长度报文头
  2. 包含版本控制字段
  3. 添加数据校验机制
  4. 使用网络字节序(大端)

三、JSON序列化实战(jsoncpp)

3.1 基础示例

#include <json/json.h>

// 序列化
Json::Value root;
root["name"] = "John";
root["age"] = 30;
root["salary"] = 5000.0;

Json::StreamWriterBuilder builder;
string jsonStr = Json::writeString(builder, root);

// 反序列化
Json::CharReaderBuilder readerBuilder;
Json::Value parsedRoot;
string errs;
istringstream iss(jsonStr);
Json::parseFromStream(readerBuilder, iss, &parsedRoot, &errs);

3.2 调试技巧

启用格式化输出:

builder["indentation"] = "\t";  // 设置缩进
cout << Json::writeString(builder, root);

输出结果:

{
    "name": "John",
    "age": 30,
    "salary": 5000.0
}

四、关键注意事项

  1. 字节序问题

    • 网络传输应统一使用大端字节序
    • 使用htonl()/ntohl()进行转换
  2. 版本兼容

    • 协议字段需要向后兼容
    • 建议添加版本号字段
  3. 安全考虑

    • 限制最大报文长度
    • 校验数据合法性
    • 防止缓冲区溢出攻击
  4. 性能优化

    // 预分配缓冲区
    jsonStr.reserve(1024); 
    // 复用解析器实例
    static thread_local Json::CharReaderBuilder readerBuilder;
    
  5. 错误处理

    if (!Json::parseFromStream(readerBuilder, iss, &parsedRoot, &errs)) {
        cerr << "JSON解析失败: " << errs << endl;
        // 实现重试或降级逻辑
    }
    

五、扩展知识

5.1 二进制协议优化

对于高频通信场景,可考虑:

// 使用内存对齐结构
#pragma pack(push, 1)
struct BinaryProtocol {
    uint32_t magic;     // 魔数标识 0x5A5AA5A5
    uint16_t cmdType;   // 命令类型
    uint32_t bodyLen;   // 数据体长度
    byte checksum;      // 校验和
    // 变长数据体...
};
#pragma pack(pop)

5.2 现代序列化方案

  • FlatBuffers:零拷贝反序列化
  • Cap’n Proto:直接内存映射
  • Avro:Schema动态验证

通过合理选择序列化方案和设计通信协议,可以构建出高效可靠的分布式系统。理解底层原理有助于在性能与开发效率之间做出最佳权衡。

板书

在这里插入图片描述

NetCal

TcpServer.cc

#include "TcpServer.hpp"
// #include "CommandExec.hpp"
#include <functional>
#include <memory>
#include "Protocol.hpp"
#include "Calculator.hpp"
#include "Daemon.hpp"


// using task_t = function<std::string (std::string)>;
using cal_fun = std::function<Response(const Request &req)>;
//  package不一定有完成报文, 
//      if 不完整-》继续读
//      else 完整-》提取 ——》 反序列化-》构建Request对象-》调用计算模块
// using namespace 

class Parse
{
public:
    Parse(cal_fun c):_cal(c)
    {}
    std::string Entry(std::string &package)
    {
        // 判断报文完整性
        std::string message;
        std::string respstr;
        while(Decode(package, &message))
        {
            LOG(LogLevel::DEBUG)<<"Content:\n"<<message;
            if(message.empty()) break;
             // 2. 反序列化, message是一个曾经被序列化的request
            Request req;
            if (!req.Deserialize(message))
                break;

            std::cout << "#############" << std::endl;
            req.Print();
            std::cout << "#############" << std::endl;

            // 3. 计算
            Response resp = _cal(req);

            // 4. 序列化
            std::string res;
            resp.Serialize(res);
            LOG(LogLevel::DEBUG) << "序列化: \n" << res;



            // 5. 添加长度报头字段!
            Encode(res);

            LOG(LogLevel::DEBUG) << "Encode: \n" << res;

            // 6. 拼接应答
            respstr += res;

        }
        LOG(LogLevel::DEBUG) << "respstr: \n" << respstr;
        return respstr;
    }

private:
    cal_fun _cal;
};

int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        std::cout<<"Usage: ./server port"<<std::endl;
        Die(USAGE_ERR);
    }
    uint16_t port = std::stoi(argv[1]);
    ENABLE_CONSOLE_LOG();
    // Command cmd;

    // task_t task = [&cmd](std::string cmdstr){
    //     return cmd.Execute(cmdstr);
    // };
    // Daemon(false, false);
    // 计算模块
    Calculator mycal;
    // 解析对象
    // printf("服务器启动\n");
    Parse myparse([&mycal](const Request &req){
        return mycal.Execute(req);
    });
    // 通信模块
    // 只负责IO

    std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>([&myparse](std::string &package){
        return myparse.Entry(package);
    }, port);
    tsvr->InitServer();
    tsvr->Start();
    return 0;
}

TcpServer.hpp

#pragma once

#include <iostream>
#include <cstring>
#include <string>
#include <cerrno>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include <functional>

#include "Log.hpp"
#include "Common.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"

#define BACKLOG 8

// using namespace LogModule;
using namespace ThreadPoolModule;
static const uint16_t gport = 8080;
using handler_t = std::function<std::string(std::string&)>;

class TcpServer
{
    using task_t = std::function<void()>;
    struct ThreadData
    {
        int sockfd;
        TcpServer *self;
    };
public:
    TcpServer(handler_t handler, int port = gport):
    _handler(handler), 
    _port(port),
    _isrunning(false)
    {

    }

    void InitServer()
    {
        // 先监听
        _listensockfd = ::socket(AF_INET, SOCK_STREAM, 0);
        if(_listensockfd < 0)
        {
            LOG(LogLevel::FATAL)<<"socket error";
            Die(SOCKET_ERR);
        }
        LOG(LogLevel::INFO)<<"socket create success, _listensocked is "<<_listensockfd;
        // 后bind
        struct sockaddr_in local;// 网络通信用sockaddr_in, 本地用sockaddr_un
        memset(&local, 0, sizeof local);
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY;
        
        int n = ::bind(_listensockfd, CONV(&local), sizeof(local));
        if(n < 0)
        {
            LOG(LogLevel::FATAL)<<"bind error";
            Die(BIND_ERR);
        }
        LOG(LogLevel::INFO) << "bind success, sockfd is: "<<_listensockfd;
        // 设置为监听状态
        n = listen(_listensockfd, BACKLOG);
        if(n < 0)
        {
            LOG(LogLevel::FATAL)<<"listen error";
            Die(LISTEN_ERR);
        }
        LOG(LogLevel::INFO)<<"listen success, socked is:"<<_listensockfd;
        // 此处可使用::signal(SIGCHLD, SIG_IGN)来将父子进程解绑
    }

    void HandlerRequest(int sockfd)
    {
        LOG(LogLevel::INFO)<<"HandlerRequest, sockfd is:"<<sockfd;
        char inbuffer[4096];
        while(true)
        {
            ssize_t n = recv(sockfd, inbuffer, sizeof inbuffer, 0);
            inbuffer[n] = 0;
            LOG(LogLevel::INFO)<<"server recived:"<<inbuffer;
            if(n > 0)
            {
                inbuffer[n] = 0;
                std::string str(inbuffer);
                std::string cmd_result = _handler(str);// 回调
                ::send(sockfd, cmd_result.c_str(), cmd_result.size(), 0);
                LOG(LogLevel::INFO)<<"server sent:"<<cmd_result;
            }
            else if(n == 0)
            {
                LOG(LogLevel::INFO)<<"client quit"<<sockfd;
                break;
            }
            else
            {
                break;
            }
        }
        ::close(sockfd);// 防止fd泄露
    }

    static void *ThreadEntry(void *args)// 设为静态函数就不用传递this
    {// 当使用多线程(不是封装好的线程池), pthread_thread_create的函数只能接收一个参数
        pthread_detach(pthread_self());
        ThreadData *data = (ThreadData *)args;
        data->self->HandlerRequest(data->sockfd);
        return nullptr;
    }

    void Start()
    {
        _isrunning = true;
        while(_isrunning)
        {
            struct sockaddr_in peer;
            socklen_t peerlen = sizeof(peer);
            LOG(LogLevel::DEBUG)<<"accepting...";
            int sockfd = ::accept(_listensockfd, CONV(&peer), &peerlen);
            if(sockfd < 0)
            {
                LOG(LogLevel::WARNING)<<"accept error:"<<strerror(errno);
                continue;
            }
            // 连接成功
            LOG(LogLevel::INFO)<<"accept success, sockfd is:"<<sockfd;
            InetAddr addr(peer);
            LOG(LogLevel::INFO)<<"client info:"<<addr.Addr();
            // 使用线程池实现
            ThreadPool<task_t>::getInstance()->Equeue([this, sockfd](){
                this->HandlerRequest(sockfd);
            });
        }   
    }

    ~TcpServer()
    {

    }


private:
    int _listensockfd;// 监听socket
    uint16_t _port;
    bool _isrunning;

    // 处理上层任务入口
    handler_t _handler;
};


TcpClient.cc

#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>

#include "Protocol.hpp" // 形成约定

// ./client_tcp server_ip server_port
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        std::cout << "Usage:./client_tcp server_ip server_port" << std::endl;
        return 1;
    }
    std::string server_ip = argv[1]; // "192.168.1.1"
    int server_port = std::stoi(argv[2]);
    int sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        std::cout << "create socket failed" << std::endl;
        return 2;
    }

    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(server_port);
    server_addr.sin_addr.s_addr = inet_addr(server_ip.c_str());

    // client 不需要显示的进行bind, tcp是面向连接的, connect 底层会自动进行bind
    int n = ::connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
    if (n < 0)
    {
        std::cout << "connect failed" << std::endl;
        return 3;
    }
    // echo client
    std::string message;
    while (true)
    {
        int x, y;
        char oper;
        std::cout << "input x: ";
        std::cin >> x;
        std::cout << "input y: ";
        std::cin >> y;
        std::cout << "input oper: ";
        std::cin >> oper;

        Request req(x, y, oper);

        // 1. 序列化
        req.Serialize(message);

        // 2. Encode
        Encode(message);

        // 3. 发送
        n = ::send(sockfd, message.c_str(), message.size(), 0);
        if (n > 0)
        {
            char inbuffer[1024];
            // 4. 获得应答
            int m = ::recv(sockfd, inbuffer, sizeof(inbuffer), 0);
            if (m > 0)
            {
                inbuffer[m] = 0;
                std::string package = inbuffer;//TODO
                std::string content;
                // 4. 读到应答完整--暂定, decode
                Decode(package, &content);

                // 5. 反序列化
                Response resp;
                resp.Deserialize(content);

                // 6. 得到结构化数据
                std::cout << resp.Result() << "[" << resp.Code() << "]" << std::endl;
            }
            else
                break;
        }
        else
            break;
    }

    ::close(sockfd);
    return 0;
}

Calculator.hpp

#pragma once
#include <iostream>
#include "Protocol.hpp"

class Calculator
{
public:
    Calculator()
    {
    }
    Response Execute(const Request &req)
    {
        // 我们拿到的都是结构化的数据,拿到的不就是类对象吗!!!
        Response resp;
        switch (req.Oper())
        {
        case '+':
            resp.SetResult(req.X() + req.Y());
            break;
        case '-':
            resp.SetResult(req.X() - req.Y());
            break;
        case '*':
            resp.SetResult(req.X() * req.Y());
            break;
        case '/':
        {
            if (req.Y() == 0)
            {
                resp.SetCode(1); // 1 就是除0
            }
            else
            {
                resp.SetResult(req.X() / req.Y());
            }
        }
        break;
        case '%':
        {
            if (req.Y() == 0)
            {
                resp.SetCode(2); // 2 就是mod 0
            }
            else
            {
                resp.SetResult(req.X() % req.Y());
            }
        }
        break;
        default:
            resp.SetCode(3); // 3 用户发来的计算类型,无法识别
            break;
        }
        return resp;
    }
    ~Calculator()
    {
    }
};

Daemon.hpp

#pragma once

#include <iostream>
#include <cstdlib>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

#define ROOT "/"
#define devnull "/dev/null"

void Daemon(bool ischdir, bool isclose)
{
   // 1. 守护进程一般要屏蔽到特定的异常信号
   signal(SIGCHLD, SIG_IGN);
   signal(SIGPIPE, SIG_IGN);

   // 2. 成为非组长
   if (fork() > 0)
       exit(0);

   // 3. 建立新会话
   setsid();

   // 4. 每一个进程都有自己的CWD,是否将当前进程的CWD更改成为 / 根目录
   if (ischdir)
       chdir(ROOT);

   // 5. 已经变成守护进程啦,不需要和用户的输入输出,错误进行关联了
   if (isclose)
   {
       ::close(0);
       ::close(1);
       ::close(2);
   }
   else
   {
       int fd = ::open(devnull, O_WRONLY);
       if (fd > 0)
       {
           // 各种重定向
           dup2(fd, 0);
           dup2(fd, 1);
           dup2(fd, 2);
           close(fd);
       }
   }
}

Protocol.hpp

#pragma once

#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>

const std::string Sep = "\n\r";

bool Encode(std::string &message)
{
    if(message.size() == 0) return false;
    std::string package = std::to_string(message.size()) + Sep + message + Sep;
    message = package;
    return true;
}

bool Decode(std::string &package, std::string* content)
{
    auto pos = package.find(Sep);
    if(pos == std::string::npos) return false;
    std::string content_length_str = package.substr(0, pos);
    int content_length = std::stoi(content_length_str);
    int full_length = content_length_str.size() + content_length + 2 * Sep.size();
    if(package.size() < full_length) return false;
    *content = package.substr(pos + Sep.size(), content_length);
    package.erase(0, full_length);
    return true;
}

class Request
{
public:
    Request(int x = 0, int y = 0, char oper = '+'):_x(x), _y(y), _oper(oper)
    {

    }
    bool Serialize(std::string &out_string)
    {
        Json::Value root;
        root["x"] = _x;
        root["y"] = _y;
        root["oper"] = _oper;
        Json::StreamWriterBuilder wb;
        std::unique_ptr<Json::StreamWriter> w(wb.newStreamWriter());
        std::stringstream ss;
        w->write(root, &ss);
        out_string = ss.str();
        return true;
    }

    bool Deserialize(std::string &in_string)
    {
        Json::Value root;
        Json::Reader reader;
        bool parsingSuccessful = reader.parse(in_string, root);
        if(!parsingSuccessful)
        {
            std::cout<<"Failed to parse JSON: "<< reader.getFormattedErrorMessages()<<std::endl;
            return false;
        }
        _x = root["x"].asInt();
        _y = root["y"].asInt();
        _oper = root["oper"].asInt();// char 也是 int

        return true;
    }

    void Print()
    {
        std::cout << _x << std::endl;
        std::cout << _oper << std::endl;
        std::cout << _y << std::endl;
    }
    
    int X() const { return _x; }
    int Y() const { return _y; }
    char Oper() const { return _oper; }
private:
    int _x;
    int _y;
    char _oper;
};


class Response
{
public:
    Response():_result(0), _code(0)
    {

    }

    Response(int result, int code):_result(result), _code(code)
    {

    }

    bool Serialize(std::string &out_string)
    {
        Json::Value root;
        root["result"] = _result;
        root["code"] = _code;
        Json::StreamWriterBuilder wb;
        std::unique_ptr<Json::StreamWriter> w(wb.newStreamWriter());
        std::stringstream ss;
        w->write(root, &ss);
        out_string = ss.str();
        return true;
    }

    bool Deserialize(std::string &in_string)
    {
        Json::Value root;
        Json::Reader reader;
        bool parsingSuccessful = reader.parse(in_string, root);
        if(!parsingSuccessful)
        {
            std::cout<<"Failed to parse JSON: "<<reader.getFormattedErrorMessages()<<std::endl;
            return false;
        }
        _result = root["result"].asInt();
        _code = root["code"].asInt();

        return true;
    }
    int Result() const { return _result; }
    int Code() const { return _code; }
    void SetResult(int res) { _result = res;}
    void SetCode(int c) {_code = c;}    
private:
    int _result;
    int _code;
};

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

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

相关文章

Ollama 本地部署 体验 deepseek

下载安装ollama,选择模型 进行部署 # 管理员命令行 执行 ollama run deepseek-r1:70b浏览器访问http://ip:11434/ 返回 Ollama is runninghttp://ip:11434/v1/models 返回当前部署的模型数据 下载安装CherryStudio&#xff0c;本地对话UI 客户端 在设置中 修改API地址&#x…

Linux探秘坊-------4.进度条小程序

1.缓冲区 #include <stdio.h> int main() {printf("hello bite!");sleep(2);return 0; }执行此代码后&#xff0c;会 先停顿两秒&#xff0c;再打印出hello bite&#xff0c;但是明明打印在sleep前面&#xff0c;为什么会后打印呢&#xff1f; 因为&#xff…

postgreSQL16.6源码安装

1.获取源码 从PostgreSQL: File Browser获取tar.bz2或者tar.gz源码 2.解压 tar xf postgresql-version.tar.bz2 roothwz-VMware-Virtual-Platform:/usr/local# tar xf postgresql-16.6.tar.bz2 roothwz-VMware-Virtual-Platform:/usr/local# ll 总计 24324 drwxr-xr-x 12 ro…

树莓派上 基于Opencv 实现人脸检测与人脸识别

一&#xff0c;需求 基于树莓派4b&#xff0c;usb1080p摄像头&#xff0c;实现人脸检测与人脸识别。尝试了海陵科的模组和百度的sdk。海陵科的模组无法录入人脸&#xff0c;浪费了100多块钱。百度的sdk 在树莓派上也无法录入人脸&#xff0c;官方解决不了。最后只能用opencv自…

mac下dify+deepseek部署,实现私人知识库

目前deepseek 十分火爆&#xff0c;本地部署实现私有知识库&#xff0c;帮助自己日常工作&#xff0c;上一篇使用工具cherry studio可以做到私人知识库。今天学习了一下&#xff0c;使用Dify链接deepseek&#xff0c;实现私人知识库&#xff0c;也非常不错&#xff0c;这里分享…

CSS 实现下拉菜单效果实例解析

1. 引言 在 Web 开发过程中&#xff0c;下拉菜单是一种常见且十分实用的交互组件。很多前端教程都提供过简单的下拉菜单示例&#xff0c;本文将以一个简洁的实例为出发点&#xff0c;从 HTML 结构、CSS 样式以及整体交互逻辑三个层面进行详细解析&#xff0c;帮助大家理解纯 C…

x64、aarch64、arm与RISC-V64:详解四种处理器架构

x64、aarch64、arm与RISC-V64:详解四种处理器架构 x64架构aarch64架构ARM架构RISC-V64架构总结与展望在计算机科学领域,处理器架构是构建计算机系统的基石,它决定了计算机如何执行指令、管理内存和处理数据。x64、aarch64、arm与RISC-V64是当前主流的四种处理器架构,它们在…

Java使用aspose实现pdf转word

Java使用aspose实现pdf转word 一、下载aspose-pdf-21.6.jar包【下载地址】&#xff0c;存放目录结构如图&#xff1b;配置pom.xml。 <!--pdf to word--> <dependency><groupId>com.aspose</groupId><artifactId>aspose-pdf</artifactId>…

国产编辑器EverEdit - 迷你查找

1 迷你查找 1.1 应用场景 某些场景下&#xff0c;用户不希望调出复杂的查找对话框&#xff0c;此时可以使用迷你查找窗口。 1.2 使用方法 选择主菜单查找 -> 迷你查找&#xff0c;或使用快捷键Ctrl Alt F&#xff0c;会在右上角弹出迷你查找窗口&#xff0c;如下图所示…

嵌入式音视频开发(一)ffmpeg框架及内核解析

系列文章目录 嵌入式音视频开发&#xff08;零&#xff09;移植ffmpeg及推流测试 嵌入式音视频开发&#xff08;一&#xff09;ffmpeg框架及内核解析 文章目录 系列文章目录前言一、ffmpeg的内核1.1 框架解析1.2 内核解析1.3 FFmpeg内部数据流1.3.1 典型的解码流程1.3.2 典型的…

javaEE-11.javaScript入门

目录 一.什么是javaScript 二.快速实现 三.JS引入方式 1.行内引入: 2.内部引入: 3.外部引入: 四.基础语法 1.变量 变量命名规则: 2.数据类型 3.运算符 五.JS对象 1.数组 创建数组: 2.操作数组 3.函数 函数注意事项: 函数参数: 4.对象 1.使用字面量 创建对象:…

畅游Diffusion数字人(16):由音乐驱动跳舞视频生成

畅游Diffusion数字人(0):专栏文章导航 前言:从Pose到跳舞视频生成的工作非常多,但是还没有直接从音乐驱动生成的工作。最近字节跳动提出了MuseDance,无需复杂的动作引导输入(如姿势或深度序列),从而使不同专业水平的用户都能轻松进行灵活且富有创意的视频生成。 目录 贡…

DeepSeek 助力 Vue 开发:打造丝滑的步骤条

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 Deep…

领略算法真谛:差分

嘿&#xff0c;各位技术潮人&#xff01;好久不见甚是想念。生活就像一场奇妙冒险&#xff0c;而编程就是那把超酷的万能钥匙。此刻&#xff0c;阳光洒在键盘上&#xff0c;灵感在指尖跳跃&#xff0c;让我们抛开一切束缚&#xff0c;给平淡日子加点料&#xff0c;注入满满的pa…

【图片转换PDF】多个文件夹里图片逐个批量转换成多个pdf软件,子文件夹单独合并转换,子文件夹单独批量转换,基于Py的解决方案

建筑设计公司在项目执行过程中&#xff0c;会产生大量的设计图纸、效果图、实景照片等图片资料。这些资料按照项目名称、阶段、专业等维度存放在多个文件夹和子文件夹中。 操作需求&#xff1a;为了方便内部管理和向客户交付完整的设计方案&#xff0c;公司需要将每个项目文件…

在Linux上如何让ollama在GPU上运行模型

之前一直在 Mac 上使用 ollama 所以没注意&#xff0c;最近在 Ubuntu 上运行发现一直在 CPU 上跑。我一开始以为是超显存了&#xff0c;因为 Mac 上如果超内存的话&#xff0c;那么就只用 CPU&#xff0c;但是我发现 Llama3.2 3B 只占用 3GB&#xff0c;这远没有超。看了一下命…

程序诗篇里的灵动笔触:指针绘就数据的梦幻蓝图<8>

大家好啊&#xff0c;我是小象٩(๑ω๑)۶ 我的博客&#xff1a;Xiao Xiangζั͡ޓއއ 很高兴见到大家&#xff0c;希望能够和大家一起交流学习&#xff0c;共同进步。 今天我们复习前面学习的指针知识 目录 关于指针数组和数组指针的区别指针数组&#xff08;Array of Poi…

快速集成DeepSeek到项目

DeepSeek API-KEY 获取 登录DeekSeek 官网&#xff0c;进入API 开放平台 2. 创建API-KEY 复制API-KEY进行保存&#xff0c;后期API调用使用 项目中集成DeepSeek 这里只展示部分核心代码&#xff0c;具体请查看源码orange-ai-deepseek-biz-starter Slf4j AllArgsConstructo…

DeepSeek做赛车游戏

赛车模型 2D生成图片 任意AI图片软件SD&#xff0c;MJ 图片生成3D模型 车身 车轮 场景 Rodin,Tripo和Meshy 询问deepSeek如何开发 拷贝代码 将汽车运行代码拖到汽车上 再让AI写个摄像头跟随代码 再去提问deepseek控制轮胎和一些处理细节

未来替代手机的产品,而非手机的本身

替代手机的产品包括以下几种&#xff1a; 可穿戴设备&#xff1a;智能手表、智能眼镜等可穿戴设备可以提供类似手机的功能&#xff0c;如通话、信息推送、浏览网页等。 虚拟现实&#xff08;VR&#xff09;技术&#xff1a;通过佩戴VR头显&#xff0c;用户可以进行语音通话、发…