【Linux网络编程】第四弹---构建UDP服务器与字典翻译系统:源码结构与关键组件解析

个人主页: 熬夜学编程的小林

💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】【Linux系统编程】【Linux网络编程】

目录

1、UdpServer.hpp

1.1、函数对象声明

1.2、Server类基本结构

1.3、构造函数

1.4、Start()

2、Dict.hpp

2.1、基本结构

2.2、加载字典文件

2.3、构造函数

2.4、翻译函数

2.5、dict.txt

3、UdpServerMain.cc

4、完整源码

4.1、Dict.hpp

4.2、dict.txt

4.3、InetAddr.hpp

4.4、LockGuard.hpp

4.5、Log.hpp

4.6、Makefile

4.7、nocopy.hpp

4.8、UdpClientMain.cc

4.9、UdpServerMain.cc


上一弹我们能够完成客户端与服务端的正常通信,但是我们在实际生活中不仅仅是要进行通信,还需要完成某种功能,此弹实现一个英文翻译成中文版服务端!!

注意:此弹内容的实现是在上一弹的原始代码基础上修改的!!

1、UdpServer.hpp

Server类的基本实现没变,但是要增加实现中英文翻译成中文功能的执行函数(即函数类型成员变量),并适当调整构造函数和启动函数!!!

1.1、函数对象声明

此处需要实现一个英文翻译成中文的执行函数,因此函数参数是一个字符串,返回值也是一个字符串!!!

// 声明函数对象
using func_t = std::function<std::string(std::string)>;

1.2、Server类基本结构

基本结构与上一弹的Server类基本一致,只增加了函数对象类型!!!

class UdpServer : public nocopy
{
public:
    UdpServer(func_t func,uint16_t localport = glocalport);
    void InitServer();
    void Start();
    ~UdpServer();
private:
    int _sockfd;          // 文件描述符
    uint16_t _localport;  // 端口号
    bool _isrunning;

    func_t _func; // 执行相应函数
};

1.3、构造函数

 构造函数只需增加一个函数对象参数,初始化列表初始化变量即可!!!

UdpServer(func_t func,uint16_t localport = glocalport)
    : _func(func), _sockfd(gsockfd), _localport(localport), _isrunning(false)
{
}

1.4、Start()

上一弹的Start()函数是先接收客户端的消息,然后将客户端的消息发送回去此弹接收客户端的英文单词,然后服务端翻译成中文,然后将中文发送回去!!!

void Start()
{
    _isrunning = true;
    char inbuffer[1024];
    while (_isrunning)
    {
        // sleep(1);
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        // 接收客户端的英文单词
        ssize_t n = recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr *)&peer, &len);
        if (n > 0)
        {
            InetAddr addr(peer);
            inbuffer[n] = 0;
            // 一个一个的单词
            std::cout << "[" << addr.Ip() << ":" << addr.Port() << "]# " << inbuffer << std::endl;
            
            std::string result = _func(inbuffer); // 执行翻译功能

            // 将翻译的中文结果发回客户端
            sendto(_sockfd,result.c_str(),result.size(),0,(struct sockaddr *)&peer,len);
        }
    }
}

2、Dict.hpp

Dict类执行加载字典文件和执行翻译的功能!!

2.1、基本结构

Dict成员包含一个存储字典的KV结构和一个文件路径

class Dict
{
private:
    // 加载字典文件
    void LoadDict(const std::string& path);
public:
    // 构造
    Dict(const std::string& dict_path);
    // 英语翻译为中文
    std::string Translate(std::string word);
    ~Dict()
    {}
private:
    std::unordered_map<std::string, std::string> _dict; // 字典结构
    std::string _dict_path;                             // 文件路径
};

2.2、加载字典文件

注意:此处的字典文件是以冒号 + 空格来分割英文与中文的!

加载字典文件的本质是以KV的形式将英文单词和中文翻译插入到_dict哈希表中

 加载文件包含3个大的步骤:

  • 1、读方式打开文件
  • 2、按行读取内容[需要考虑中间有空格情况,一行中没找到分隔符情况]
  • 3、关闭文件
const static std::string sep = ": "; // 分隔符 冒号+空格

// 加载字典文件
void LoadDict(const std::string &path)
{
    // 1.读方式打开文件
    std::ifstream in(path);
    // 判断是否打开成功
    if (!in.is_open())
    {
        LOG(FATAL, "open %s failed\n", path.c_str());
        exit(1);
    }

    std::string line;
    // 2.按行读取内容
    while (std::getline(in, line))
    {
        LOG(DEBUG, "load info : %s ,success\n", line.c_str());
        if (line.empty())
            continue; // 中间有空格情况

        auto pos = line.find(sep); // 使用find找到分割符位置,返回迭代器位置
        if (pos == std::string::npos)
            continue; // 一行中没找到分隔符

        // apple: 苹果
        std::string key = line.substr(0, pos); // [) 前闭后开
        if (key.empty())
            continue;

        std::string value = line.substr(pos + sep.size()); // 从pos + 分隔符长度开始到结尾
        if (value.empty())
            continue;

        _dict.insert(std::make_pair(key, value));
    }
    LOG(INFO, "load %s done\n", path.c_str());
    in.close(); // 3.关闭文件
}

2.3、构造函数

构造函数初始化字典文件和加载字典文件(将字典文件以KV格式插入到_dict中)。

// 构造
Dict(const std::string &dict_path) : _dict_path(dict_path)
{
    LoadDict(_dict_path);
}

2.4、翻译函数

翻译函数在_dict中查找是否有该单词,有该单词则返回_dict的value值(没找到返回None)

// 英语翻译为中文
std::string Translate(std::string word)
{
    if (word.empty())
        return "None";
    auto iter = _dict.find(word);
    if (iter == _dict.end())
        return "None";
    else
        return iter->second;
}

2.5、dict.txt

dict.txt文件存储对应的英文单词和翻译结果

apple: 苹果
banana: 香蕉
cat: 猫
dog: 狗
book: 书
pen: 笔
happy: 快乐的
sad: 悲伤的
run: 跑
jump: 跳
teacher: 老师
student: 学生
car: 汽车
bus: 公交车
love: 爱
hate: 恨
hello: 你好
goodbye: 再见
summer: 夏天
winter: 冬天

3、UdpServerMain.cc

服务端主函数基本结构没变,但是构造指针对象时需要传递翻译函数对象,但是这个函数对象的参数是字符串类型,返回值是字符串类型,而Dict类中的翻译函数有this指针,因此我们需要使用bind()绑定函数

// .udp_client local-port
// .udp_client 8888
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << "  server-port" << std::endl;
        exit(0);
    }

    uint16_t port = std::stoi(argv[1]);
    EnableScreen();

    Dict dict("./dict.txt");                                                      // 构造字典类
    func_t translate = std::bind(&Dict::Translate, &dict, std::placeholders::_1); // 绑定翻译函数

    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(translate, port); // C++14标准
    usvr->InitServer();
    usvr->Start();

    return 0;
}

运行结果 

4、完整源码

4.1、Dict.hpp

#pragma once

#include <iostream>
#include <fstream>
#include <string>
#include <unordered_map>
#include "Log.hpp"

using namespace log_ns;

const static std::string sep = ": "; // 分隔符 冒号+空格

class Dict
{
private:
    // 加载字典文件
    void LoadDict(const std::string &path)
    {
        // 1.读方式打开文件
        std::ifstream in(path);
        // 判断是否打开成功
        if (!in.is_open())
        {
            LOG(FATAL, "open %s failed\n", path.c_str());
            exit(1);
        }

        std::string line;
        // 2.按行读取内容
        while (std::getline(in, line))
        {
            LOG(DEBUG, "load info : %s ,success\n", line.c_str());
            if (line.empty())
                continue; // 中间有空格情况

            auto pos = line.find(sep); // 使用find找到分割符位置,返回迭代器位置
            if (pos == std::string::npos)
                continue; // 一行中没找到分隔符

            // apple: 苹果
            std::string key = line.substr(0, pos); // [) 前闭后开
            if (key.empty())
                continue;

            std::string value = line.substr(pos + sep.size()); // 从pos + 分隔符长度开始到结尾
            if (value.empty())
                continue;

            _dict.insert(std::make_pair(key, value));
        }
        LOG(INFO, "load %s done\n", path.c_str());
        in.close(); // 3.关闭文件
    }

public:
    // 构造
    Dict(const std::string &dict_path) : _dict_path(dict_path)
    {
        LoadDict(_dict_path);
    }
    // 英语翻译为中文
    std::string Translate(std::string word)
    {
        if (word.empty())
            return "None";
        auto iter = _dict.find(word);
        if (iter == _dict.end())
            return "None";
        else
            return iter->second;
    }
    ~Dict()
    {
    }

private:
    std::unordered_map<std::string, std::string> _dict; // 字典结构
    std::string _dict_path;                             // 文件路径
};

4.2、dict.txt

apple: 苹果
banana: 香蕉
cat: 猫
dog: 狗
book: 书
pen: 笔
happy: 快乐的
sad: 悲伤的
run: 跑
jump: 跳
teacher: 老师
student: 学生
car: 汽车
bus: 公交车
love: 爱
hate: 恨
hello: 你好
goodbye: 再见
summer: 夏天
winter: 冬天

4.3、InetAddr.hpp

#pragma once

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

class InetAddr
{
private:
    // 网络地址转本地地址 
    void ToHost(const struct sockaddr_in& addr) 
    {
        _port = ntohs(addr.sin_port); // 网络转主机
        _ip = inet_ntoa(addr.sin_addr); // 结构化转字符串
    }
public:
    InetAddr(const struct sockaddr_in& addr):_addr(addr)
    {
        ToHost(addr);
    }
    std::string Ip()
    {
        return _ip;
    }
    uint16_t Port()
    {
        return _port;
    }
    ~InetAddr()
    {}
private:
    std::string _ip;
    uint16_t _port;
    struct sockaddr_in _addr;
};

4.4、LockGuard.hpp

#pragma once 
#include <pthread.h>

class LockGuard
{
public:
    LockGuard(pthread_mutex_t* mutex):_mutex(mutex)
    {
        pthread_mutex_lock(_mutex);
    }
    ~LockGuard()
    {
        pthread_mutex_unlock(_mutex);
    }
private:
    pthread_mutex_t* _mutex;
};

4.5、Log.hpp

#pragma once

#include <iostream>
#include <fstream>
#include <ctime>
#include <cstring>
#include <sys/types.h>
#include <unistd.h>
#include <stdarg.h>
#include <pthread.h>
#include "LockGuard.hpp"

namespace log_ns
{

    // 日志等级
    enum
    {
        DEBUG = 1,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };

    std::string LevelToString(int level)
    {
        switch (level)
        {
        case DEBUG:
            return "DEBUG";
        case INFO:
            return "INFO";
        case WARNING:
            return "WARNING";
        case ERROR:
            return "ERROR";
        case FATAL:
            return "FATAL";
        default:
            return "UNKNOW";
        }
    }

    std::string GetCurrTime()
    {
        time_t now = time(nullptr); // 时间戳
        struct tm *curr_time = localtime(&now);
        char buffer[128];
        snprintf(buffer, sizeof(buffer), "%d-%02d-%02d %02d:%02d:%02d",
                    curr_time->tm_year + 1900,
                    curr_time->tm_mon + 1,
                    curr_time->tm_mday,
                    curr_time->tm_hour,
                    curr_time->tm_min,
                    curr_time->tm_sec);
        return buffer;
    }

    class logmessage
    {
    public:
        std::string _level;        // 日志等级
        pid_t _id;                 // pid
        std::string _filename;     // 文件名
        int _filenumber;           // 文件行号
        std::string _curr_time;    // 当前时间
        std::string _message_info; // 日志内容
    };

#define SCREEN_TYPE 1
#define FILE_TYPE 2

    const std::string glogfile = "./log.txt";
    pthread_mutex_t glock = PTHREAD_MUTEX_INITIALIZER;

    // log.logMessage(""/*文件名*/,12/*文件行号*/,INFO/*日志等级*/,"this is a %d message,%f,%s,hello world"/*日志内容*/,x,,);
    class Log
    {
    public:
        // 默认向显示器打印
        Log(const std::string &logfile = glogfile) : _logfile(logfile), _type(SCREEN_TYPE)
        {
        }
        // 打印方式
        void Enable(int type)
        {
            _type = type;
        }
        // 向屏幕打印
        void FlushToScreen(const logmessage &lg)
        {
            printf("[%s][%d][%s][%d][%s] %s",
                    lg._level.c_str(),
                    lg._id,
                    lg._filename.c_str(),
                    lg._filenumber,
                    lg._curr_time.c_str(),
                    lg._message_info.c_str());
        }
        // 向文件打印
        void FlushToFile(const logmessage &lg)
        {
            std::ofstream out(_logfile, std::ios::app); // 追加打开文件
            if (!out.is_open())
                return; // 打开失败直接返回

            char logtxt[2048];
            snprintf(logtxt, sizeof(logtxt), "[%s][%d][%s][%d][%s] %s",
                        lg._level.c_str(),
                        lg._id,
                        lg._filename.c_str(),
                        lg._filenumber,
                        lg._curr_time.c_str(),
                        lg._message_info.c_str());
            out.write(logtxt, strlen(logtxt)); // 写文件
            out.close();                       // 关闭文件
        }
        // 刷新日志
        void FlushLog(const logmessage &lg)
        {
            // 加过滤逻辑 --- TODO
            // ...

            LockGuard lockguard(&glock); // RAII锁
            switch (_type)
            {
            case SCREEN_TYPE:
                FlushToScreen(lg);
                break;
            case FILE_TYPE:
                FlushToFile(lg);
                break;
            }
        }
        // ... 可变参数(C语言)
        // 初始化日志信息
        void logMessage(std::string filename, int filenumber, int level, const char *format, ...)
        {
            logmessage lg;

            lg._level = LevelToString(level);
            lg._id = getpid();
            lg._filename = filename;
            lg._filenumber = filenumber;
            lg._curr_time = GetCurrTime();

            va_list ap;           // va_list-> char*指针
            va_start(ap, format); // 初始化一个va_list类型的变量
            char log_info[1024];
            vsnprintf(log_info, sizeof(log_info), format, ap);
            va_end(ap); // 释放由va_start宏初始化的va_list资源
            lg._message_info = log_info;

            // std::cout << lg._message_info << std::endl; // 测试
            // 日志打印出来(显示器/文件)
            FlushLog(lg);
        }
        ~Log()
        {
        }

    private:
        int _type;            // 打印方式
        std::string _logfile; // 文件名
    };

    Log lg;
// 打印日志封装成宏,使用函数方式调用
#define LOG(Level, Format, ...)                                          \
    do                                                                   \
    {                                                                    \
        lg.logMessage(__FILE__, __LINE__, Level, Format, ##__VA_ARGS__); \
    } while (0)
// 设置打印方式,使用函数方式调用
#define EnableScreen()          \
    do                          \
    {                           \
        lg.Enable(SCREEN_TYPE); \
    } while (0)
// 设置打印方式,使用函数方式调用
#define EnableFile()          \
    do                        \
    {                         \
        lg.Enable(FILE_TYPE); \
    } while (0)

}

4.6、Makefile

.PHONY:all
all:udpserver udpclient

udpserver:UdpServerMain.cc
	g++ -o $@ $^ -std=c++14

udpclient:UdpClientMain.cc
	g++ -o $@ $^ -std=c++14

.PHONY:clean 
clean:
	rm -rf udpserver udpclient

4.7、nocopy.hpp

#pragma once

class nocopy
{
public:
    nocopy(){}
    ~nocopy(){}
    nocopy(const nocopy&) = delete;
    const nocopy& operator=(const nocopy&) = delete;
};

4.8、UdpClientMain.cc

#include "UdpServer.hpp"
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

// 客户端在未来一定要知道服务器的IP地址和端口号
// .udp_client server-ip server-port
// .udp_client 127.0.0.1 8888
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << " server-ip server-port" << std::endl;
        exit(0);
    }

    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    // 1.创建套接字
    int sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "create socket eror\n"
                  << std::endl;
        exit(1);
    }

    // client的端口号,一般不让用户自己设定,而是让client 所在OS随机选择?怎么选择?什么时候?
    // client 需要bind它自己的IP和端口,但是client 不需要 "显示" bind它自己的IP和端口
    // client 在首次向服务器发送数据的时候,OS会自动给client bind它自己的IP和端口

    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());

    while (true)
    {
        std::string line;
        std::cout << "Please Enter# ";
        std::getline(std::cin, line); // 以行读取消息
        // 发消息,你要知道发送给谁
        int n = sendto(sockfd, line.c_str(), line.size(), 0, (struct sockaddr *)&server, sizeof(server));
        if(n > 0)
        {
            // 收消息
            struct sockaddr_in temp;
            socklen_t len = sizeof(temp);
            char buffer[1024];
            int m = recvfrom(sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&temp,&len);
            if(m > 0)
            {
                buffer[m] = 0;
                std::cout << buffer << std::endl;
            }
            else
            {
                break;
            }
        }
        else
        {
            break;
        }
    }

    // 关闭套接字
    ::close(sockfd);
    return 0;
}

4.9、UdpServerMain.cc

#include "UdpServer.hpp"
#include "Dict.hpp"
#include <memory>

// .udp_client local-port
// .udp_client 8888
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << "  server-port" << std::endl;
        exit(0);
    }

    uint16_t port = std::stoi(argv[1]);
    EnableScreen();

    Dict dict("./dict.txt");                                                      // 构造字典类
    func_t translate = std::bind(&Dict::Translate, &dict, std::placeholders::_1); // 绑定翻译函数

    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(translate, port); // C++14标准
    usvr->InitServer();
    usvr->Start();

    return 0;
}

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

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

相关文章

数字IC后端设计实现之分段长clock tree经典案例

最近发现很多读者问到分段长clock tree的做法&#xff0c;小编今天给大家分享几个SoC芯片中复杂时钟结构设计的分段长clock tree的应用案例。希望对各位的学习和工作有所助益。 数字后端设计实现之时钟树综合实践篇 数字IC后端实现专家都具备哪些技能&#xff1f;&#xff08…

计算机毕业设计Spark+SpringBoot旅游推荐系统 旅游景点推荐 旅游可视化 旅游爬虫 景区客流量预测 旅游大数据 大数据毕业设计

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

EasyMedia播放rtsprtmp视频流

学习链接 MisterZhang/EasyMedia - gitee地址 EasyMedia转码rtsp视频流flv格式&#xff0c;hls格式&#xff0c;H5页面播放flv流视频 文章目录 学习链接介绍步骤easydarwin启动rtsp服务&#xff0c;ffmpeg推送摄像头&#xff08;模拟rtsp视频流&#xff09;nginx添加rtmp支持…

【Linux】开启你的Linux之旅:初学者指令指南

Linux相关知识点可以通过点击以下链接进行学习一起加油&#xff01; 在 Linux 开发中&#xff0c;GDB 调试器和 Git 版本控制工具是开发者必备的利器。GDB 帮助快速定位代码问题&#xff0c;Git 则提供高效的版本管理与协作支持。本指南将简明介绍两者的核心功能与使用技巧&…

SpringBoot-问题排查 Controller全局打印入参,返回值,响应时间,异常日志

问题: 想要打印每次请求的入参,返回值,响应时间,异常日志,如果给每个方法挨个添加打印日志非常麻烦 解决方案: 使用切面的方式将所有的Controller每个方法加入切入点使用环绕通知的方式可以在切入点执行前后执行切面,符合我们的需求在方法执行前后打印相关日志忽略LogIgnore注解…

mysql数据库varchar截断问题

用了这么多年mysql数据库&#xff0c;才发现varchar是可以截断的&#xff0c;而且是在我们线上数据库。个人觉得dba的这个设置是非常有问题的&#xff0c;用户往数据库里存东西&#xff0c;就是为了以后用的&#xff0c;截断了存放&#xff0c;数据不完整&#xff0c;就用不了了…

C++草原三剑客之一:继承

为王的诞生献上礼炮吧&#xff01; 目录 1 继承的概念及其定义 1.1 继承的概念 1.2 继承的定义 1.2.1 定义格式 1.2.2 继承方式以及继承基类成员访问方式的变化 1.3 继承类模板 2 基类和派生类之间的转换 3 继承中的作用域 3.1 隐藏规则 3.2 两道考察继承作用的相关…

Crash-SQLiteDiskIOException

目录 相关问题 日志信息 可能原因 问题排查 相关问题 蓝牙wifi无法使用 日志信息 可能原因 磁盘空间不足&#xff1a;当设备上的可用存储空间不足时&#xff0c;SQLite无法完成磁盘I/O操作&#xff0c;从而导致SQLiteDiskIOException。 数据库文件损坏&#xff1a;如果数…

6.824/6.5840 Lab 1: Lab 3: Raft

漆昼中温柔的不像话 静守着他的遗憾啊 旧的摇椅吱吱呀呀停不下 风卷走了满院的落叶落花 ——暮色回响 完整代码见&#xff1a; https://github.com/SnowLegend-star/6.824 在完成Lab之前&#xff0c;务必把论文多读几遍&#xff0c;力求完全理解Leader选举、log日志等过程。 …

【C++动态规划 BFS 博弈】3283. 吃掉所有兵需要的最多移动次数|2473

本文涉及知识点 C动态规划 CBFS算法 数学 博弈 LeetCode3283. 吃掉所有兵需要的最多移动次数 给你一个 50 x 50 的国际象棋棋盘&#xff0c;棋盘上有 一个 马和一些兵。给你两个整数 kx 和 ky &#xff0c;其中 (kx, ky) 表示马所在的位置&#xff0c;同时还有一个二维数组 …

6.824/6.5840 Lab 2: Key/Value Server

故事里能毁坏的只有风景 谁也摧毁不了我们的梦境 弦月旁的流星划过了天际 我许下 的愿望 该向谁 去说明 ——我是如此相信 完整代码见&#xff1a; https://github.com/SnowLegend-star/6.824 还是那句话&#xff0c;尽量只是参考思路而不是照抄 先阅读几遍实验说明的Introd…

Linux-异步IO和存储映射IO

异步IO 在 I/O 多路复用中&#xff0c;进程通过系统调用 select()或 poll()来主动查询文件描述符上是否可以执行 I/O 操作。而在异步 I/O 中&#xff0c;当文件描述符上可以执行 I/O 操作时&#xff0c;进程可以请求内核为自己发送一个信号。之后进程就可以执行任何其它的任务…

R 语言科研绘图第 1 期 --- 折线图-基础

在发表科研论文的过程中&#xff0c;科研绘图是必不可少的&#xff0c;一张好看的图形会是文章很大的加分项。 为了便于使用&#xff0c;本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中&#xff0c;获取方式&#xff1a; R 语言科研绘图模板 --- sciRplothttps://mp.…

企业中数据防泄漏如何防范?有哪些防泄密措施?

企业数据不仅是业务运营的核心&#xff0c;也是企业竞争力的关键所在。 然而&#xff0c;随着信息技术的快速发展&#xff0c;数据泄露的风险也随之增加。 数据一旦泄露&#xff0c;不仅可能导致企业经济损失&#xff0c;还可能损害企业声誉&#xff0c;甚至引发法律纠纷。 …

汽车控制软件下载移动管家手机控车一键启动app

移动管家手机控制汽车系统是一款实现车辆远程智能控制的应用程序‌。通过下载并安装特定的APP&#xff0c;用户可以轻松实现以下功能&#xff1a;‌远程启动与熄火‌&#xff1a;无论身处何地&#xff0c;只要有网络&#xff0c;即可远程启动或熄火车辆&#xff0c;提前预冷或预…

基于事件驱动构建 AI 原生应用

作者&#xff1a;寒斜 AI 应用在商业化服务的阶段会面临诸多挑战&#xff0c;比如更快的服务交付速度&#xff0c;更实时、精准的结果以及更人性化的体验等&#xff0c;传统架构限制于同步交互&#xff0c;无法满足上述需求&#xff0c;本篇文章给大家分享一下如何基于事件驱动…

如何查看阿里云ddos供给量

要查看阿里云上的 DDoS 攻击量&#xff0c;你可以通过阿里云的 云盾 DDoS 防护 服务来进行监控和查看攻击数据。阿里云提供了详细的流量监控、攻击日志以及攻击趋势分析工具&#xff0c;帮助用户实时了解 DDoS 攻击的情况。以下是九河云总结的查看 DDoS 攻击量的步骤&#xff1…

华为HarmonyOS 让应用快速拥有账号能力 - 获取用户手机号

场景介绍 当应用对获取的手机号时效性要求不高时&#xff0c;可使用Account Kit提供的手机号授权与快速验证能力&#xff0c;向用户发起手机号授权申请&#xff0c;经用户同意授权后&#xff0c;获取到手机号并为用户提供相应服务。以下只针对Account kit提供的手机号授权与快…

React 的学习记录一:与 Vue 的相同点和区别

目录 一、学习目标 二、学习内容1️⃣——React的特点 1.组件化设计 2.单向数据流 3.声明式 UI 4.虚拟 DOM 5.Hooks 6.JSX 7.React Native 三、React与vue的比较总结 四、总结 一、学习目标 时间&#xff1a;两周 内容&#xff1a; React的特点React的入门React的…

使用epoll监测定时器是否到达指定时间,并执行回调函数

总览&#xff1a;Linux提供了定时器&#xff0c;暴露出来了文件描述符&#xff0c;所以我们使用epoll帮助我们监测&#xff0c;时间到达后&#xff0c;epoll_wait返回&#xff0c;于是我们根据fd&#xff0c;找到对应的回调函数&#xff0c;然后执行。从而达到定时执行函数的目…