基于Socket简单的UDP网络程序 vs 简单的TCP网络程序

小白苦学IT的博客主页

初学者必看:Linux操作系统入门

代码仓库:Linux代码仓库

❤关注我一起讨论和学习Linux系统

1.前言

网络编程前言

网络编程是连接数字世界的桥梁,它让计算机之间能够交流信息,为我们的生活和工作带来便利。从简单的网页浏览到复杂的分布式系统,网络编程无处不在。

然而,网络编程涉及诸多复杂概念和技术,如IP地址、端口号、Socket、TCP/UDP协议等,需要我们深入学习和掌握。同时,网络环境的复杂性、数据安全性等问题也带来了挑战。

但正是这些挑战,让网络编程充满了无限可能。掌握网络编程技术,我们可以开发出各种创新应用,为人们提供更高效、智能的服务。

本文旨在介绍网络编程的Socket编程接口及其技术,分享实用经验,帮助读者打下坚实的网络编程基础。

1.socket编程接口

socket常见API

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,socklen_t address_len);
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

sockaddr结构

socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及后面要谈的UNIX DomainSocket. 然而, 各种网络协议的地址格式并不相同.

  • IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16位端口号和32位IP地址。
  • IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6. 这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容。
  • socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数。

sockaddr 结构 

sockaddr 是一个通用的套接字地址结构,它用于表示各种类型的套接字地址。但是,sockaddr 结构本身并不包含足够的信息来确定地址的类型,因此它通常被更具体的结构(如 sockaddr_in)所替代。sockaddr 结构的主要作用是为不同的地址结构提供一个统一的接口。

  • 通用性sockaddr是一个通用的套接字地址结构,设计初衷是为了能够表示各种类型的套接字地址,包括IPv4、IPv6以及其他可能的地址类型。这种通用性使得sockaddr能够作为许多网络编程函数的参数,如bindconnectrecvfromsendto等,用于指明地址信息。
  • 扩展性:通过定义sa_family字段,sockaddr能够区分不同类型的地址结构。这使得在未来引入新的地址类型时,不需要修改现有函数的接口,只需定义新的地址结构并设置相应的sa_family即可。

sockaddr_in 结构

sockaddr_in 是 sockaddr 结构的一个特例,用于表示 IPv4 地址和端口号。它包含了 IP 地址和端口号的信息,以及地址族和协议信息。

  • IPv4特化:尽管sockaddr具有通用性,但在实际编程中,特别是在处理IPv4地址时,直接使用sockaddr结构会显得过于复杂和冗余。sockaddr_in结构是针对IPv4地址设计的,它包含了IPv4地址和端口号等必要信息,并且以更直观和易于操作的方式呈现这些信息。
  • 便利性sockaddr_in提供了专门的字段来存储IPv4地址(sin_addr)和端口号(sin_port),这使得在处理IPv4网络编程任务时更加方便和高效。同时,通过类型转换,sockaddr_in结构可以很容易地转换为sockaddr结构,从而与需要sockaddr参数的函数兼容。

in_addr结构 

in_addr 结构用于表示一个 IPv4 地址。它通常与 sockaddr_in 结构一起使用,作为 sin_addr 字段的类型。

在这个结构中,s_addr 是一个无符号长整数,表示 IPv4 地址。在实际使用中,我们通常不会直接操作这个长整数,而是使用诸如 inet_pton 和 inet_ntop 这样的函数来将点分十进制格式的 IP 地址(如 "192.168.1.1")转换为 in_addr 结构,或者将 in_addr 结构转换为点分十进制格式的字符串。 

  • IPv4地址表示in_addr结构专门用于表示IPv4地址。它通过一个无符号长整数(s_addr)来存储IPv4地址,这种表示方式在网络编程中非常常见。尽管IPv4地址通常以点分十进制的形式表示(如192.168.1.1),但在内部处理和网络传输时,它们通常被转换为这种整数形式。
  • 转换方便in_addr结构使得在点分十进制格式和内部整数格式之间转换IPv4地址变得相对简单。通过调用如inet_ptoninet_ntop这样的函数,可以轻松实现这两种格式之间的转换,从而方便网络编程中的地址处理。

总结一下就是:

  • sockaddr 是一个通用的套接字地址结构,用于表示各种类型的地址。
  • sockaddr_in 是 sockaddr 的一个特例,用于表示 IPv4 地址和端口号。
  • in_addr 用于表示 IPv4 地址。

这三种结构的存在是为了满足不同网络编程需求和提高编程效率。sockaddr提供了通用性和扩展性,sockaddr_in则针对IPv4地址提供了更直观和便利的操作方式,而in_addr则专门用于表示和转换IPv4地址。在实际编程中,根据具体需求选择合适的结构进行处理,可以提高代码的可读性和可维护性。

2.简单UDP的echo服务器(代码实现)

封装 UdpSocket

UdpServer.hpp

默认ip用 0.0.0.0

端口:8080

对udp服务器进行封装:

#pragma once
#include "Log.hpp"
#include <string>
#include <strings.h>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<errno.h>
#include<functional>

using func_t = std::function<std::string (const std::string&,uint16_t &,const std::string&)>;

uint16_t defaultport = 8080;
std::string defaultip = "0.0.0.0";
const int size = 1024;

enum
{
    SOCKET_ERR = 1,
    BIND_ERR

};

class UdpServer
{
public:
    UdpServer(const uint16_t &port = defaultport, const std::string &ip = defaultip)
        :_sockfd(-1), _port(port), _ip(ip),_isrunning(false)
    {
    }
    void Init()
    {
        //1.创建udp socket
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            log.LogMessage(FATAL, "socket create error,_sockfd: %d", _sockfd);
            exit(SOCKET_ERR);
        }
        log.LogMessage(INFO, "socket create success, _sockfd: %d ", _sockfd);

        //2.bind socket
        struct sockaddr_in local;
        bzero(&local,sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);//需要保证我的端口号是网络字节序列,因为该端口号是要给对方发送的。
        local.sin_addr.s_addr = inet_addr(_ip.c_str()); //1.string->uint32_t 2.uint32_t 必须是网络序列的
        //local.sin_addr.s_addr = htonl(INADDR_ANY);

        if(bind(_sockfd,(const struct sockaddr *)&local,sizeof(local))<0)
        {
            log.LogMessage(FATAL,"bind error , error: %d, error string : %s",errno,strerror(errno));
            exit(BIND_ERR);
        }
            log.LogMessage(INFO,"bind success , error: %d, error string : %s",errno,strerror(errno));

    }

    void Run(func_t func)
    {
        _isrunning = true;
        char inbuffer[size];
        while(_isrunning)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            std::cout<<"server is run!!!"<<std::endl;
            ssize_t n = recvfrom(_sockfd,inbuffer,sizeof(inbuffer) - 1,0,(struct sockaddr *)&client,&len);
            if(n<0)
            {
                log.LogMessage(WARNING,"recvfrom error, errno: %d ,errno string : %s",errno,strerror(errno));
                continue;
            }

            uint16_t clientport = ntohs(client.sin_port);
            std::string clientip = inet_ntoa(client.sin_addr);

            inbuffer[n] = 0;
            //充当了一次数据的处理
            std::string info = inbuffer;
            std::string echo_string = func(info,clientport,clientip);

            sendto(_sockfd,echo_string.c_str(),echo_string.size(),0,(struct sockaddr*)&client,len);

        }
    }

    ~UdpServer() 
    {
        if(_sockfd>0) close(_sockfd);
    }

private:
    int _sockfd;//网络文件描述符
    std::string _ip;//字符串类型ip地址
    uint16_t _port;//服务器进程的端口号   
    bool _isrunning;
};

Main.cc

#include"UdpServer.hpp"
#include<memory>
#include<iostream>
#include<cstdio>
#include<vector>

void Usage(std::string proc)
{
    std::cout<<"\n\rUsage: "<<proc<<" port[1024+]\n"<<std::endl;
}

std::string Handler(const std::string& str,uint16_t & clientport,const std::string& clientip)
{
    std::cout<<"[ ip: "<< clientip<<" port: "<<clientport<<" ]# ";
    std::string res = "server get a message: ";
    res+=str;
    std::cout<<res<<std::endl;
    return res;
}

bool SafeCheck(const std::string & cmd)
{
    std::vector<std::string> key_word = 
    {
        "rm",
        "mv",
        "cp",
        "kill",
        "sudo",
        "unlink",
        "uninstall",
        "yum",
        "top"
    };

    for(auto &word:key_word)
    {
        auto pos = cmd.find(word);
        if(pos!=std::string::npos) return false;
    }

    return true;
}

std::string ExcuteCommand(const std::string & cmd)
{
    if(!SafeCheck(cmd)) return "bad man";
    FILE* fp = popen(cmd.c_str(),"r");
    if(nullptr == fp)
    {
        perror("popen error");
        return "error";
    }

    std::string result;
    char buffer[4096];
    while(true)
    {
        char * getc = fgets(buffer,sizeof(buffer),fp);
        if(nullptr == getc)
        {
            break;
        }
        result+=buffer;
    }
    pclose(fp);
    return result;

}


int main(int argc,char* argv[])
{
    if(argc!=2)
    {
        Usage(argv[0]);
        exit(1);
    }

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

    std::unique_ptr<UdpServer> svr(new UdpServer(port));
    svr->Init();
    svr->Run(Handler);    
    return 0;
}

UdpClient.cc(客户端代码)

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

using namespace std;

void Usage(std::string proc)
{
    std::cout << "\n\rUsage: " << proc << " serverip serverport\n"
              << std::endl;
}

// ./udpclient serverip serverport
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(0);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    struct sockaddr_in server;
    bzero(&server, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport); //?
    server.sin_addr.s_addr = inet_addr(serverip.c_str());
    socklen_t len = sizeof(server);

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        cout << "socker error" << endl;
        return 1;
    }

    // client 要bind吗?要!只不过不需要用户显示的bind!一般有OS自由随机选择!
    // 一个端口号只能被一个进程bind,对server是如此,对于client,也是如此!
    // 其实client的port是多少,其实不重要,只要能保证主机上的唯一性就可以!
    // 系统什么时候给我bind呢?首次发送数据的时候

    string message;
    char buffer[1024];
    while (true)
    {
        cout << "Please Enter@ ";
        getline(cin, message);
        cout<<message<<endl;

        // std::cout << message << std::endl;
        // 1. 数据 2. 给谁发
        sendto(sockfd, message.c_str(), message.size(), 0, (const sockaddr *)&server, len);
        
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);

        ssize_t s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len);
        if(s > 0)           
        {
            buffer[s] = 0;
            cout << buffer << endl;
        }
    }

    close(sockfd);
    return 0;
}

日志类:

#pragma once

#include <iostream>
#include <cstdarg>
#include <ctime>
#include <string>
#include <unistd.h>
#include <fstream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

enum
{
    DEBUG = 0,
    INFO,
    WARNING,
    ERROR,
    FATAL
};

enum
{
    Screen = 10,
    Onefile,
    Classfile
};

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 "Unknown";
    }
}

const int defaultstyle = Screen;
const std::string default_filename = "log.";
const std::string logdir="log";

class Log
{
public:
    Log():style(defaultstyle),filename(default_filename)
    {
        mkdir(logdir.c_str(),0775);
    }

    void Enable(int sty)
    {
        style = sty;
    }

    std::string TimestampToLocalTime()
    {
        time_t curr = time(nullptr);
        struct tm *currtime = localtime(&curr);
        char time_buffer[128];
        snprintf(time_buffer, sizeof(time_buffer), "%d-%d-%d %d:%d:%d",
                 currtime->tm_year + 1900, currtime->tm_mon, currtime->tm_mday, currtime->tm_hour,
                 currtime->tm_min, currtime->tm_sec);

        return time_buffer;
    }

    void WriteLog(const std::string &levelstr, const std::string &message)
    {
        switch (style)
        {
        case Screen:
            std::cout << message<<std::endl;
            break;
        case Onefile:
            WriteLogToOnefile("all", message);
            break;
        case Classfile:
            WriteLogToClassfile(levelstr, message);
            break;
        default:
            break;
        }
    }

    void WriteLogToOnefile(const std::string &logname, const std::string &message)
    {
        umask(0);
        int fd = open(logname.c_str(),O_CREAT | O_WRONLY | O_APPEND,0666);
        if(fd<0)return;
        write(fd,message.c_str(),message.size());
        close(fd);
        // std::ofstream out(logname);
        // if (!out.is_open())
        //     return;
        // out.write(message.c_str(), message.size());
        // out.close();
    }

    void WriteLogToClassfile(const std::string &levelstr, const std::string &message)
    {
        std::string logname = logdir;
        logname+="/";
        logname+=filename;
        logname += levelstr;
        WriteLogToOnefile(logname, message);
    }

    void LogMessage(int level, const char *format, ...) // 类c的日志接口
    {
        char rightbuffer[1024];
        va_list args;
        va_start(args, format);
        vsnprintf(rightbuffer, sizeof(rightbuffer), format, args);
        va_end(args);

        char leftbuffer[1024];
        std::string curtime = TimestampToLocalTime();
        std::string levelstr = LevelToString(level);
        std::string idstr = std::to_string(getpid());
        snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%s][%s]",
                 levelstr.c_str(), curtime.c_str(), idstr.c_str());

        std::string logInfo = leftbuffer;
        logInfo += rightbuffer;

        WriteLog(levelstr, logInfo);
    }
    ~Log() {}

private:
    int style;
    std::string filename;
};


Log log;

class Conf
{
public:
    Conf()
    {
        log.Enable(Screen);
    }
    ~Conf(){}
};

Conf conf;

Makefile

.PHONY:all
all:udpserver udpclient

udpserver:Main.cc
	g++ -o $@ $^ -g -std=c++11
udpclient:UdpClient.cc
	g++ -o $@ $^ -g -std=c++11

.PHONY:clean
clean:
	rm -rf udpserver udpclient

运行结果:

实现了客户端,服务端双方交互,当然我们这只是简单的进行数据处理,其实还可以通过实现其他功能,这里可以发挥自己的想象去写。

地址转换函数

这里只介绍基于IPv4的socket网络编程,sockaddr_in中的成员struct in_addr sin_addr表示32位 的IP 地址,但是我们通常用点分十进制的字符串表示IP 地址,以下函数可以在字符串表示 和in_addr表示之间转换;

字符串转in_addr的函数:

in_addr转字符串的函数:

其中inet_pton和inet_ntop不仅可以转换IPv4的in_addr,还可以转换IPv6的in6_addr,因此函数接口是void*addrptr。

关于inet_ntoa

inet_ntoa这个函数返回了一个char*, 很显然是这个函数自己在内部为我们申请了一块内存来保存ip的结果. 那么是否需要调用者手动释放呢?

man手册上说, inet_ntoa函数, 是把这个返回结果放到了静态存储区. 这个时候不需要我们手动进行释放.

那么问题来了, 如果我们调用多次这个函数, 会有什么样的效果呢? 参见如下代码:

#include<stdio.h>
#include<netinet/in.h>
#include<arpa/inet.h>

int main()
{
    struct sockaddr_in addr1;
    struct sockaddr_in addr2;
    addr1.sin_addr.s_addr=0;
    addr2.sin_addr.s_addr=0xffffffff;
    char* ptr1 = inet_ntoa(addr1.sin_addr);
    char* ptr2 = inet_ntoa(addr2.sin_addr);
    printf("ptr1: %s,ptr2: %s\n",ptr1,ptr2);
    return 0;
}

运行结果:

因为inet_ntoa把结果放到自己内部的一个静态存储区, 这样第二次调用时的结果会覆盖掉上一次的结果

  • 如果有多个线程调用 inet_ntoa, 是否会出现异常情况呢?
  • 在APUE中, 明确提出inet_ntoa不是线程安全的函数;
  • 但是在centos7上测试, 并没有出现问题, 可能内部的实现加了互斥锁;
  • 在多线程环境下, 推荐使用inet_ntop, 这个函数由调用者提供一个缓冲区保存结果, 可以规避线程安全问题;

如果测试如下代码:

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
void *Func1(void *p)
{
    struct sockaddr_in *addr = (struct sockaddr_in *)p;
    while (1)
    {
        char *ptr = inet_ntoa(addr->sin_addr);
        printf("addr1: %s\n", ptr);
    }
    return NULL;
}
void *Func2(void *p)
{
    struct sockaddr_in *addr = (struct sockaddr_in *)p;
    while (1)
    {
        char *ptr = inet_ntoa(addr->sin_addr);
        printf("addr2: %s\n", ptr);
    }
    return NULL;
}
int main()
{
    pthread_t tid1 = 0;
    struct sockaddr_in addr1;
    struct sockaddr_in addr2;
    addr1.sin_addr.s_addr = 0;
    addr2.sin_addr.s_addr = 0xffffffff;
    pthread_create(&tid1, NULL, Func1, &addr1);
    pthread_t tid2 = 0;
    pthread_create(&tid2, NULL, Func2, &addr2);
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    return 0;
}

运行结果:

这段代码试图创建两个线程,Func1 和 Func2,它们分别无限循环地打印两个 sockaddr_in 结构的 IP 地址。这两个 sockaddr_in 结构,addr1 和 addr2,被初始化为具有特定的 sin_addr.s_addr 值。

addr1.sin_addr.s_addr 被初始化为 0,这在 IPv4 地址中通常表示一个未指定的地址,或者说是无效的地址。

addr2.sin_addr.s_addr 被初始化为 0xffffffff,这在 IPv4 地址中通常表示广播地址。

然而,代码中有一些需要注意的地方:

  1. inet_ntoa的静态缓冲区inet_ntoa 函数使用静态缓冲区来存储转换后的字符串。这意味着如果两个线程同时调用 inet_ntoa,它们可能会覆盖彼此的缓冲区,导致不可预测的结果。因此,在多线程环境中使用 inet_ntoa 是不安全的。
  2. 无限循环:两个线程都包含一个无限循环,这会导致程序永远不会退出,除非被外部因素(如用户终止)中断。
  3. pthread_join:虽然代码中包含了 pthread_join 调用,但由于线程中的无限循环,这些调用实际上永远不会返回,因此 main 函数也永远不会结束。

测试这段代码时,你会看到两个线程分别不停地打印出相同的 IP 地址字符串,但由于 inet_ntoa 的问题,这些字符串可能会被互相覆盖,导致输出变得混乱。

此外,具体的输出取决于操作系统的具体实现和线程调度的行为。在某些情况下,你可能会看到 addr1 和 addr2 交替出现,而在其他情况下,你可能会看到某个地址连续出现多次,然后被另一个地址覆盖。

总的来说,这段代码并不是一个好的示例,因为它在多线程环境中不正确地使用了 inet_ntoa,并且包含了无限循环,这会导致程序行为不可预测且难以管理。

如果你需要在多线程环境中处理 IP 地址,建议使用更安全的函数,如 inet_ntop,并确保正确管理线程的生命周期和同步。

3.简单TCP的echo服务器(待更新)

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

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

相关文章

GitHub git push超过100MB大文件失败(write error: Broken pipe)完美解决

问题 在使用git push推送大文件&#xff08;超过了100MB&#xff09;到GitHub远程仓库时提示异常&#xff0c;异常信息如下&#xff1a; fatal: sha1 file <stdout> write error: Broken pipe fatal: the remote end hung up unexpectedly 通过查阅了一些资料&#xff0c…

langchain + azure chatgpt组合配置并运行

首先默认你已经有了azure的账号。 最重要的是选择gpt-35-turbo-instruct模型、api_version&#xff1a;2023-05-15&#xff0c;就这两个参数谷歌我尝试了很久才成功。 我们打开https://portal.azure.com/#home&#xff0c;点击更多服务&#xff1a; 我们点击Azure OpenAI&#…

Mysql密码修改问题

docker安装mysql&#xff0c;直接拉取镜像&#xff0c;挂载关键目录即可启动&#xff0c;默认3306端口。此时无法直接连接&#xff0c;需要配置密码。docker进入mysql容器中 docker exec -it mysql bash #mysq是容器名称&#xff0c;也可以用容器id通过修改mysql的配置进行免密…

腾讯云服务器4核8g配置怎么样?能用来干什么?

腾讯云4核8G服务器多少钱&#xff1f;腾讯云4核8G轻量应用服务器12M带宽租用价格646元15个月&#xff0c;活动页面 txybk.com/go/txy 活动链接打开如下图所示&#xff1a; 腾讯云4核8G服务器优惠价格 这台4核8G服务器是轻量应用服务器&#xff0c;详细配置为&#xff1a;轻量4核…

Prometheus+grafana环境搭建rabbitmq(docker+二进制两种方式安装)(二)

搭建完Prometheusgrafana基础环境后参见&#xff1a;Prometheusgrafana环境搭建方法及流程两种方式(docker和源码包)(一)-CSDN博客&#xff0c;对我本地的一些常用法人服务进行一个监控。基本都可以根据官方文档完成搭建&#xff0c;因为docker和二进制方式安装各有优缺点。 d…

5:数据结构--5.1:线性结构,5.2:数组与矩阵

转上一节&#xff1a; http://t.csdnimg.cn/M9Zdphttp://t.csdnimg.cn/M9Zdp 课程内容提要&#xff1a; 5&#xff1a;知识点考点详解 5.1&#xff1a;线性结构 考点1:线性表 1&#xff1a;线性表 顺序表&#xff1a;数据在内存中紧邻。 (1)顺序存储方式&#xff1a;数…

iOS-App:App Store新的审核政策,在应用隐私清单中声明和解释使用特定API的原因

App Store新的审核政策&#xff0c;在应用隐私清单中声明和解释使用特定API的原因 设备/引擎&#xff1a;Mac&#xff08;11.6&#xff09;/Mac Mini 开发工具&#xff1a;终端 开发需求&#xff1a;苹果官方邮件通知&#xff0c; App Store新的审核政策&#xff0c;在应用隐…

linux清理缓存垃圾命令和方法介绍

在Linux系统中&#xff0c;清理缓存和垃圾文件可以通过多种方法完成&#xff0c;这些方法旨在释放磁盘空间、提高系统性能。以下是一些常用的方法&#xff0c;结合了搜索结果中的信息&#xff1a; 1. 使用sync和echo命令清除RAM缓存和交换空间1 清除页面缓存&#xff08;Page …

【原创】springboot+vue校园疫情防控管理系统设计与实现

个人主页&#xff1a;程序猿小小杨 个人简介&#xff1a;从事开发多年&#xff0c;Java、Php、Python、前端开发均有涉猎 博客内容&#xff1a;Java项目实战、项目演示、技术分享 文末有作者名片&#xff0c;希望和大家一起共同进步&#xff0c;你只管努力&#xff0c;剩下的交…

Redis中的复制功能(四)

复制的实现 步骤2:建立套接字连接 在SLAVEOF命令执行之后&#xff0c;从服务器将根据命令所设置的IP地址和端口&#xff0c;创建连向主服务器的套接字连接&#xff0c;如图所示。如果从服务器创建的套接字能成功连接(connect)到主服务器&#xff0c;那么从服务器将为这个套接…

数据结构进阶篇 之 【交换排序】(冒泡排序,快速排序递归、非递归实现)详细讲解

当你觉的自己不行时&#xff0c;你就走到斑马线上&#xff0c;这样你就会成为一个行人 一、交换排序 1.冒泡排序 BubbleSort 1.1 基本思想 1.2 实现原理 1.3 代码实现 1.4 冒泡排序的特性总结 2.快速排序 QuickSort 2.1 基本思想 2.2 递归实现 2.2.1 hoare版 2.2.2 …

ros小问题之rosdep update time out问题

在另外一篇ROS 2边学边练系列的文章里有写碰到这种问题的解决方法&#xff08;主要参考了其他博主的文章&#xff0c;只是针对ROS 2做了些修改调整&#xff09;&#xff0c;此处单拎出来方便查找。 在ROS 2中执行rosdep update时&#xff0c;报出如下错误&#xff1a; 其实原因…

数字乡村创新实践探索:科技赋能农业现代化与乡村治理体系现代化同步推进

随着信息技术的飞速发展&#xff0c;数字乡村作为乡村振兴的重要战略方向&#xff0c;正日益成为推动农业现代化和乡村治理体系现代化的关键力量。科技赋能下的数字乡村&#xff0c;不仅提高了农业生产的效率和品质&#xff0c;也为乡村治理带来了新的机遇和挑战。本文旨在探讨…

2024年腾讯云4核8G服务器性能可以满足哪些使用场景?

腾讯云4核8G服务器多少钱&#xff1f;腾讯云4核8G轻量应用服务器12M带宽租用价格646元15个月&#xff0c;活动页面 txybk.com/go/txy 活动链接打开如下图所示&#xff1a; 腾讯云4核8G服务器优惠价格 这台4核8G服务器是轻量应用服务器&#xff0c;详细配置为&#xff1a;轻量4核…

基于Python3的数据结构与算法 - 22 贪心算法

一、贪心算法 贪心算法&#xff08;又称贪婪算法&#xff09;是指&#xff0c;在对问题求解时&#xff0c;总是做出在当前看来是最好的选择。也就是说&#xff0c;不从整体最优上加以考虑&#xff0c;它所做出的是在某种意义上的局部最优解。贪心算法并不会保证会得到最优解&a…

Scala第二十章节(Akka并发编程框架、Akka入门案例、Akka定时任务代码实现、两个进程间通信的案例以及简易版spark通信框架案例)

Scala第二十章节 章节目标 理解Akka并发编程框架简介掌握Akka入门案例掌握Akka定时任务代码实现掌握两个进程间通信的案例掌握简易版spark通信框架案例 1. Akka并发编程框架简介 1.1 Akka概述 Akka是一个用于构建高并发、分布式和可扩展的基于事件驱动的应用工具包。Akka是…

深入浅出 -- 系统架构之微服务架构

1.1 微服务的架构特征&#xff1a; 单一职责&#xff1a;微服务拆分粒度更小&#xff0c;每一个服务都对应唯一的业务能力&#xff0c;做到单一职责 自治&#xff1a;团队独立、技术独立、数据独立&#xff0c;独立部署和交付 面向服务&#xff1a;服务提供统一标准的接口&…

【C语言】【Leetcode】【递归】22. 括号生成

文章目录 题目思路代码实现 题目 链接: https://leetcode.cn/problems/generate-parentheses/description/ 思路 我们可以通过回溯递归算法求解 如果左括号数量不大于n&#xff0c;我们可以放一个左括号。 如果右括号数量小于左括号的数量&#xff0c;我们可以放一个右括号…

数据库的介绍分类作用特点

目录 1.概述 2.分类 2.1.关系型数据库 2.2.非关系型数据库 2.3.分布式数据库 ​​​​​​​2.4.云数据库 3.作用 4.特点 5.应用举例 5.1.MySQL ​​​​​​​5.1.1.作用 ​​​​​​​5.1.2.特点 ​​​​​​​5.1.3.应用案例 ​​​​​​​5.2.达梦 ​​​…

十分钟掌握在 PyTorch 中构建一个深度神经网络,基本组件、步骤和代码实现,从导入模块和定义网络结构到训练和评估网络性能。

🍉 CSDN 叶庭云:https://yetingyun.blog.csdn.net/ 深度神经网络(Deep Neural Networks, DNNs),也被称为人工神经网络(Artificial Neural Networks,ANNs),已成为当今机器学习任务中最流行、最成功的方法之一。这些网络能够表示数据中的复杂关系,并在图像分类、自然…