【Linux网络编程三】Udp套接字编程(简易版服务器)

【Linux网络编程三】Udp套接字编程(简易版服务器)

  • 一.创建套接字
  • 二.绑定网络信息
    • 1.构建通信类型
    • 2.填充网络信息
      • ①网络字节序的port
      • ②string类型的ip地址
    • 3.最终绑定
  • 三.读收消息
    • 1.服务器端接收消息recvfrom
    • 2.服务器端发送消息sendto
    • 3.客户端端发送消息sendto
    • 4.客户端端接收消息recvfrom
  • 四.关于绑定ip与port细节
  • 五.客户端不需要主动绑定
  • 六.客户端/服务器端代码

UDP套接字编程:
网络通信本质是进程之间通信,所以我们需要两个进程来网络通信,假设一个为服务器进程,一个为客户端进程演示。

一.创建套接字

在这里插入图片描述
socket()接口可以用来创建套接字,它总共有三个参数。

第一个参数domain,表示通信的类型,是使用网络通信还是本地通信由用户选择,当传入AF_INET/AF_INET6时表示使用网络通信。
第二个参数type,表示套接字的类型,是TCP呢还是UDP呢。如果传递SOCK_DGRAM表示是UDP,如果传递的SOCK_STREAM表示是TCP.
第三个参数protocol,表示协议,默认为0.

创建套接字成功后会返回一个文件描述符sockfd,也就是创建套接字的本质就是打开一个文件!
在这里插入图片描述

服务器端进程在创建完套接字后,该做什么呢?该套接字(文件)是属于你服务器进程的,然后呢?假设客户端也打开一个套接字(文件),这两个套接字文件都是属于同一个,也就是满足了进程间通信的前提条件:能看到一个共享资源。
而两个进程看到同一份资源后,那么该如何准确的发送给对方呢?因为可能打开这个套接字文件的进程有很多。所以接下来就是两个进程需要知道各自对方能够唯一标识自己的ip地址和端口号等网络信息。这样才能够准确的将数据从客户端进程发送给服务器进程。

 // 1.创建udp套接字,本质就是打开网络套接字文件
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0) // 表示创建失败
        {
            lg(Fatal, "socket create error,socket: %d", _sockfd);
            exit(SOCKET_ERR); // 创建失败之间退出
        }

二.绑定网络信息

套接字创建成功后,就相当于打开了一个文件,该文件是就两个进程通信的共享资源,不过要想准确通信,还需要知道各自进程的ip地址和端口号,这样往文件里传输数据时,对端才能准确接收到,也就是这个文件需要绑定一些各自进程的网络信息才能准确的传递到对端。

进程之间网络通信需要先绑定端口号,这里创建完套接字后,就需要让该进程的端口号与套接字绑定。这样对端的进程才能找到这个进程
在这里插入图片描述
第一个参数就是创建的套接字,也就是文件对象
第二个参数就是要绑定的该进程的网络信息,包括端口号,ip地址等
第三个参数是网络信息结构体对象的大小
这个网络信息结构体对象要求传的是统一的接口,但是你实际使用什么类型的网络通信,你就定义什么类型,然后传递时强转即可。

 // 在绑定套接信息之前,需要先将对应的结构体对象填充完毕sock_addr
        struct sockaddr_in local;                       // 网络通信类型
        bzero(&local, sizeof(local));                   // 将内容置为0
        local.sin_family = AF_INET;                     // 网络通信类型
        local.sin_port = htons(_port);                  // 网络通信中,端口号需要不断发送,所以需要符合网络字节序,主机--->网络字节序
        local.sin_addr.s_addr = inet_addr(_ip.c_str()); // 需要将string类型的ip转换成int类型,并且还需要满足网络字节序的要求
        socklen_t len = sizeof(local);
        // 以上只是将要绑定的信息填充完毕,套接字(网络文件)而还没有绑定套接信息

1.构建通信类型

比如如果我要网络通信,那么就需要定义一个sockaddr_in结构体对象。
在这里插入图片描述
该结构体对象里有三个需要初始化的参数:
1.sin_family:要使用的通信类型
2.sin_port:该进程的端口号
3.sin_addr:该进程的ip地址

sin_addr这个结构体对象里面只有一个参数,s_addr这个也就是真正的ip地址。

2.填充网络信息

①网络字节序的port

在给套接字绑定网络信息之前,需要将网络信息给构建好,就比如端口号,我们需要将当前进程的端口号填充到sockaddr_in这个结构体对象里。
不过端口号在网络通信中,是要不但的来回发送的,不管是客户端,还是服务器端,两个进程通信就必须知道对方的端口号。
所以端口号是需要发送到网络里的,所以在填充时,必须是网络字节序。
在这里插入图片描述
也就是主机转网络字节序

②string类型的ip地址

用户一般喜欢用string类型的ip地址类型,这样比较好显示。
但是系统里的ip是uint16_t类型的,所以我们在填充初始化时.
【要求1】首先需要将string类型的参数转换成uint16_t类型。
在这里插入图片描述

【要求2】其次ip地址也需要发送到网络里的,所以也必须是网络字节序。
在这里插入图片描述
系统里给了我们相关的接口:inet_addr(char*cp)
在这里插入图片描述

它就是可以将string类型的数据转换成uint16_t类型,并且将主机字节序转换成网络字节序。

3.最终绑定

在这里插入图片描述

  if (bind(_sockfd, (const struct sockaddr *)&local, len) < 0) // 绑定失败
        {
            lg(Fatal, "bind sockfd error,errno:%d,err string:%s", errno, strerror(errno));
            exit(BIND_ERR);
        }
        lg(Info, "bind sockfd success,errno:%d,err string:%s", errno, strerror(errno)); // 绑定成功

三.读收消息

1.服务器端接收消息recvfrom

一般客户端进程对服务器进程发送消息,服务器进程接收客户端发送的消息。那么服务器进程如何接收客户端发送的消息呢?
服务器进程是从套接字接收消息,也就是该进程创建的文件里接收。
在这里插入图片描述
不过服务器除了能够接收到消息,还需要知道是谁给它发送的消息,这样它才可以将消息再发送回去。
所以就需要一个sockaddr_in结构体对象,作为输出型参数,将发送端的网络信息存储下来。也就是将客户端的网络信息带出来。

所以recvfrom的功能
1.除了接收到对端发送的消息内容
2.还可以知道对端的网络消息。知道是谁发送过来的。

            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            // 服务器接收到消息,它还需要知道谁给它发送的,为了后续将应答返回过去
            // 利用一个输出型参数,将对方的网络信息填充到里面
            ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&client, &len);
            if (n < 0)
            {
                lg(Warning, "recvfrom sockfd err,errno: %d, err string %s", errno, strerror(errno));
                continue;
            }
            // 读取成功,将网络文件里的消息读取到buffer里
            buffer[n] = 0; // 字符串形式

2.服务器端发送消息sendto

服务器一般只要用来接收其他客户端的消息,然后加工处理,再将数据发送回去,所以服务器将数据再发送回客户端,也就是往套接字里发送消息,而要发送的客户端网络信息,刚好被存储在输出型参数里。因为客户但是主动发送消息的,服务器一定是先收到客户端发送的消息,然后会将客户端的网络消息存储起来,再根据客户端网络信息发送回去。
在这里插入图片描述

 // 3.将应答发送回去
// 发送给谁呢?服务器知道吗?服务器知道!因为在接收消息时,服务器就用一个输出型参数,将客户端的网络消息保存下来了
sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, (const struct sockaddr *)&client, len);

因为服务器端在接收客户端发送的消息时,还会保存客户端的网络信息,所以如果服务器想发送消息给客户端是很容易的。

3.客户端端发送消息sendto

客户端将消息发送给服务器端,该怎么发送呢?通过套接字(文件)发送给服务器,发送时需要服务器端的网络信息,比如ip地址端口号等,这样客户端才能准确的发送给该服务器端。
在这里插入图片描述
在这里插入图片描述

//构建服务器端的网络信息
   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_addr.s_addr = inet_addr(serverip.c_str()); // 将string类型转换成int类型,并且是网络字节序
    server.sin_port = htons(serverport);
    socklen_t len = sizeof(server);
    
    // 1.创建套接字---本质就是打开网络文件
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        cout << "socket create err" << endl;
        return 1;
    }
    // 创建成功
    // 2.需要绑定吗?系统会给我自动绑定,首次发送的时候就会绑定


    char outbuffer[1024];
    string message;
    getline(cin, message);
      //1.发送给服务器
    sendto(sockfd, message.c_str(), message.size(), 0, (const struct sockaddr *)&server, len);

4.客户端端接收消息recvfrom

客户端要想接收服务器发送回来的消息,只需要读取套接字里的消息即可。利用recvfrom接口读取客户端的套接字。
不过还需要定义一个结构体对象用来保存服务器端的网络信息,虽然客户端已经知道,但是接口要求,所以必须定义。

        struct sockaddr_in temp;
        socklen_t l=sizeof(temp); 
        //2.接收服务器的应答
        ssize_t s=recvfrom(sockfd,outbuffer,1023,0,(struct sockaddr*)&temp,&l);

四.关于绑定ip与port细节

【细节1】当服务器端绑定ip时,如果ip地址是’0.0.0.0"则表示任意ip地址绑定。
就表示不管客户端发送时目的ip是多少,只要消息发送到服务器的主机上,那么都可以接收,并将端口号往上发送。

也就是只要是发送到我这台主机上的报文,那么就会忽略到该报文的目的ip地址是多少,只看端口号。
这就表示任意ip地址绑定。相当于一种动态绑定。可以接收到所有发送到我这台主机上的报文。并往上传递。
在这里插入图片描述
还要注意在云服务器上,公用ip是不能随意绑定的,无法直接绑定,不管是TCP还是UDP。除非是127.0.0.1这个ip地址是专门用来检测服务器和客户端的。其他的不要瞎绑定。但是在虚拟机上可以。
【细节2】端口号不是随意绑定的,有些是已经被固定使用的,【0,1023】是属于系统内定的端口号,一般要有固定的应用层协议使用。
所以我们最好使用1023后面的。

【总结】
如果服务器端的ip地址默认是0的话,那么我们只需要知道服务器端的端口号即可。在这里插入图片描述

五.客户端不需要主动绑定

在这里插入图片描述
所以客户端是不需要显示的绑定相关的端口号和ip地址的。操作系统会帮它自动绑定。
本质原因是用户不关心客户端的端口号和ip地址等网络信息,所以不需要显示绑定。

但是服务器端必须主动绑定端口号!为什么呢?
因为用户关心服务器的端口号,必须知道服务器端的端口号。不然无法找到服务器端。
在这里插入图片描述

六.客户端/服务器端代码

【客户端】

#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;

#include <iostream>
#include <strings.h>
#include <sys/types.h>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace std;
// 客户端
void Usage(std::string proc)
{
    std::cout << "\n\r./Usage: " << proc << " serverip serverport\n"
              << endl;
}
// 启动客户端时要求是: ./Client 服务器ip 服务器port
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_addr.s_addr = inet_addr(serverip.c_str()); // 将string类型转换成int类型,并且是网络字节序
    server.sin_port = htons(serverport);
    socklen_t len = sizeof(server);
    
    // 1.创建套接字---本质就是打开网络文件
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        cout << "socket create err" << endl;
        return 1;
    }
    // 创建成功
    // 2.需要绑定吗?系统会给我自动绑定,首次发送的时候就会绑定

    // 3.往服务器的套接字里发送消息--需要知道服务器的ip和端口号,目的ip和目的port,将ip和port填入结构体对象里
   

    char outbuffer[1024];
    string message;
    while (true)
    {
        cout<<"Please enter@ ";
        getline(cin, message);
        //1.发送给服务器
        sendto(sockfd, message.c_str(), message.size(), 0, (const struct sockaddr *)&server, len);

        struct sockaddr_in temp;
        socklen_t l=sizeof(temp); 
        //2.接收服务器的应答
        
        ssize_t s=recvfrom(sockfd,outbuffer,1023,0,(struct sockaddr*)&temp,&l);
        if(s>0)
        {
        //接收成功
        outbuffer[s]=0;
        cout<<outbuffer<<endl;
        }
        
    }
    close(sockfd);
    return 0;
}

【服务器端】

@ -0,0 +1,103 @@
#pragma once

#include "Log.hpp"
#include <iostream>
#include <string>
#include <strings.h>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
std::string defaultip = "0.0.0.0";
uint16_t defaultport = 8080;
Log lg; // 日志,默认往显示屏打印
typedef std::function<std::string(const std::string&)> func_t;//相当于定义了一个函数指针
//返回值是string类型,函数参数也是string类型,利用函数回调的方法,将服务器端对数据的处理操作进行分离,由上层传递的函数来决定如何处理
enum
{
    SOCKET_ERR = 1,
    BIND_ERR
};
class Udpserver
{
public:
    Udpserver(const uint16_t &port = defaultport, std::string &ip = defaultip) : _sockfd(0), _port(port), _ip(ip)
    {
    }
    void Init()
    {
        // 1.创建udp套接字,本质就是打开网络套接字文件
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0) // 表示创建失败
        {
            lg(Fatal, "socket create error,socket: %d", _sockfd);
            exit(SOCKET_ERR); // 创建失败之间退出
        }
        // 创建成功
        lg(Info, "socket create success,socket: %d", _sockfd);
        // 2.绑定服务器的套接信息,比如ip和端口号
        // 在绑定套接信息之前,需要先将对应的结构体对象填充完毕sock_addr
        struct sockaddr_in local;                       // 网络通信类型
        bzero(&local, sizeof(local));                   // 将内容置为0
        local.sin_family = AF_INET;                     // 网络通信类型
        local.sin_port = htons(_port);                  // 网络通信中,端口号需要不断发送,所以需要符合网络字节序,主机--->网络字节序
        local.sin_addr.s_addr = inet_addr(_ip.c_str()); // 需要将string类型的ip转换成int类型,并且还需要满足网络字节序的要求
        socklen_t len = sizeof(local);
        // 以上只是将要绑定的信息填充完毕,套接字(网络文件)而还没有绑定套接信息

        if (bind(_sockfd, (const struct sockaddr *)&local, len) < 0) // 绑定失败
        {
            lg(Fatal, "bind sockfd error,errno:%d,err string:%s", errno, strerror(errno));
            exit(BIND_ERR);
        }
        lg(Info, "bind sockfd success,errno:%d,err string:%s", errno, strerror(errno)); // 绑定成功
    }
    void Run(func_t func) // 服务器是一旦启动不会退出,服务器接收消息,并发送答应
    {
        // 1.接收信息
        char buffer[SIZE];

        while (true)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            // 服务器接收到消息,它还需要知道谁给它发送的,为了后续将应答返回过去
            // 利用一个输出型参数,将对方的网络信息填充到里面
            ssize_t n = recvfrom(_sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr *)&client, &len);
            if (n < 0)
            {
                lg(Warning, "recvfrom sockfd err,errno: %d, err string %s", errno, strerror(errno));
                continue;
            }
            // 读取成功,将网络文件里的消息读取到buffer里
            buffer[n] = 0; // 字符串形式
            
            // 2.加工处理
            
            // std::string info = buffer;
            // std::string echo_string  = "server echo# " + info;
            std::string info=buffer;
            std::string echo_string=func(info);
            //将接收的信息由外层函数进行处理
           
            

            // 3.将应答发送回去
            // 发送给谁呢?服务器知道吗?服务器知道!因为在接收消息时,服务器就用一个输出型参数,将客户端的网络消息保存下来了
            sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, (const struct sockaddr *)&client, len);
        }
    }
    ~Udpserver()
    {
        if (_sockfd > 0)
            close(_sockfd);
    }

private:
    int _sockfd;     // 套接字文件描述符
    std::string _ip; // 我们习惯用string类型的ip地址
    uint16_t _port;  // 服务器进程的端口号
};


#include "Udpserver.hpp"
#include <memory>
#include <cstdio>
#include <stdlib.h>
// "120.78.126.148" 点分十进制字符串风格的IP地址
std::string handler(const std::string &info)
{ 
   std::string res="get a message: ";
   res+=info;
   std::cout<<res<<std::endl;
   return res;//最后将处理的数据返回回去
}


#include "Udpserver.hpp"
#include <memory>
#include <cstdio>

void Usage(std::string proc)
{
    std::cout<<"\n\rUsage: "<<proc<<" port[1024+]\n"<<std::endl;
}
//服务器进程,启动时,按照./Udpserver+port的形式传递
int main(int args,char* argv[])
{
    if(args!=2)
    {
         Usage(argv[0]);
         exit(0);
    }

    uint16_t port=std::stoi(argv[1]);
    std::unique_ptr<Udpserver> svr(new Udpserver(port));//首先创建一个服务器对象指针
    //智能指针,用一个UdpServer指针来管理类对象
    svr->Init();//初始化服务器
    
    svr->Run(handler);//启动服务器
    return 0;
} 

在这里插入图片描述

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

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

相关文章

Python使用分治算法作归并排序

对于分治算法的一个较为常规的应用中&#xff0c;归并排序是一个使用分治算法的排序方式。给定一个随机排序的数组&#xff0c;我们要将其元素按照升序或者降序的方式进行排序&#xff0c;可以设想到这样的一种算法&#xff0c;如果一个数组的上半部分和下半部分已经排好序&…

Vue打包Webpack源码及物理路径泄漏问题解决

修复前&#xff1a; 找到vue.config.js文件&#xff0c;在其中增加配置 module.exports {productionSourceMap: false,// webpack 配置configureWebpack: {devtool: false,}}其中打包的物理路径泄露我这边试了好多次&#xff0c;发现只有打包的时候NODE_ENVproduction 才能保…

JSP和JSTL板块:第三节 JSP四大域对象 来自【汤米尼克的JAVAEE全套教程专栏】

JSP和JSTL板块&#xff1a;第三节 JSP四大域对象 一、page范围二、request范围三、session范围四、application范围 在服务器和客户端之间、各个网页之间、哪怕同一个网页之内&#xff0c;总是需要传递各种参数值&#xff0c;这时JSP的内置对象就是传递这些参数的载具。内置对象…

Iceberg从入门到精通系列之二十三:Spark查询

Iceberg从入门到精通系列之二十三&#xff1a;Spark查询 一、使用 SQL 查询二、使用 DataFrame 进行查询三、Time travel四.Incremental read五、检查表六、History七、元数据日志条目八、Snapshots九、Files十、Manifests十一、Partitions十二、所有元数据表十三、参考十四、使…

泰克示波器(TBS2000系列)触发功能使用讲解——边沿触发

# Trigger区域 触发区域用于对触发功能进行配置。示波器的触发功能用于采集&#xff08;Acquire&#xff09;那些在瞬间出现的信号&#xff0c;便于我们分析观察&#xff0c;此时可以当做逻辑分析仪使用。触发区域按钮包括&#xff1a;menu、Level\Force Trig三个。 目录 1.1 …

【机器学习 深度学习】卷积神经网络简述

&#x1f680;个人主页&#xff1a;为梦而生~ 关注我一起学习吧&#xff01; &#x1f4a1;专栏&#xff1a;机器学习 欢迎订阅&#xff01;相对完整的机器学习基础教学&#xff01; ⭐特别提醒&#xff1a;针对机器学习&#xff0c;特别开始专栏&#xff1a;机器学习python实战…

telnet笔记

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、场景二、介绍1.测试端口2.访问百度3. 简单的爬虫 前言 最近telnet命令用的比较多&#xff0c;所以记录一下。 一、场景 ping应该是大家最常用的命令&…

unity角色触摸转向

1、挂载脚本到角色的父物体A上 2 、以屏幕左边的触摸为移动&#xff0c;右边为转向操作 3、加载角色时&#xff0c;将角色的父物体设置为A&#xff0c;须将角色的位置和角度置0 using System; using System.Collections; using System.Collections.Generic; using UnityEngin…

python之组合数据类型-字典dict

字典 字典的定义与特点操作字典创建字典字典的增删改查添加键值对删除键值对修改键值对访问元素 遍历字典 嵌套 字典的定义与特点 字典&#xff1a;字典是一系列键值对&#xff0c;是一种无序的数据集合&#xff0c;它是通过键来访问的&#xff0c;而不是索引 字典的特点&#…

【Go语言成长之路】安装Go

文章目录 安装Go一、下载Go语言安装包二、删除以前安装的Go版本三、添加/usr/local/go/bin到环境变量内四、确认安装成功 安装Go Note: 这里只演示安装Linux版本的Go&#xff0c;若为其它版本&#xff0c;请按照官网的安装教程进行安装即可。 一、下载Go语言安装包 ​ 在浏览…

Unity | Spine动画记录

https://blog.csdn.net/linshuhe1/article/details/79792432 https://blog.csdn.net/winds_tide/article/details/128925407 1.需要的三个文件 通常制作好的 Spine 动画导出时会有三个文件&#xff1a; .png 、.json 和 .atlas&#xff1a; skeleton-name.json 或 skeleton-…

Blender教程(基础)-面的法向-12

一、准备 新建如下图所示立方体演示面的法向 默认法向方向 二、显示法向 再菜单栏右上角、找到网络编辑模式&#xff0c;最下面的显示发法线打勾&#xff0c;如下图所示&#xff0c;出现的浅蓝色线条就是代表法向方向。 调整大小显示 三、正面 再显示叠加层菜单下找到面…

pytorch调用多个gpu训练,手动分配gpu以及指定gpu训练模型的流程以及示例

torch.device("cuda" if torch.cuda.is_available() else "cpu") 当使用上面的这个命令时&#xff0c;PyTorch 会检查系统是否有可用的 CUDA 支持的 GPU。如果有&#xff0c;它将选择默认的 GPU&#xff08;通常是第一块&#xff0c;即 “cuda:0”&#xf…

win10重装Ubuntu22.04安装报错复盘

目录 一&#xff1a;补充启动盘制作 二&#xff1a;错误信息[0xC0030570] The file or directory is corrupted and unreadable. 三&#xff1a;ubuntu重装步骤&#xff1a; 四&#xff1a;磁盘冗余阵列 五&#xff1a;尝试将SCS11(2,0.0), 第1分区(sda)设备的一个vfat文…

获取指定进程中的数据

此文章是对《打印指定进程中的数据》的扩展&#xff0c;增加了用户空间的控制接口&#xff0c;可以实现从用户空间发送指令&#xff0c;指定要获取数据的进程id和内存地址&#xff0c;然后将取到的数据返回给用户空间。 下面是驱动部分的代码 #include <linux/module.h>…

2024年混合云:趋势和预测

混合云环境对于 DevOps 团队变得越来越重要&#xff0c;主要是因为它们能够弥合公共云资源的快速部署与私有云基础设施的安全和控制之间的差距。这种环境的混合为 DevOps 团队提供了灵活性和可扩展性&#xff0c;这对于大型企业中的持续集成和持续部署 (CI/CD) 至关重要。 在混…

react+ProComponents简单实现表格

文章目录 使用ProComponents的原因 一般后台管理系统&#xff0c;大部分页面功能都是列表和表单的形式。 即便使用了组件、等&#xff0c;依旧需要写大量高度重复性的代码&#xff0c;比如列表页通常会有 筛选栏、操作栏、表格区域、和分页栏四个部分&#xff0c; 新增/编辑页…

【JavaEE Spring】Spring事务和事务传播机制

Spring事务和事务传播机制 1. 事务回顾1.1 什么是事务?1.2 为什么需要事务?1.3 事务的操作 2. Spring 中事务的实现2.1 Spring编程式事务(了解)2.2 Spring声明式事务Transactional 3. Transactional 详解3.1 rollbackFor3.2 事务隔离级别3.2.1 MySQL事务隔离级别(回顾)3.2.2 …

avast网页随机密码生成器

随机密码生成器 | 告别 12345 | Avast 可以生成随机密码 按需调整

C++ 新特性 构造函数

1.委托构造函数 委托构造函数出现的意义: 委托构造初始化能够减少代码的冗余的问题 使代码变得简洁 明朗 现在大家来看一个例子: 本代码采用了普通函数的构造方法 看起来简洁一些 但是并没有从实际上解决问题 尤其是对于 复杂数据类型的存储 比如String类型 已经发生了默认的…