【Linux】应用层协议序列化和反序列化

在这里插入图片描述

欢迎来到Cefler的博客😁
🕌博客主页:折纸花满衣
🏠个人专栏:题目解析
🌎推荐文章:C++【智能指针】

在这里插入图片描述


前言
在正式代码开始前,会有一些前提知识引入


目录

  • 👉🏻序列化和反序列化
  • 👉🏻三次握手和四次挥手
  • 👉🏻一些概念知识
    • 全双工
    • TCP和UDP对比
    • send和recv
  • 👉🏻TCP通信-使用相同结构化字段传输数据(但未真正的序列化和反序列化)
    • Protocol.hpp(协议内容)
    • Socket.hpp(封装socket通信的功能)
    • TcpServer.hpp(封装服务端功能)
    • TcpServerMain.cc
    • TcpClientMain.cc
    • 实现效果

👉🏻序列化和反序列化

在网络应用层中,序列化(Serialization)和反序列化(Deserialization)是将数据转换为可在网络上传输的格式,并从网络接收的数据恢复为本地数据结构的过程。

🌈序列化(Serialization):

序列化是将数据对象转换为一系列字节的过程,以便在网络上传输或存储到磁盘上。序列化的目标是创建一个可以被发送到其他系统或进程,并且能够被正确解释和还原的数据表示形式。在网络应用中,常见的序列化格式包括JSON(JavaScript Object Notation)、XML(eXtensible Markup Language)、Protocol Buffers、MessagePack等。

序列化的过程通常涉及以下步骤:

  1. 选择序列化格式:根据数据的特性和需求选择合适的序列化格式。
  2. 定义数据结构:确定要序列化的数据对象的结构,并为其定义序列化规则。
  3. 将数据转换为字节流:根据所选的序列化格式,将数据对象转换为字节流。
  4. 传输或存储:将序列化后的字节流发送到网络或者存储到磁盘上。

🌈反序列化(Deserialization):

反序列化是将序列化后的数据流转换回原始数据对象的过程。在接收到网络传输的数据后,需要对其进行反序列化以还原成原始数据对象。

反序列化的过程通常包括以下步骤:

  1. 接收数据流:从网络或者磁盘读取序列化后的数据流。
  2. 解析数据:根据所选的序列化格式,解析字节流并将其转换为数据对象。
  3. 数据还原:根据序列化规则和数据结构,将解析后的数据转换为原始数据对象。
  4. 应用数据:将还原后的数据对象用于应用程序的后续处理。

序列化和反序列化在网络通信中扮演着重要的角色,它们允许不同系统之间以统一的方式进行数据交换,同时也提供了数据传输的可靠性和可扩展性。


📒 一个小故事理解序列化和反序列化

故事标题:糖果工厂的序列化奇遇

一天,糖果工厂的老板决定向全球各地拓展市场,他决定使用一种特殊的糖果序列化器来包装他的糖果,以确保它们在长途运输中保持新鲜和美味。

序列化器(Serializationizer)是一台神奇的机器,它可以将任何形状、口味的糖果转换成一种特殊的串口糖果,这种串口糖果可以轻松地传输到世界各地,并在需要时还原为原始的糖果。

老板向工厂的工程师们解释了他的计划,然后开始了序列化器的操作。首先,他们把一袋五彩斑斓的糖果放进了序列化器中,它发出了一声“嘟噜嘟噜”的声音,然后从另一端输出了一串光滑而有序的串口糖果。

工程师们快乐地向老板展示他们的成果,老板也很满意。于是,他们将这些串口糖果装进了特殊的包装盒,准备发往全球各地的客户。

然而,一名新来的工程师在整理文件时不小心碰到了序列化器的控制台,他误触了一个按钮,导致序列化器的设置发生了改变。

于是,下一个批次的糖果被转换成了一种奇怪的形状,颜色也变得混乱不堪。这些串口糖果被送到了全球各地,但客户们收到后都表示了不满,称他们从未见过如此奇特的糖果。

老板赶紧调查了原因,发现了新工程师的失误。他们及时纠正了序列化器的设置,重新开始了正常的生产。这次,他们确保了每个糖果被正确序列化,而不是变成了像乱七八糟的串口糖果。

结局: 糖果工厂重新获得了客户的信任,全球各地的人们再次享用到了美味的糖果。而那位新工程师也从这个经历中学到了重要的教训:在操作序列化器时一定要小心,否则可能会引发一场糖果灾难!

在这里插入图片描述

👉🏻三次握手和四次挥手

当建立和终止TCP连接时,通常会执行三次握手(Three-Way Handshake)和四次挥手(Four-Way Handshake)的过程,以确保通信的可靠性和正确性。

🌈三次握手(Three-Way Handshake):

  1. 客户端发送同步序列号(SYN)报文:

    • 客户端首先向服务器发送一个带有SYN标志的报文,表示客户端请求建立连接,并选择一个初始的序列号(Sequence Number)。
  2. 服务器确认连接请求:

    • 服务器收到客户端的SYN报文后,会发送一个带有SYN和ACK标志的报文,表示同意建立连接,并确认收到了客户端的连接请求,并选择自己的初始序列号。
  3. 客户端确认连接:

    • 客户端收到服务器的SYN+ACK报文后,会发送一个带有ACK标志的报文给服务器,表示确认连接建立。

这样,客户端和服务器之间的TCP连接就建立起来了,可以开始进行数据传输。

🌈 四次挥手(Four-Way Handshake):

  1. 客户端发送关闭连接请求:

    • 客户端发送一个带有FIN(结束)标志的报文给服务器,表示客户端不再发送数据,但仍愿意接收数据。
  2. 服务器确认关闭请求并关闭数据传输:

    • 服务器收到客户端的FIN报文后,会发送一个带有ACK标志的报文给客户端,表示确认关闭请求,并停止向客户端发送数据,但仍可以接收数据。
  3. 服务器发送关闭连接请求:

    • 服务器发送一个带有FIN标志的报文给客户端,表示服务器也准备关闭连接。
  4. 客户端确认关闭请求并关闭连接:

    • 客户端收到服务器的FIN报文后,会发送一个带有ACK标志的报文给服务器,表示确认关闭请求,并关闭连接。

这样,客户端和服务器之间的TCP连接就完全关闭了。四次挥手的过程中,双方都可以发送数据,并且在关闭连接后都不能再发送数据。


📒 一个小例子理解三次握手和四次挥手
好的,让我们用一种有趣的方式来理解三次握手和四次挥手。

🫱🏻🫲🏻 三次握手(Three-Way Handshake):

想象一下你和朋友约好去吃披萨。这里有个名叫小明的朋友(客户端)和一个叫披萨店的地方(服务器)。

  1. 小明: “嗨,披萨店老板!我想要一份披萨!”(发送SYN)

  2. 披萨店老板: “好的,小明,你想要什么口味的披萨?”(发送SYN+ACK)

  3. 小明: “我想要意大利香肠披萨!”(发送ACK)

现在,小明和披萨店之间建立了连接,披萨店知道了小明的口味,准备开始制作披萨。

🤲🏻 四次挥手(Four-Way Handshake):

披萨终于做好了,大家都吃得很开心,然后就是结束这次美好的披萨时光。

  1. 小明: “披萨店老板,谢谢你的披萨,我不想再点了!”(发送FIN)

  2. 披萨店老板: “不客气,小明,欢迎下次再来!披萨店休息了!”(发送ACK)

  3. 披萨店老板: “好了,披萨店打烊了,我们关门了!”(发送FIN)

  4. 小明: “明白了,披萨店老板,再见!”(发送ACK)

这样,小明和披萨店之间的交流就结束了,披萨店可以关门休息了,而小明也满足地离开了。
在这里插入图片描述

👉🏻一些概念知识

全双工

全双工(Full Duplex)是指数据通信系统中能够同时实现双向通信的能力,即在同一时间点上可以同时进行发送和接收数据的操作。这种模式下,通信双方能够同时进行双向数据传输,而不需要等待对方完成发送或接收操作。

在全双工通信中,发送和接收数据的通道是完全独立的,彼此之间互不干扰。这意味着通信双方可以在不同的频率或者不同的频道上同时进行通信,而不会造成碰撞或数据丢失。

全双工通信通常用于需要高速、实时双向数据传输的场景,比如电话通话、视频会议、网络通信等。相比于半双工通信(Half Duplex),全双工通信具有更高的通信效率和更低的延迟,因为它允许发送和接收数据同时进行,而不需要等待切换操作。

在网络通信中,全双工模式通常通过使用不同的通信频率(如Wi-Fi、蓝牙等无线通信)、不同的通信信道(如以太网的双绞线)或者使用不同的时隙(如时分多址技术)来实现。这种模式在现代通信技术中被广泛应用,为用户提供了更流畅、更高效的通信体验。

TCP和UDP对比

TCP(传输控制协议)和UDP(用户数据报协议)是两种常用的网络传输协议,它们在数据传输时有着不同的特点和适用场景:

  1. 连接性:

    • TCP是面向连接的协议,它在通信双方建立连接后才能进行数据传输,确保数据的可靠性和顺序性。
    • UDP是无连接的协议,通信双方无需建立连接即可直接发送数据,因此不保证数据的可靠性和顺序性。
  2. 可靠性:

    • TCP提供可靠的数据传输,通过序号、确认和重传机制来确保数据的完整性和可靠性,保证数据不会丢失或损坏。
    • UDP不提供可靠性保证,数据包可能会丢失、重复或者乱序,因此在一些实时性要求高、但对数据完整性要求较低的场景下使用较多。
  3. 流量控制(面向字节流)和拥塞控制:

    • TCP通过流量控制和拥塞控制机制来调节数据传输速率,以避免网络拥塞和数据丢失。
    • UDP不提供流量控制和拥塞控制,数据传输速率由发送方直接决定,可能会导致网络拥塞。
  4. 适用场景:

    • TCP适用于需要可靠数据传输和顺序传输的场景,如文件传输、网页浏览、电子邮件等。
    • UDP适用于实时性要求高、但对数据完整性要求较低的场景,如音频和视频流、在线游戏、实时通信等。
  5. 开销:

    • TCP的头部开销较大,包含了序号、确认、窗口大小等信息,因此在传输小量数据时可能会存在较大的开销。
    • UDP的头部开销较小,只包含了源端口、目标端口、长度和校验和等基本信息,因此在传输小量数据时开销较小。

总的来说,TCP提供了可靠的数据传输和顺序传输,适用于对数据完整性要求高的场景;而UDP提供了更快速的数据传输和更低的开销,适用于实时性要求高、但对数据完整性要求较低的场景。选择使用哪种协议取决于具体的应用需求和性能要求。

send和recv

当编写网络程序时,常用的函数之一是sendrecv,它们通常用于在TCP连接上发送和接收数据。

🍓send 函数:

  • 功能: 用于在已建立的连接上发送数据。
  • 语法: send(socket, data, flags)
    • socket:指定发送数据的套接字。
    • data:要发送的数据。
    • flags:指定发送操作的可选标志。

🍓recv 函数:

  • 功能: 用于从已建立的连接上接收数据。
  • 语法: recv(socket, buffersize, flags)
    • socket:指定接收数据的套接字。
    • buffersize:指定接收缓冲区的大小。
    • flags:指定接收操作的可选标志。

这两个函数在TCP编程中非常常见,它们允许程序在客户端和服务器之间进行双向通信。

🍴 与sendto 函数和 recvfrom 函数的区别:

  • sendto 函数: 用于在无连接的套接字上发送数据。通常用于UDP套接字。
    • 它需要指定目标地址和端口。
  • recvfrom 函数: 用于从无连接的套接字上接收数据。通常用于UDP套接字。
    • 它返回发送数据的源地址和端口。

在网络编程中,write和read函数通常用于TCP套接字,因此它们也是基于已连接的。

总的来说,sendrecv函数适用于TCP连接,而sendtorecvfrom函数适用于UDP套接字。前者是基于连接的,后者是无连接的。

👉🏻TCP通信-使用相同结构化字段传输数据(但未真正的序列化和反序列化)

代码目录:
在这里插入图片描述

Protocol.hpp(协议内容)

#pragma once

#include<iostream>
#include<memory>
using namespace std;

class Request
{
public:
    Request()
    {}
    Request(int x,int y,char op)
    :_data_x(x),_data_y(y),_oper(op)
    {}


    void Inc()
    {
        _data_x++;
        _data_y++;
    }
    void Debug()
    {
        cout<<"_data_x: "<<_data_x<<endl;
        cout<<"_data_y: "<<_data_y<<endl;
        cout<<"_oper: "<<_oper<<endl;
    }


private:
    int _data_x;
    int _data_y;
    char _oper;//操作数
};

class Response
{
public:
    Response()
    {}
    Response(int result,int code)
    :_result(result),_code(code){

    }
private:
    int _result;
    int _code;
};

//工厂模式,建造类设计模式,直接返回指针对象
class Factory
{
public:
    shared_ptr<Request> BuildRequest()
    {
        shared_ptr<Request> req = make_shared<Request>();
        return req;
    }
    
       shared_ptr<Request> BuildRequest(int x,int y,char op)
       {
        shared_ptr<Request> req = make_shared<Request>(x,y,op);
        return req;
    }

    shared_ptr<Response> BuildResponse()
    {
        shared_ptr<Response> resp = make_shared<Response>();

        return resp;
    }
      shared_ptr<Response> BuildResponse(int result,int code)
    {
        shared_ptr<Response> resp = make_shared<Response>(result,code);

        return resp;
    }

};

Socket.hpp(封装socket通信的功能)

#pragma once 

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

#define Convert(addrptr) ((struct sockaddr*)addrptr)

using namespace std;

namespace Net_Work
{
    const static int defaultsockfd = -1;
    const int backlog = 5;
    enum
    {
        SocketError = 1,
        BindError,
        ListenError,
    };
    //封装一个基类:Socket接口类
    class Socket
    {
    public:
        virtual ~Socket(){}
        virtual void CreateSocketOrDie() = 0;//创建一个套接字
        virtual void BindSocketOrDie(uint16_t port) = 0;//套接字进行绑定网络信息
        virtual void ListenSocketOrDie(int backlog) = 0;//进行监听
        virtual Socket* AcceptConnection(string * peerip,uint16_t* peerport)=0;//接收连接,并返回一个新的套接字
        virtual bool ConnectServer(string& peerip,uint16_t peerport)=0;//连接服务端
        virtual int GetSockFd() = 0;//返回套接字描述符
        virtual void SetSockFd(int sockfd) = 0;//
        virtual void CloseSocket() = 0;//关闭套接字
    public:
        void BuildListenSocketMethod(uint16_t port,int backlog)//创建一个监听服务
        {
            //1.创建套接字
            CreateSocketOrDie();
            //2.套接字进行绑定网络信息
            BindSocketOrDie(port);
            //3.开始监听
            ListenSocketOrDie(backlog);
        }
        bool BuildConnectSocketMethod(string& serverip,uint16_t& serverport)//创建一个连接服务
        {
            //1.创建套接字
            CreateSocketOrDie();
            return ConnectServer(serverip,serverport);

        }
        void BuildNormalSocketMethod(int sockfd)
        {
            SetSockFd(sockfd);
        }
    };

    //实现Tcp套接字
    class TcpSocket:public Socket
    {
    public:
        TcpSocket(int sockfd = defaultsockfd )
        :_sockfd(sockfd)
        {

        }
        ~TcpSocket(){}
        /
       
         void CreateSocketOrDie() override//创建一个套接字
         {
            _sockfd = socket(AF_INET,SOCK_STREAM,0);
            if(_sockfd<0)
                exit(SocketError);
         }

         void BindSocketOrDie(uint16_t port) override//套接字进行绑定网络信息
         {
            //本地网络信息初始化
            struct sockaddr_in local;
            memset(&local,0,sizeof(local));
            local.sin_family = AF_INET;
            local.sin_addr.s_addr = INADDR_ANY;//服务端的ip由本地随机绑定
            local.sin_port = htons(port);

            //开始绑定
            int n = bind(_sockfd,Convert(&local),sizeof(local));
            if(n<0) exit(BindError);
         }
         void ListenSocketOrDie(int backlog) override//进行监听
         {
            int n = listen(_sockfd,backlog);
            if(n<0) exit(ListenError);
         }
        Socket* AcceptConnection(string * peerip,uint16_t* peerport)override//接收连接
        {
            struct sockaddr_in peer;//用来存储客户端的地址信息
            socklen_t len = sizeof(peer);
            int newsockfd = accept(_sockfd,Convert(&peer),&len);
            if(newsockfd<0)
                return nullptr;

            *peerport = ntohs(peer.sin_port);//网络序列本地化
            *peerip = inet_ntoa(peer.sin_addr);

            Socket* s = new TcpSocket(newsockfd);
            return s;
        }
         bool ConnectServer(string& serverip,uint16_t serverport)override//连接服务端
         {
            struct sockaddr_in server;//存储服务端的地址信息
            memset(&server,0,sizeof(server));
            server.sin_family = AF_INET;
            server.sin_port = htons(serverport);
            server.sin_addr.s_addr = inet_addr(serverip.c_str());//ip网络字节序化,4字节

            int n = connect(_sockfd,Convert(&server),sizeof(server));
            if(n==0)
                return true;
            else
                return false;
         }
         int GetSockFd() override//返回套接字描述符
         {
            return _sockfd;
         }
         void SetSockFd(int sockfd) override//
         {
            _sockfd = sockfd;
         }
         void CloseSocket() override//关闭套接字
         {
            if(_sockfd>defaultsockfd)
                close(_sockfd);
         }
    private:
        int _sockfd;
    };
};

TcpServer.hpp(封装服务端功能)

#pragma once

#include"Socket.hpp"
#include<pthread.h>
#include<functional>

using func_t = function<void(Net_Work::Socket* sockp)>;

class TcpServer;

class ThreadData
{
public:
    ThreadData(TcpServer* tcp_this,Net_Work::Socket* sockp)
    :_this(tcp_this),_sockp(sockp)
    {}
public:
    TcpServer* _this;//TcpServer的指针对象
    Net_Work::Socket* _sockp;//套接字指针对象
};


class TcpServer
{
public:
    TcpServer(uint16_t port,func_t handler_request)
    :_port(port),_listensocket(new Net_Work::TcpSocket()),_hanlder_request(handler_request)
    {
        _listensocket->BuildListenSocketMethod(_port,Net_Work::backlog);//开启监听事务
    }
    static void * ThreadRun(void* args)//因为pthread_create要求方法参数中的参数必须只有一个void*
    //所以必须变为静态,否则成员函数第一个参数默认隐式为this指针
    {
        //因为执行的是多线程,这里我们也没有封装线程的自动回收
        //所以为了不发生线程阻塞,我们要让当前线程与主线程分离,不影响主线程,并且自己做完任务自己回收
        pthread_detach(pthread_self());
        ThreadData* td = static_cast<ThreadData*>(args);
       
        td->_this->_hanlder_request(td->_sockp);//执行_hanlder_request方法

        td->_sockp->CloseSocket();//关闭accept的新套接字
        delete td->_sockp;//销毁指针
        delete td;
        return nullptr;
    }
    void Loop()
    {
         while(true)
        {
            string peerip;
             uint16_t peerport;
            Net_Work::Socket* newsocket = _listensocket->AcceptConnection(&peerip,&peerport);//接收客户端信息
            if(newsocket==nullptr) continue;
                 cout<<"获取一个新连接,sockfd:"<<newsocket->GetSockFd()<<"client info: "<<peerip<<" "<<peerport<<endl;

            //用完后关闭newsocket
            //newsocket->CloseSocket();   

            //使用多线程进行处理任务
            pthread_t tid;
            ThreadData* td = new ThreadData(this,newsocket);
            pthread_create(&tid,nullptr,ThreadRun,td);//线程创建并执行相对应任务
         }
    }

    ~TcpServer()
    {
        delete _listensocket;
    }

private:
    uint16_t _port;
    Net_Work::Socket* _listensocket;

public:
    func_t _hanlder_request;//request执行方法

};

TcpServerMain.cc

#include"Protocol.hpp"
#include"Socket.hpp"
#include"TcpServer.hpp"
#include<memory>

using namespace Net_Work;
void HandlerRequest(Socket* sockp)
{
    while(true)
    {
        struct Request req;//用来存储客户端发来的需求信息
        recv(sockp->GetSockFd(),&req,sizeof(req),0);//接收
        req.Debug();//打印信息
    }
}

int main(int argc,char* argv[])
{
     if(argc != 2)
    {
        cout << "Usage : " << argv[0] << " port" << std::endl;
        return 0;
    }
    uint16_t  localport = stoi(argv[1]);

    unique_ptr<TcpServer> svr (new TcpServer(localport,HandlerRequest));//unique_ptr只能支持移动构造

    svr->Loop();//server开始不断获取新连接
    return 0;
}

TcpClientMain.cc

#include"Protocol.hpp"
#include"Socket.hpp"


int main(int argc,char* argv[])
{
    if(argc != 3)
    {
        cout << "Usage : " << argv[0] << " serverip serverport" << std::endl;
        return 0;
    }
    string serverip = argv[1];
    uint16_t  serverport = stoi(argv[2]);

    Net_Work::Socket* s = new Net_Work::TcpSocket();
    if(!s->BuildConnectSocketMethod(serverip, serverport))
    {
        cerr << "connect " << serverip << ":" << serverport << " failed" << std::endl;
    }
    cout << "connect " << serverip << ":" << serverport << " success" << std::endl;

    unique_ptr<Factory> factory = make_unique<Factory>();//创建一个工厂对象指针(后续可以生产需求和回应),工厂只能有一个,所以用unique_ptr指针

    shared_ptr<Request> req = factory->BuildRequest(10,20,'+');

    while(true)
    {
        req->Inc();
        send(s->GetSockFd(),&(*req),sizeof(*req),0);//将需求信息发送给服务端
        sleep(1);
    }

    s->CloseSocket();//关闭套接字
    return 0;
}

实现效果

在这里插入图片描述


如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注🌹🌹🌹❤️ 🧡 💛,学海无涯苦作舟,愿与君一起共勉成长
在这里插入图片描述

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

人造石墨电极下游应用集中在钢铁冶炼领域 行业市场份额集中在少数企业

人造石墨电极下游应用集中在钢铁冶炼领域 行业市场份额集中在少数企业 人造石墨电极是以石油焦、针状焦为主要原材料&#xff0c;煤沥青为粘结剂&#xff0c;经过煅烧、粉碎磨粉、配料混捏、挤压成形、焙烧、沥青浸渍、石墨化、机械加工等一系列工序生产出来的一种耐高温石墨质…

第47篇:简易处理器<一>

Q&#xff1a;本期我们开始介绍一种数字系统----简易处理器&#xff0c;可以执行由指令指定的各种操作。 A&#xff1a;简易处理器包含多个9位寄存器、一个数据选择器、一个加/减法器单元和一个控制单元(有限状态机)。 数据选择器&#xff1a;可以将输入数据加载到各种寄存器&…

Linux(磁盘管理与文件系统)

目录 1. 磁盘基础 1.1 磁盘结构 1.2 MBR 1.3 磁盘分区结构 2. 文件系统类型 2.1 XFS文件系统 2.2 SWAP 2.3 fdisk命令 2.4 创建新硬盘 3.创建文件系统 3.1 mkfs 3.2 挂载、卸载文件系统 3.3 查看磁盘使用情况 1. 磁盘基础 1.1 磁盘结构 磁盘的物理结构 盘片:硬…

活动理论的散点图

import pandas as pd import matplotlib.pyplot as plt# 假设您已经有一个名为 data.xlsx 的 Excel 文件 # 您可以使用以下代码读取数据# 读取 Excel 文件 try:data pd.read_excel(data.xlsx) except Exception as e:print(f"Error: {e}")# 假设您的数据包含以下列:…

网络安全事件频发,让态势感知来提前洞察快速防护

一、引言 随着信息技术的飞速发展&#xff0c;网络安全问题日益凸显&#xff0c;成为社会各界普遍关注的焦点。近年来&#xff0c;网络安全事件频发&#xff0c;给个人、企业乃至国家带来了严重的损失。这些事件不仅揭示了网络安全领域的严峻挑战&#xff0c;也敲响了信息安全…

使用Docker,【快速】搭建个人博客【WordPress】

目录 1.安装Mysql&#xff0c;创建&#xff08;WordPress&#xff09;用的数据库 1.1.安装 1.2.创建数据库 2.安装Docker 3.安装WodPress&#xff08;使用Docker&#xff09; 3.1.创建文件夹 3.2.查看镜像 3.3.获取镜像 3.4.查看我的镜像 3.5.使用下载的镜像&#xf…

SFusion论文速读

SFusion: Self-attention Based N-to-One Multimodal Fusion Block 摘要 人们用不同的感官感知世界&#xff0c;例如视觉、听觉、嗅觉和触觉。处理和融合来自多种模式的信息使人工智能能够更轻松地理解我们周围的世界。然而&#xff0c;当缺少模态时&#xff0c;可用模态的数…

group by 多字段分组查询和 order by

直接看试验步骤就知道了. 表 一.单列group by 执行单列group by语句 SELECT name, COUNT(1) count FROM nomol GROUP BY name 执行结果 我们发现他把原始表分为了两个小组&#xff0c;狗狗小组和猫猫小组。从这可以看出分组查询就是把相同的数据分到一个组 . 二.多列group …

陇剑杯 ios 流量分析 CTF writeup

陇剑杯 ios 流量分析 链接&#xff1a;https://pan.baidu.com/s/1KSSXOVNPC5hu_Mf60uKM2A?pwdhaek 提取码&#xff1a;haek目录结构 LearnCTF ├───LogAnalize │ ├───linux简单日志分析 │ │ linux-log_2.zip │ │ │ ├───misc日志分析 │ │…

在STM32中给固定的地址写入一个值,并通过memory窗口进行查看

首先对STM32中存储数据的地方有一个了解 一个是FLASH,一个是RAM RAM是易失存储器&#xff0c;FLASH是非易失存储器&#xff0c;这是最直观的解释。 主要记住以下几点&#xff1a; RAM&#xff08;随机存储器&#xff09;&#xff1a;既可以从中读取数据&#xff0c;也可以写…

乘风破浪,川流入海 —— LLM在阿里妈妈智能文案的应用

1.引言 在现代广告系统中&#xff0c;文案是不可或缺的一部分&#xff0c;几乎参与了广告素材的各个地方。随着NLP技术的极速发展&#xff0c;广告主逐渐学会并习惯使用文案生成工具直接或者间接的参与到广告的制作中&#xff0c;这为NLP技术在广告场景的应用提供了巨大的潜力和…

网络基础-基于TCP协议的Socket通讯

一、Socket通讯基于TCP协议流程图 UDP 的 Socket 编程相对简单些不在介绍。 二、 服务端程序启动 服务端程序要先跑起来&#xff0c;然后等待客户端的连接和数据。 服务端程序首先调用 socket() 函数&#xff0c;创建网络协议为 IPv4&#xff0c;以及传输协议为 TCP 的…

LearnOpenGl练习题-着色器

LearnOpenGl练习题-着色器 题目地址&#xff1a;着色器 - LearnOpenGL CN 题目一&#xff1a;修改顶点着色器让三角形上下颠倒&#xff1a; #version 330 core layout (location 0) in vec3 aPos; layout (location 1) in vec3 aColor; out vec3 ourColor; void main() {gl…

Swift Publisher 5 for mac:打造精美版面

Swift Publisher 5 for mac&#xff1a;打造精美版面 Swift Publisher 5是一款专业的版面设计和编辑工具&#xff0c;为Mac用户提供了强大的设计功能和直观的操作界面。以下是关于Swift Publisher 5的功能介绍&#xff1a; 直观易用的界面&#xff1a;用户能够轻松地使用Swift …

《人工智能》文档715篇,文档非常详细,云盘下载

人工智能未来的前景十分广阔&#xff0c;随着技术的不断发展&#xff0c;越来越多的人工智能应用将会出现在我们的生活中&#xff0c;在以下方面有着广泛的应用&#xff1a;《人工智能》文档715篇&#xff0c;文档非常详细&#xff0c;值得收藏 下载地址&#xff1a; 链接&…

浅析LED节能原理

随着全球对节能环保意识的增强&#xff0c;LED显示屏行业也在积极探索更加节能的生产和使用方式。作为显示屏制造厂家&#xff0c;了解和应用LED节能原理不仅是市场的需求&#xff0c;也是企业履行社会责任的表现。本文将浅析LED节能原理及其在显示屏制造中的应用。 LED节能的基…

数据库技术基础

根据希赛相关视频课程汇总整理而成&#xff0c;个人笔记&#xff0c;仅供参考。 基本概念 数据库通常是指有组织地、动态地存储在&#xff08;外存上的相互联系的数据的集合&#xff09;应用数据库主要目的是解决数据&#xff08;共享&#xff09;问题。 三级模式/两级映像&a…

云闪付支付通道接口申请

云闪付支付通道接口申请 云闪付支付通道申请是许多商家都希望完成的一项任务&#xff0c;因为它代表着更多的支付选择&#xff0c;更广泛的用户覆盖和更高的交易安全性。申请云闪付支付通道并不仅仅是一个简单的步骤&#xff0c;它需要商家对自身的业务有深入的了解&#xff0c…

代码随想录阅读笔记-回溯【N皇后】

题目 n 皇后问题 研究的是如何将 n 个皇后放置在 nn 的棋盘上&#xff0c;并且使皇后彼此之间不能相互攻击。 给你一个整数 n &#xff0c;返回所有不同的 n 皇后问题 的解决方案。 每一种解法包含一个不同的 n 皇后问题 的棋子放置方案&#xff0c;该方案中 Q 和 . 分别代表…

【笔记】Telephony SIM SPN及运营商名称显示数据来源介绍

来源介绍 网络名称显示 来源及优先级&#xff08;高到低&#xff09; SourceCommentEnhanced Operator Name String(Eons) 名称信息存放&#xff1a; EF_PNN(PLMN Network Name, fid: 6FC5) &#xff1a;LAC和EF_PNN中的Record Identifier EF_OPL(Operator PLMN List, fid: 6FC…