Linux网络-自定义协议、序列化和反序列化、网络计算服务器的实现和Windows端客户端

文章目录

  • 前言
  • 一、自定义协议
    • 传结构体对象
  • 序列化和反序列化
    • 什么是序列化?
    • 反序列化
  • 二、计算器服务端(线程池版本)
    • 1.main.cc
    • 2.Socket.hpp
    • 3.protocol.hpp
    • 4.Calculator.hpp
    • 5.serverCal.hpp
    • 6.threadPool.hpp
    • 7.Task.hpp
    • 8. log.hpp
  • 客户端
  • Windows客户端
  • 运行


前言

我们已经学会了Tcp、Udp网络传输协议,并且之前我们也实现了简易的聊天室和翻译器。

我们知道,传输层是OS系统里就给我们写好的, 应用层才是我们自己需要去编写的,现在我们对应用层来进行一些初步的了解。


一、自定义协议

在之前我们使用Tcp和Udp服务进行网络通信,我们一直都是以字符串的形式互相发送消息。

那么我们就只能发送字符串吗? 当然不是,Udp是基于数据流进行网络通信,Udp是基于字节流进行网络通信,虽然我们对字节流和数据流并没有一个特别清晰的认识,但是我们可以知道的是,我们其实是可以传各种各样的类型进行通行的。

这里就提出一种方案

传结构体对象

从我们之前写的代码来看,只要我们在发送数据和接收数据时制定一个协议,每次只读写一个结构体对象的大小,那么我们就可以将该结构体进行通信。 而实际上,底层的很多库也确实是这么做的,但是这并不代表这种方式不存在明显弊端。

最大的弊端就是,你能保证在不同环境之下,同样的结构体类型在不同主机、不同环境下的大小是一样的吗。 结构体的大小涉及到许多,比如说结构体字节对齐,32位和64位系统下内置类型大小可能不同…

所以我们并不推崇这种方案。

而实际上我们其实更推崇传字符串的方式。

序列化和反序列化

什么是序列化?

在现实生活中,我们介绍自己,有些人习惯先说自己的名字,有些人习惯先说自己来自于哪一个城市。 如果你需要去统计大量的人的信息,最好就是先列一个表格,然后让他们严格按照表格上的个人信息顺序去介绍自己,这就是序列化。

再比如说我们今天要写一个网络版本的计算器,我可以写成1+1,也可以写成一加一,再也可以写写成1 + 1,中间带几个空格。 那么这样的话,我们服务器接受到的数据就是形形色色的,不利于我们去解析。

于是我们就制定一个协议,你必须要写成"1 + 1"的形式,否则就是违反协议!

反序列化

反序列化很简单,就比如说我们收到了一段数据,它是一个字符串形式的"1 + 1",我们就需要将该字符串进行解析,反序列化成int x = 1, char op = ‘+’ , int y = 1.
这也是一种反序列化。

二、计算器服务端(线程池版本)

1.main.cc

#include "serverCal.hpp"

void Usage(const char *mes)
{
    std::cout << "Usage: " << mes << " port[8080-9000]" << std::endl;
}

const std::string default_ip = "0.0.0.0";

enum{
    Usage_Err = 1
};

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage("./serverCal");
        exit(Usage_Err);
    }
    ServerCal sc;
    sc.Init(AF_INET, default_ip, atoi(argv[1]));
    sc.Run();
    return 0;
}

2.Socket.hpp

对套接字进行封装

#pragma once

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include "log.hpp"
enum
{
    Socket_Err = 1,
    Bind_Err,
    Listen_Err
};

extern Log lg;

const int backlog = 10;

class Socket
{
public:
    Socket()
        : _sockfd(-1)
    {
    }

    int Getfd()
    {
        return _sockfd;
    }
    void Init()
    {
        int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
        if (socket_fd < 0)
        {
            lg(Fatal, "Socket Create Failed...");
            exit(Socket_Err);
        }
        lg(Info, "Socket Create Succeeded...");

        _sockfd = socket_fd;
    }

    void Bind(const int sinfamily, const std::string &ip, const uint16_t port)
    {
        memset(&_sockaddr, 0, sizeof _sockaddr);
        switch (sinfamily)
        {
        case AF_INET:
            _sockaddr.sin_family = AF_INET;
            break;
        case AF_INET6:
            _sockaddr.sin_family = AF_INET6;
            break;
        }
        _sockaddr.sin_port = htons(port);
        inet_aton(ip.c_str(), &(_sockaddr.sin_addr));
        int n = bind(_sockfd, (const struct sockaddr *)&_sockaddr, sizeof _sockaddr);
        if (n < 0)
        {
            lg(Fatal, "Bind Failed...");
            exit(Bind_Err);
        }
        lg(Info, "Bind Succeeded..., port: %d", port);
    }

    void Listen()
    {
        int n = listen(_sockfd, backlog);
        if (n < 0)
        {
            lg(Fatal, "Listen Failed...");
            exit(Listen_Err);
        }
        lg(Info, "Listen Succeeded...");
    }

    int Accept(struct sockaddr_in *clientsock, socklen_t *len)
    {
        int fd = accept(_sockfd, (sockaddr *)clientsock, len);
        if (fd < 0)
        {
            lg(Warning, "Accept Failed...");
            return -1;
        }
        lg(Info, "Accept Succeeded..., Get A new Link, fd: %d", fd);

        return fd;
    }

    int Connect(const std::string &ip, const std::string &port)
    {
        struct sockaddr_in serversock;
        serversock.sin_port = htons(atoi(port.c_str()));
        serversock.sin_family = AF_INET;
        inet_aton(ip.c_str(), &serversock.sin_addr);

        int n = connect(_sockfd, (const struct sockaddr *)&serversock, sizeof serversock);
        if (n < 0)
        {
            lg(Warning, "Accept Failed...");
            return n;
        }
        lg(Info, "Connect Succeeded...");
        return n;
    }

    ~Socket()
    {
        close(_sockfd);
    }

private:
    int _sockfd;
    struct sockaddr_in _sockaddr;
};

3.protocol.hpp

序列化和反序列化的协议制定

#pragma once
#include <iostream>
#include <string>
#include "log.hpp"

extern Log lg;
const char blank_space_sep = ' ';
const char protocol_sep = '\n';

enum Code
{
    Div_Zero_Err = 1,
    Mod_Zeor_Err,
    Operatorr_Err
};

class Request
{
public:
    Request() {} // 提供一个无参构造

    Request(int x, int y, char op)
        : _x(x), _y(y), _operator(op) {}

    bool serialize(std::string *out_str)
    {
        // 协议规定 字符串格式应序列化为"len\n""_x + _y\n"
        std::string main_body = std::to_string(_x);
        main_body += blank_space_sep;
        main_body += _operator;
        main_body += blank_space_sep;
        main_body += std::to_string(_y);
        *out_str = std::to_string(main_body.size());
        *out_str += protocol_sep;
        *out_str += main_body;
        *out_str += protocol_sep;
        return true;
    }

    bool deserialize(std::string &in_str)
    {
        // 协议规定 in_str的格式应为"len\n""_x + _y\n..."
        size_t pos = in_str.find(protocol_sep);
        if (pos == std::string::npos)
        {
            // 说明没找到'\n'
            lg(Warning, "Message Format Error..., No Found The First Second \\n");
            return false;
        } 
        std::string sl = in_str.substr(0, pos);
        int len = std::stoi(sl); // 如果这里的sl不是一串数字,stoi就会抛异常! BUG
        int total_len = sl.size() + 1 + len + 1;
        if (in_str.size() < total_len)
        {
            lg(Warning, "Message Format Error..., Lenth Error");
            return false;
        }
        if (in_str[total_len - 1] != '\n')
        {
            
            lg(Warning, "Message Format Error..., No Found The Second \\n");
            return false;
        }

        std::string main_body = in_str.substr(pos + 1, len);
        // main_body"_x +  _y"
        int left = main_body.find(blank_space_sep);
        if (left == std::string::npos)
        {
            // 说明没找到' '
            lg(Warning, "Message Format Error..., No Found The First ' '");
            return false;
        }
        int right = main_body.rfind(blank_space_sep);
        if (left == right)
        {
            // 说明只有一个' '
            lg(Warning, "Message Format Error...,No Found The Second ' '");
            return false;
        }
        _x = std::stoi(main_body.substr(0, left));   // 如果这里的sl不是一串数字,stoi就会抛异常! BUG
        _y = std::stoi(main_body.substr(right + 1)); // 如果这里的sl不是一串数字,stoi就会抛异常! BUG
        _operator = main_body[left + 1];
        in_str.erase(0, total_len);
        return true;
    }

    void print()
    {
        std::cout << _x << " " << _operator << " " << _y << std::endl;
    }
    ~Request() {}

public:
    int _x;
    int _y;
    char _operator;
};

class Respond
{
public:
    Respond() {} // 提供一个无参构造

    Respond(int result, int code)
        : _result(result), _code(code) {}

    bool serialize(std::string *out_str)
    {
        // 协议规定 字符串格式应序列化为"_result _code"
        *out_str = std::to_string(_result);
        *out_str += blank_space_sep;
        *out_str += std::to_string(_code);
        return true;
    }

    bool deserialize(const std::string &in_str)
    {
        // 协议规定 in_str的格式应为"_result _code"
        size_t pos = in_str.find(blank_space_sep);
        if (pos == std::string::npos)
        {
            // 没找到字符' '
            lg(Warning, "Result Message Error...");
            return false;
        }
        _result = std::stoi(in_str.substr(0, pos));
        _code = std::stoi(in_str.substr(pos + 1));
        return true;
    }

    void print()
    {
        std::cout << _result << " " << _code << std::endl;
    }

    ~Respond() {}

public:
    int _result;
    int _code; // 表示结果可信度  0表示可信
};

4.Calculator.hpp

计算器功能接口函数

#pragma once
#include"protocol.hpp"

class Calculator{
    public:
    Calculator() {}

    Respond calculate(const Request& rq)
    {
        Respond rs;
        switch (rq._operator)
        {
            case '+':
            rs._result = rq._x + rq._y;
            break;
            case '-':
            rs._result = rq._x - rq._y;
            break;
            case '*':
            rs._result = rq._x * rq._y;
            break;
            case '/':
            if(rq._y == 0)
            {
                lg(Warning,"Found Div Zero Error...");
                rs._code = Div_Zero_Err;
            }
            rs._result = rq._x / rq._y;
            break;
            case '%':
            if(rq._y == 0)
            {
                lg(Warning,"Found Mod Zero Error...");
                rs._code = Mod_Zeor_Err;
            }
            rs._result = rq._x - rq._y;
            break;
            default:
            lg(Warning,"Found Operator Error...");
            rs._code = Operatorr_Err;
            break;
        }
        return rs;
    }
};


5.serverCal.hpp

代码如下(示例):

#pragma once

#include "Socket.hpp"
#include "protocol.hpp"
#include "threadPool.hpp"
#include "Task.hpp"
class ServerCal
{
public:
    ServerCal()
    {
    }

    void Init(const int sinfamily, const std::string &ip, const uint16_t port)
    {
        _listensock.Init();
        _listensock.Bind(sinfamily, ip, port);
        _listensock.Listen();
    }

    void Run()
    {
        ThreadPool<Task> *tp = ThreadPool<Task>::GetInstance();
        tp->Start();
        struct sockaddr_in client;


        while (true)
        {
            memset(&client, 0, sizeof client);
            socklen_t len;
            int socketfd = _listensock.Accept(&client, &len);
            if (socketfd < 0)
                continue;
            tp->Push(socketfd);
        }
    }

private:
    Socket _listensock;
};

6.threadPool.hpp

很熟悉的线程池封装

#pragma once

#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <pthread.h>
#include <unistd.h>

struct ThreadInfo
{
    pthread_t tid;
    std::string name;
};

static const int defalutnum = 10;

template <class T>
class ThreadPool
{
public:
    void Lock()
    {
        pthread_mutex_lock(&mutex_);
    }
    void Unlock()
    {
        pthread_mutex_unlock(&mutex_);
    }
    void Wakeup()
    {
        pthread_cond_signal(&cond_);
    }
    void ThreadSleep()
    {
        pthread_cond_wait(&cond_, &mutex_);
    }
    bool IsQueueEmpty()
    {
        return tasks_.empty();
    }
    std::string GetThreadName(const pthread_t tid)
    {
        for (const auto &ti : threads_)
        {
            if (ti.tid == tid)
                return ti.name;
        }
        return "None";
    }

public:
    static void *HandlerTask(void *args)
    {
        ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);
        std::string name = tp->GetThreadName(pthread_self());
        while (true)
        {
            tp->Lock();

            while (tp->IsQueueEmpty())
            {
                tp->ThreadSleep();
            }
            T t = tp->Pop();
            tp->Unlock();

            t();
        }
    }
    void Start()
    {
        int num = threads_.size();
        for (int i = 0; i < num; i++)
        {
            threads_[i].name = "thread-" + std::to_string(i + 1);
            pthread_create(&(threads_[i].tid), nullptr, HandlerTask, this);
        }
    }
    T Pop()
    {
        T t = tasks_.front();
        tasks_.pop();
        return t;
    }
    void Push(const T &t)
    {
        Lock();
        tasks_.push(t);
        Wakeup();
        Unlock();
    }
    static ThreadPool<T> *GetInstance()
    {
        if (nullptr == tp_) // ???
        {
            pthread_mutex_lock(&lock_);
            if (nullptr == tp_)
            {
                // std::cout << "log: singleton create done first!" << std::endl;
                tp_ = new ThreadPool<T>();
            }
            pthread_mutex_unlock(&lock_);
        }

        return tp_;
    }

private:
    ThreadPool(int num = defalutnum) : threads_(num)
    {
        pthread_mutex_init(&mutex_, nullptr);
        pthread_cond_init(&cond_, nullptr);
    }
    ~ThreadPool()
    {
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&cond_);
    }
    ThreadPool(const ThreadPool<T> &) = delete;
    const ThreadPool<T> &operator=(const ThreadPool<T> &) = delete; // a=b=c
private:
    std::vector<ThreadInfo> threads_;
    std::queue<T> tasks_;

    pthread_mutex_t mutex_;
    pthread_cond_t cond_;

    static ThreadPool<T> *tp_;
    static pthread_mutex_t lock_;
};

template <class T>
ThreadPool<T> *ThreadPool<T>::tp_ = nullptr;

template <class T>
pthread_mutex_t ThreadPool<T>::lock_ = PTHREAD_MUTEX_INITIALIZER;


7.Task.hpp

派发给线程池的任务

#pragma once
#include "Socket.hpp"
#include "protocol.hpp"
#include "Calculator.hpp"
class Task
{
public:
    Task(int socket_fd)
        : _socket_fd(socket_fd)
    {
    }

    void run()
    {
        char in_buffer[1024];
        Calculator cal;
        std::string message = "";
        while (true)
        {
            memset(in_buffer, 0, sizeof in_buffer);
            //std:: cout << "开始等待读取..." <<std::endl;
            int n = read(_socket_fd, (void *)in_buffer, sizeof in_buffer - 1);
            //std::cout << n << " " << strerror(errno) <<std::endl;
            //std::cout << "读取到的有效字符为" << n << std::endl;
            if (n == 0)
            {
                lg(Warning, "Connection closed by foreign host, socketfd[%d] closed...", _socket_fd);
                break;
            }
            else if (n < 0)
            {
                lg(Warning, "Read Error, socketfd[%d]...", _socket_fd);
                break;
            }
            in_buffer[n] = 0;
            message += in_buffer;
            //std::cout << "报文大小: "<< message.size() <<" ,报文内容: "<< message << std::endl;
            Request rq;
            if(!rq.deserialize(message))  continue;
            Respond rs = cal.calculate(rq);
            std::string res;
            rs.serialize(&res);
            printf("%d %c %d = %d\n",rq._x,rq._operator,rq._y,rs._result);
            write(_socket_fd, res.c_str(), res.size());
        }
    }
    void operator()()
    {
        run();
        close(_socket_fd);
    }

    ~Task()
    {}

private:
    int _socket_fd;
};

8. log.hpp

输出日志消息

#pragma once

#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

#define SIZE 1024

#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 LogFile "log.txt"

class Log
{
public:
    Log()
    {
        printMethod = Screen;
        path = "./log/";
    }
    void Enable(int method)
    {
        printMethod = method;
    }
    std::string levelToString(int level)
    {
        switch (level)
        {
        case Info:
            return "Info";
        case Debug:
            return "Debug";
        case Warning:
            return "Warning";
        case Error:
            return "Error";
        case Fatal:
            return "Fatal";
        default:
            return "None";
        }
    }

    void printLog(int level, const std::string &logtxt)
    {
        switch (printMethod)
        {
        case Screen:
            std::cout << logtxt << std::endl;
            break;
        case Onefile:
            printOneFile(LogFile, logtxt);
            break;
        case Classfile:
            printClassFile(level, logtxt);
            break;
        default:
            break;
        }
    }
    void printOneFile(const std::string &logname, const std::string &logtxt)
    {
        std::string _logname = path + logname;
        int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt"
        if (fd < 0)
            return;
        write(fd, logtxt.c_str(), logtxt.size());
        close(fd);
    }
    void printClassFile(int level, const std::string &logtxt)
    {
        std::string filename = LogFile;
        filename += ".";
        filename += levelToString(level); // "log.txt.Debug/Warning/Fatal"
        printOneFile(filename, logtxt);
    }

    ~Log()
    {
    }
    void operator()(int level, const char *format, ...)
    {
        time_t t = time(nullptr);
        struct tm *ctime = localtime(&t);
        char leftbuffer[SIZE];
        snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
                 ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,
                 ctime->tm_hour, ctime->tm_min, ctime->tm_sec);

        va_list s;
        va_start(s, format);
        char rightbuffer[SIZE];
        vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
        va_end(s);

        // 格式:默认部分+自定义部分
        char logtxt[SIZE * 2];
        snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);

        // printf("%s", logtxt); // 暂时打印
        printLog(level, logtxt);
    }

private:
    int printMethod;
    std::string path;
};

Log lg;


客户端

#include "Socket.hpp"
#include "protocol.hpp"

void Usage(const char *mes)
{
    std::cout << "Usage: " << mes << " ip[xxx.xxx.xxx.xxx] port[8080-9000]" << std::endl;
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage("./clientCal");
    }
    Socket local;
    local.Init();
    int n = local.Connect(argv[1], argv[2]);
    if (n < 0)
    {
        return 1;
    }

    int localfd = local.Getfd();

    std::cout << "       简易计算器, 目前仅支持\" + - * / %\"运算符 " << std::endl;
    std::cout << "       数字和运算符请用空格或回车隔开" << std::endl;
    Request rq;
    Respond rs;
    std::string message;
    char buffer[1024];
    while (true)
    {
        memset(buffer, 0, sizeof buffer);
        std::cout << "请输入您的算式@ ";
        std::cin >> rq._x >> rq._operator >> rq._y;
        rq.serialize(&message);
        write(localfd, message.c_str(), message.size());

        // 开始等待结果
        n = read(localfd, buffer, sizeof buffer - 1);
        if (n == 0)
        {
            lg(Warning, "Connection closed by foreign host, socketfd[%d] closed...",localfd);
            break;
        }
        else if (n < 0)
        {
            lg(Warning, "Read Error, socketfd[%d]...", localfd);
            break;
        }
        buffer[n] = 0;
        std::string res = buffer;
        rs.deserialize(res);
        if(rs._code != 0)
        {
            switch(rs._code)
            {
                case 1:
                std::cout << "出现除0错误" << std::endl;
                break;
                case 2:
                std::cout << "出现模0错误" << std::endl;
                break;
                case 3:
                std::cout << "使用了除 + - * / % 以外的运算符" << std::endl;
                break;
                default:
                std::cout << "发生未知错误" <<std::endl;
                break;
            }
            continue;
        }
        printf("%d %c %d = %d\n",rq._x,rq._operator,rq._y,rs._result);
    }
    return 0;
}

Windows客户端

#include<iostream>
#include<string>
#include<WinSock2.h>
#include<Windows.h>
#include<functional>
#include<stdlib.h>
#include"protocol.hpp"


#pragma comment(lib, "ws2_32.lib")
#pragma warning(disable:4996)
#pragma execution_character_set("utf-8")


const int server_port = 8889;
const std::string server_ip = "43.143.58.29";

int main()
{
    //初始化网络环境
    WSADATA wsd;
    WSAStartup(MAKEWORD(2, 2), &wsd);
    system("chcp 65001");

    //申请套接字
    SOCKET socket_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (socket_fd == SOCKET_ERROR)
    {
        perror("Socket Error");
        exit(1);
    }
    

    //创建并初始化Server端sockaddr_in结构体
    struct sockaddr_in server;
    memset(&server, 0, sizeof server);
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(server_ip.c_str());
    server.sin_port = htons(server_port);

    //开始连接服务器
    int n = connect(socket_fd, (const struct sockaddr*)&server, sizeof server);
    if (n < 0)
    {
        //连接失败
        std::cout << "Connect Failed" << std::endl;
        return 1;
    }

    std::cout << "       简易计算器, 目前仅支持\" + - * / %\"运算符 " << std::endl;
    std::cout << "       数字和运算符请用空格或回车隔开" << std::endl;
    Request rq;
    Respond rs;
    std::string message;
    char buffer[1024];
    while (true)
    {
        memset(buffer, 0, sizeof buffer);
        std::cout << "请输入您的算式@ ";
        std::cin >> rq._x >> rq._operator >> rq._y;
        rq.serialize(&message);
        send(socket_fd, message.c_str(), (int)message.size(),0);

        // 开始等待结果
        n = recv(socket_fd, buffer, sizeof buffer - 1,0);
        if (n == 0)
        {
            lg(Warning, "Connection closed by foreign host, socketfd[%d] closed...", socket_fd);
            break;
        }
        else if (n < 0)
        {
            lg(Warning, "Read Error, socketfd[%d]...", socket_fd);
            break;
        }
        buffer[n] = 0;
        std::string res = buffer;
        rs.deserialize(res);
        if (rs._code != 0)
        {
            switch (rs._code)
            {
            case 1:
                std::cout << "出现除0错误" << std::endl;
                break;
            case 2:
                std::cout << "出现模0错误" << std::endl;
                break;
            case 3:
                std::cout << "使用了除 + - * / % 以外的运算符" << std::endl;
                break;
            default:
                std::cout << "发生未知错误" << std::endl;
                break;
            }
            continue;
        }
        printf("%d %c %d = %d\n", rq._x, rq._operator, rq._y, rs._result);
    }


    //清理环境
    closesocket(socket_fd);
    WSACleanup();
    return 0;
}

运行

在这里插入图片描述

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

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

相关文章

PS插件一键轻松搞定电商产品摄影图!

在电商行业中&#xff0c;一张高质量的产品摄影图往往能够吸引更多潜在消费者的目光&#xff0c;从而增加产品的销量。然而&#xff0c;对于许多电商卖家和摄影师来说&#xff0c;后期处理产品图片却是一个既耗时又费力的工作。 最近我发现一款PS插件可以一键生成电商产品摄影…

品牌舆情监测系统是什么?怎么监测?

品牌形象与口碑对于企业的重要性不言而喻&#xff0c;品牌舆情监测系统应运而生&#xff0c;成为企业守护品牌声誉的利器。品牌舆情监测系统是什么&#xff1f;怎么选择合适的舆情监测系统&#xff1f;接下来伯乐网络传媒就给大家分享一下。 一、品牌舆情监测系统的应用价值 1…

【一百零四】【算法分析与设计】【模板】二维差分,2132. 用邮票贴满网格图,LCP 74. 最强祝福力场,二位差分,差分思想,记录变化值,离散化技巧

【模板】二维差分 描述 给你一个n行m列的矩阵&#xff0c;下标从1开始。 接下来有q次操作&#xff0c;每次操作输入5个参数x1, y1, x2, y2, k 表示把以(x1, y1)为左上角,(x2,y2)为右下角的子矩阵的每个元素都加上k&#xff0c; 请输出操作后的矩阵。 输入描述&#xff1a; 第一…

读书-《蛤蟆先生去看心理医生》

书名蛤蟆先生去看心理医生作者罗伯特戴博德状态阅读中简介该书借用《柳林风声》的故事主角蛤蟆先生&#xff0c;讲述了他接受心理咨询的故事。作者通过陷入抑郁的蛤蟆先生和心理咨询师苍鹭的互动&#xff0c;探索蛤蟆先生爱炫耀、自卑性格和抑郁情绪的来源&#xff0c;指出童年…

二叉树的OJ题

1.二叉树的前序遍历 /*** Definition for a binary tree node.* struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* };*/ /*** Note: The returned array must be malloced, assume caller calls free().*/int TreeeSize(struct Tre…

通过血清拉曼光谱进行COVID-19的高效初步筛查

通过血清拉曼光谱进行COVID-19的高效初步筛查 原创 小王搬运工 时序课堂 2024-06-04 20:04 四川 论文地址&#xff1a;https://analyticalsciencejournals.onlinelibrary.wiley.com/doi/full/10.1002/jrs.6080 论文源码&#xff1a;无 期刊&#xff1a;JOURNAL OF RAMAN SPE…

制作自己的 ButterKnife(使用 AutoService 和 APT 注解处理器在编译期生成 Java 代码)

ButterKnife 开发过 Android 的肯定都知道曾经有这么一个库&#xff0c;它能够让你不用再写 findViewById 这样的代码&#xff0c;这就是大名鼎鼎的 ButterKnife&#xff08;https://github.com/JakeWharton/butterknife&#xff09;。虽然现在这个库已经不再维护&#xff0c;…

C语言基础学习之链表与共同体

数组: 数据结构---操作时候的特点&#xff1a; 优势&#xff1a;随机访问(存取)方便 不足&#xff1a;插入数据删除数据不方便 链式数据结构--链表 struct stu sl; // s1struct stu s2; // s2struct stu s3; //s3 s1-->s2-->s3 特点: 优势:增力和删除数据方便劣势…

2024年手机能做的赚钱软件有哪些?整理了八个手机能做的正规赚钱软件分享

在这个指尖滑动的时代&#xff0c;手机不仅仅是通讯工具&#xff0c;更是我们探索财富的钥匙。你是否曾幻想过&#xff0c;躺在沙发上&#xff0c;轻轻一滑&#xff0c;就能让钱包鼓起来&#xff1f; 今天&#xff0c;就让我们一起来探索那些隐藏在手机里的赚钱秘笈&#xff0c…

ubuntu系统 kubeadm方式搭建k8s集群

服务器环境与要求&#xff1a; 三台服务器 k8s-master01 192.168.26.130 操作系统&#xff1a; Ubuntu20.04 k8s-woker01 192.168.26.140 操作系统&#xff1a; Ubuntu20.04 k8s-woker02 192.168.26.150 操作系统&#xff1a; Ubuntu20.04 最低配置&#xff1a;2…

C++——IO流

C语言的输入和输出 C语言中我们用到的最频繁的输入输出方式就是scanf ()与printf()。 scanf(): 从标准输入设备(键 盘)读取数据&#xff0c;并将值存放在变量中。printf(): 将指定的文字/字符串输出到标准输出设备(屏幕)。 注意宽度输出和精度输出控制。C语言借助了相应的缓冲…

信息学奥赛初赛天天练-19-挑战程序阅读-探索因数、所有因数平和、质数的奥秘

PDF文档公众号回复关键字:20240604 1 2023 CSP-J 阅读程序3 阅读程序&#xff08;程序输入不超过数组成字符串定义的范围&#xff1a;判断题正确填√&#xff0c;错误填&#xff1b;除特殊说明外&#xff0c;判断题1.5分&#xff0c;选择题3分&#xff0c;共计40分&#xff…

Windows上配置完整Linux开发环境(六):安装CentOS

1、CentOS vs. Ubuntu 基础和发行周期&#xff1a; CentOS 是基于Red Hat Enterprise Linux (RHEL)的社区版。它与RHEL共享源代码&#xff0c;因此提供与RHEL类似的稳定性。CentOS的版本更新不频繁&#xff0c;通常在RHEL发布新版本后才推出&#xff0c;保持长期支持周期&#…

《STM32Cube高效开发教程基础篇》第7章/案例7.1外部中断示例EXTI方式检测按键

文章目录 目的新建项目CudeMX设置CudeIDE编码 目的 采用外部中断方式检验4个按键的输入 新建项目 在案例6_1基础上另存为Demo7_1EXIT CudeMX设置 CudeIDE编码

MobileNetV4实战:使用MobileNetV4实现图像分类任务(一)

文章目录 摘要安装包安装timm 数据增强Cutout和MixupEMA项目结构计算mean和std生成数据集 摘要 论文链接&#xff1a;https://arxiv.org/pdf/2404.10518 MobileNetV4&#xff0c;作为新一代移动设备神经网络架构&#xff0c;凭借其创新的通用倒置瓶颈UIB块和Mobile MQA注意力块…

Java程序策——Java连接数据库保姆级教程(超详细步骤)

【Java程序策】——连接数据库 目录 ​编辑 一&#xff1a;在数据库中建立一个表&#xff08;student表&#xff09; 1.1&#xff1a;进入mysql 1.2&#xff1a;建立一个“数据库成员” 1.3&#xff1a;建立一个表&#xff08;student表&#xff09; 1.4&#xff1a;给表…

Springboot+vue二手房交易管理系统

Springbootvue二手房交易管理系统&#xff0c;项目用mybatis与数据库&#xff0c;数据库为mysql&#xff0c;16个表结构。有问题直接加我询问&#xff0c;我会一直在线哒。 功能如下&#xff1a; 房东管理 用户管理 房源信息管理&#xff08;可预约和查看评论&#xff09; 看房…

C语言 指针——函数指针的典型应用:通用排序

目录 编程实现升序和降序排序 如果不使用函数指针编程… 使用函数指针编写一个通用的排序函数 小结 编程实现升序和降序排序 如果不使用函数指针编程… 使用函数指针编写一个通用的排序函数 小结 正确理解指针的概念  指针是一种特殊的数据类型  指针类型的变量&am…

【动态规划】状态压缩dp

发现dp调试打最后二维dp表非常有用 1.吃奶酪类 先出状态&#xff0c;再走到哪 dp[1][0]0;for(int i3;i<maxn;i){//状态 for(int j1;j<n;j){//走过j if(i&(1<<j)){ for(int k0;k<n;k){//刚才在k dp[i][j]; } } } } P1433 吃奶酪 - 洛谷 | 计算机科学教育新生…

ARP欺骗的原理与详细步骤

ARP是什么&#xff1a; 我还记得在计算机网络课程当中&#xff0c;学过ARP协议&#xff0c;ARP是地址转换协议&#xff0c;是链路层的协议&#xff0c;是硬件与上层之间的接口&#xff0c;同时对上层提供服务。在局域网中主机与主机之间不能直接通过IP地址进行通信&#xff0c…