Linux--Socket 编程 UDP(简单的回显服务器和客户端代码)

news2024/9/22 5:41:34

目录

0.上篇文章

1.V1 版本 - echo server

1.1认识接口

1.2实现 V1 版本 - echo server(细节)

1.3添加的日志系统(代码)

 1.4 解析网络地址

 1.5 禁止拷贝逻辑(基类)

1.6 服务端逻辑 (代码)

1.7客户端逻辑(代码)

1.8 用例测试 


0.上篇文章

Linux--Socket编程预备-CSDN博客

1.V1 版本 - echo server


1.1认识接口

1.创建socket

        它允许不同计算机或同一计算机上的不同进程之间进行数据交换。Socket编程基于客户端-服务器模型,其中服务器监听来自客户端的连接请求,并在连接建立后交换数据。

  1. socket():创建一个新的socket。
    • 原型:int socket(int domain, int type, int protocol);
    • 参数:
      • domain:指定socket使用的协议族,如AF_INET(IPv4)或AF_INET6(IPv6)。
      • type:指定socket的类型,如SOCK_STREAM(TCP)或SOCK_DGRAM(UDP)。
      • 此处我们先对 TCP(Transmission Control Protocol 传输控制协议)有一个直观的认识;
        后面我们再详细讨论 TCP 的一些细节问题.
        • 传输层协议
        • 有连接
        • 可靠传输(可靠性高)
        • 面向字节流
      • 此处我们也是对 UDP(User Datagram Protocol 用户数据报协议)有一个直观的认识; 后
        面再详细讨论.
        • 传输层协议
        • 无连接
        • 不可靠传输(但操作简单)
        • 面向数据报
      • protocol:通常设置为0,表示选择给定domain和type的默认协议。

        AF_UNIX表示本地协议族,是进行本地通信的;AF_INET表示网络协议族,是进行网络通信的。

返回值概述

  • 成功时socket()函数成功执行时,会返回一个非负整数,这个整数被称为socket的文件描述符。文件描述符是一个非负整数,用于标识打开的文件、管道、socket等I/O资源。在后续的网络通信中,会使用这个文件描述符来引用和操作这个socket。
  • 失败时:如果socket()函数执行失败,它会返回-1,并设置全局变量errno以指示错误的具体原因。errno是一个由系统定义的全局变量,用于在函数调用失败时提供错误代码。

2.bind

        络服务的 bind(绑定):在网络编程中,bind 是一个系统调用,用于将套接字(socket)与一个特定的 IP 地址和端口号关联起来。这是服务器程序启动时的常见步骤,它告诉操作系统这个套接字将监听来自特定 IP 地址和端口号的连接请求。

参数说明:

1. sockfd

  • 类型int
  • 描述:这是由socket函数返回的套接字文件描述符(socket file descriptor)。它代表了创建的套接字,是后续网络通信操作的基础。

2. addr

  • 类型const struct sockaddr *
  • 描述:这是一个指向sockaddr结构体的指针,但实际上更常用的是其派生结构体如sockaddr_in(用于IPv4)或sockaddr_in6(用于IPv6)。这个结构体包含了要绑定的IP地址和端口号信息。
    • 对于sockaddr_in结构体,它至少包含以下成员:
      • sin_family:地址族,对于IPv4,此值应为AF_INET
      • sin_port:端口号,在网络字节序中。
      • sin_addr:IP地址,也是以网络字节序表示。
    • 需要注意的是,由于bind函数要求的是sockaddr类型的指针,因此在使用sockaddr_insockaddr_in6时,需要将其地址强制转换为sockaddr*类型。

3. addrlen

  • 类型socklen_t
  • 描述:这个参数指定了addr参数所指向的地址结构体的长度(以字节为单位)。由于sockaddr是一个通用结构体,其大小可能因不同的地址族而异(尽管sockaddr本身的大小是固定的,但使用其派生结构体时,实际长度会更大),因此需要通过这个参数来明确告诉bind函数应该如何处理addr参数。

3.recvfrom收消息

        这个函数允许程序从指定的套接字(socket)接收数据,并且能够获取发送数据的源地址信息。

参数说明

  • sockfd:要接收数据的套接字文件描述符。
  • buf:指向数据缓冲区的指针,用于存储接收到的数据。
  • len:缓冲区 buf 的长度,即最大可接收的数据量。
  • flags:调用标志,通常设置为 0,但在某些情况下可以指定特殊的行为(如 MSG_DONTWAIT)。
  • src_addrsrc_addr 是一个指向 sockaddr 结构(或其特定类型,如 sockaddr_in 用于 IPv4)的指针。当 recvfrom 函数被调用时,如果成功接收到数据,发送方的地址信息(包括 IP 地址和端口号)将被填充到这个结构中。这样,接收方就可以知道数据是从哪里来的,并可能基于此信息进行后续操作,比如回复发送方。
  • addrlenaddrlen 是一个指向 socklen_t 类型变量的指针。在调用 recvfrom 之前,这个变量应该被设置为 src_addr 指向的缓冲区的大小(即 sockaddr_in 或其他 sockaddr 派生类型的大小)。recvfrom 函数在成功执行后,会通过这个指针返回实际存储在 src_addr 中的地址结构的大小。这允许调用者知道有多少字节的 src_addr 被实际使用,尽管在大多数情况下,对于特定的 sockaddr 类型(如 sockaddr_in),这个大小是固定的。

返回值

  • 成功时,recvfrom 返回接收到的字节数。
  • 如果连接被对方正常关闭,返回 0。
  • 如果发生错误,返回 -1,并设置 errno 以指示错误的原因。

4.sento发消息

        用于在网络编程中发送数据,特别是与UDP(用户数据报协议)通信时。

参数说明:

  • sockfd:套接字文件描述符,表示要发送数据的套接字。
  • buf:指向要发送数据的缓冲区的指针。
  • len:要发送数据的字节数。
  • flags:发送数据的标志位,通常设置为0。
  • dest_addr:指向目的地址结构体的指针,包括目的IP地址和端口号等信息。
  • addrlen:目的地址结构体的长度。

特点:

  1. 无连接发送sendto支持无连接的发送方式,发送方不需要与接收方建立连接即可发送数据。这使得UDP协议在实时通信、视频流传输等场景中具有优势。
  2. 灵活性sendto允许发送方指定目标地址和端口号,这使得它可以向不同的接收方发送数据,而不需要为每个接收方建立单独的连接。
  3. 可靠性:虽然UDP协议本身不提供可靠性保证(如TCP的确认和重传机制),但sendto函数可以通过分片发送数据的方式,在网络环境不稳定或拥塞的情况下,尽量保证数据的完整性和准确性。

返回值:

  • 成功时sendto函数成功执行时,其返回值是成功发送的数据的字节数。这意味着如果应用程序请求发送的数据量为N字节,并且这N字节全部被成功发送,那么sendto将返回N。
  • 失败时:如果发送操作失败,sendto函数将返回-1。此时,可以通过检查全局变量errno来获取具体的错误原因。

1.2实现 V1 版本 - echo server(细节)

        该服务器就是创建好对应的套接字,和网络信息进行绑定,服务器不断地接收和发送数据。因为我们对应的服务器和客户端都在一台机器上,127.0.0.1 是一个特殊的IP地址,被称为回环地址,当你在计算机上访问 127.0.0.1 时,实际上是在与本机上的某个服务或应用程序进行通信,而不是通过网络与其他计算机通信。

        当然如果你是使用的云服务器,你当然可以使用你服务器的ip。但云服务器上,服务端不能直接(也强烈不建议)bind自己的公网ip!因为该ip是虚拟出来的,该机器上也没有这个ip。下面这个才是你机器上的ip(内网ip)

        但是bind内网ip的话,就不会往服务器上收消息了,外部无法直接访问内网。在云服务器上,ip地址一般设置为0,那如何理解呢?这可以让服务器bind任意ip。

        一般现在的服务器只有一张网卡,绑定了一个公网ip。但如果你的服务器上有很多的ip地址,一个ip1,一个ip2,(如内网ip,回环ip)而我们上层的端口号为:8888。如果你的服务器bind ip1和8888,未来你的服务器收到各种报文都是发给8888的。有ip1的也有ip2的,但服务器只会接收ip1的报文,但如果服务器bind的ip为0,就意味着不管发送的ip是谁,只要是发给端口号为:8888的,服务器都会接收!!!(在做本地测试的时候,你也可以使用公网ip向你的服务器发消息,能不能成功,就看你的云服务器是怎么设定的了)

        因此,我们在server端就不需要ip了,bind端口号为8888就行了  


1.3添加的日志系统(代码)

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

Log.hpp

#pragma once
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <ctime>
#include <cstdarg>
#include <fstream>
#include <cstring>
#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 "UNKNOWN";
        }
    }

    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;
        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 hellwrodl", x, , , );
    class Log
    {
    public:
        Log(const std::string &logfile = glogfile) : _logfile(logfile), _type(SCREEN_TYPE)
        {
        }
        void Enable(int type)
        {
            _type = type;
        }
        void FlushLogToScreen(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 FlushLogToFile(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);
            switch (_type)
            {
            case SCREEN_TYPE:
                FlushLogToScreen(lg);
                break;
            case FILE_TYPE:
                FlushLogToFile(lg);
                break;
            }
        }
        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_start(ap, format);
            char log_info[1024];
            vsnprintf(log_info, sizeof(log_info), format, ap);
            va_end(ap);
            lg._message_info = log_info;

            // 打印出来日志
            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)
};


 1.4 解析网络地址

此功能实现较为简单,请看注释:

#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);//这个端口号是随机bind的
        _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;//保存一下网络序列
};


 1.5 禁止拷贝逻辑(基类)

 禁止对象之间的拷贝,而非智能指针的,因为有很多网络相关的,这样可以避免出错:

nocopy.hpp

#pragma once
//禁止拷贝
class nocopy
{
public:
    nocopy(){}
    ~nocopy(){}
    nocopy(const nocopy&) = delete;
    const nocopy& operator=(const nocopy&) = delete;
};


1.6 服务端逻辑 (代码)

UdpServer.hpp:

以下是 UdpServer 类的主要功能和组成部分:

  1. 构造函数:接收一个可选的本地端口号参数,默认为 8888。构造函数初始化了文件描述符 _sockfd 为 -1(表示无效的文件描述符),设置了本地端口号 _localport,并将服务器运行状态 _isrunning 设置为 false

  2. InitServer 方法:用于初始化服务器。这个方法首先创建一个UDP套接字,如果创建失败,则记录一条致命错误日志并退出程序。然后,它将套接字绑定到一个本地地址和端口上。如果绑定失败,同样记录一条致命错误日志并退出程序。

  3. Start 方法:用于启动服务器。这个方法将 _isrunning 设置为 true,然后进入一个循环,不断接收来自客户端的数据,处理数据,并向客户端发送响应。如果接收数据失败,它会打印一条错误消息。

  4. 析构函数:在 UdpServer 类的实例被销毁时调用。如果文件描述符 _sockfd 是有效的(即大于 -1),则关闭该套接字。

整个类使用了日志记录功能来记录关键事件,如套接字创建和绑定成功或失败。这有助于调试和监控服务器的运行状态。

        因为实现比较简单,结合逻辑和注释,理解起来较为容易:

#pragma once

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

#include "nocopy.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"

using namespace log_ns;

static const int gsockfd = -1;
static const uint16_t glocalport = 8888;

enum
{
    SOCKET_ERROR = 1,
    BIND_ERROR//绑定失败
};

// UdpServer user("192.1.1.1", 8899);
// 一般服务器主要是用来进行网络数据读取和写入的。IO的
// 服务器IO逻辑 和 业务逻辑 解耦
class UdpServer : public nocopy//继承,禁止拷贝构造,禁止赋值,                              
{                              //禁止对象之间的拷贝,而非智能指针的
public:                        //因为有很多网络相关的,这样可以避免出错
    UdpServer(uint16_t localport = glocalport)
        : _sockfd(gsockfd),//初始化为-1
          _localport(localport),
          _isrunning(false)
    {
    }
    void InitServer()//初始化服务器
    {
        // 1. 创建socket文件
        //AF_INET表示的是网络套接(IPv4),SOCK_DGRAM表示为UDP协议 
        _sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);//系统调用,创建套接字
        if (_sockfd < 0)
        {
            LOG(FATAL, "socket error\n");//提示创建失败,致命错误,日志
            exit(SOCKET_ERROR);
        }
        // 把此次套接字所对应的文件fd也写进日志
        LOG(DEBUG, "socket create success, _sockfd: %d\n", _sockfd); // 把此次套接字所对应的文件fd也写进日志


        // 2. bind
        struct sockaddr_in local;//用于表示Internet地址,特别是IPv4地址和端口号。
        memset(&local, 0, sizeof(local));//先清空再使用
        local.sin_family = AF_INET;//表示你的套接字将使用IPv4协议。
        local.sin_port = htons(_localport);//端口号,htons主机序列转为网络序列
        // local.sin_addr.s_addr = inet_addr(_localip.c_str()); // 1. 需要4字节IP 2. 需要网络序列的IP -- 暂时
        local.sin_addr.s_addr = INADDR_ANY; // 服务器端,进行任意IP地址绑定

        int n = ::bind(_sockfd, (struct sockaddr *)&local, sizeof(local));//填充进入bind
        if (n < 0)
        {
            LOG(FATAL, "bind error\n");
            exit(BIND_ERROR);
        }
        LOG(DEBUG, "socket bind success\n");  
    }
    void Start()//启动服务器
    {
        _isrunning = true;
        char inbuffer[1024];
        while (_isrunning)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            //读取数据
           // (struct sockaddr *)&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 echo_string = "[udp_server echo] #";
                echo_string += inbuffer;//返回给客户端的内容
                sendto(_sockfd, echo_string.c_str(), echo_string.size(), 0, (struct sockaddr *)&peer, len);
            }
            else
            {
                std::cout << "recvfrom ,  error"  << std::endl;
            }
        }
    }
    ~UdpServer()
    {
        if(_sockfd > gsockfd) ::close(_sockfd);
    }

private:
    int _sockfd;//文件描述符
    //socket()函数成功执行时,会返回一个非负整数,这个整数被称为socket的文件描述符。
    uint16_t _localport;//端口号
    // std::string _localip; //不需要了
    bool _isrunning;//服务器是否在运行
};

UdpServerMain.cc:

        服务端启动程序:

#include "UdpServer.hpp"

#include <memory>

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

    EnableScreen();  
    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port); //C++14的标准
    usvr->InitServer();
    usvr->Start();
    return 0;
}


1.7客户端逻辑(代码)

UdpClientMain.cc:

  1. 参数检查:程序首先检查用户是否提供了正确的参数数量(即服务器的IP地址和端口号)。如果参数数量不正确,程序将打印出正确的使用方式并退出。

  2. 解析参数:程序从命令行参数中解析出服务器的IP地址和端口号,并将它们存储在相应的变量中。

  3. 创建套接字:程序调用socket函数创建一个UDP套接字。如果套接字创建失败,程序将打印出错误信息并退出。

  4. 填充服务器信息:程序创建一个sockaddr_in结构体来存储服务器的地址信息,包括IP地址和端口号。这些信息将用于向服务器发送数据。

  5. 主循环:程序进入一个无限循环,等待用户输入。用户输入的字符串将被发送到服务器。

    • 发送数据:程序使用sendto函数将用户输入的字符串发送到服务器。如果发送失败,程序将打印出错误信息并退出循环。

    • 接收数据:发送数据后,程序使用recvfrom函数等待并接收来自服务器的响应。如果接收到数据,程序将打印出服务器的响应。如果接收失败,程序将打印出错误信息并退出循环。

  6. 关闭套接字:当程序退出循环时(通常是因为用户输入了某些特定的命令或发生了错误),它将关闭套接字并退出。

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

// 客户端在未来一定要知道服务器的IP地址和端口号
// ./udp_client server-ip server-port
// ./udp_client 127.0.0.1 8888
int main(int argc, char *argv[])//获取服务端的ip和端口号
{
    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]);

    int sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);//和服务端一样创建
    if(sockfd < 0)
    {
        std::cerr << "create socket error" << 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);//要把主机序列转为网络序列
    // inet_addr把字符串形式的ip转为4字节
    server.sin_addr.s_addr = inet_addr(serverip.c_str());
   
    while(1)
    {
        std::string line;
        std::cout << "Please Enter# ";
        std::getline(std::cin, line);

        // std::cout << "line message is@ " << line << std::endl;
        //向服务端发消息 ,并且自动的将客户端的ip和端口号进行绑定
        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)-1, 0, (struct sockaddr*)&temp, &len);
            if(m > 0)
            {
                //收到服务器的字符串
                buffer[m] = 0;
                std::cout << buffer << std::endl;
            }
            else
            {
                std::cout << "recvfrom error" << std::endl;
                break;
            }
        }
        else
        {
            std::cout << "sendto error" << std::endl;
            break;
        }
    }

    ::close(sockfd);
    return 0;
}

1.8 用例测试 

如果你的云服务器支持通过公网ip访问,当然是可以通过网络跨机器进行交互的。那么就可以通过公网ip找到你的主机,通过端口号找到你对应的进程了。

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Leetcode—769. 最多能完成排序的块【中等】

2024每日刷题&#xff08;149&#xff09; Leetcode—769. 最多能完成排序的块 实现代码 class Solution { public:int maxChunksToSorted(vector<int>& arr) {int ans 0;int mx INT_MIN;for(int i 0; i < arr.size(); i) {mx max(arr[i], mx);if(mx i) {a…

【C++】C++应用案例-旋转图像

旋转图像的需求&#xff0c;在图片处理的过程中非常常见。我们知道对于计算机而言&#xff0c;图像其实就是一组像素点的集合&#xff0c;所以图像旋转的问题&#xff0c;本质上就是一个二维数组的旋转问题。 我们可以给定一个二维数组&#xff0c;用来表示一个图像&#xff0c…

【C++】——红黑树(手撕红黑树,彻底弄懂红黑树)

目录 前言 一 红黑树简介 二 为什么需要红黑树 三 红黑树的特性 四 红黑树的操作 4.1 变色操作 4.2 旋转操作 4.3 插入操作 4.4 红黑树插入代码实现 4.5 红黑树的删除 五 红黑树迭代器实现 总结 前言 我们之前都学过ALV树&#xff0c;AVL树的本质就是一颗平…

Oracle对比两表数据的不一致

MINUS 基本语法如下 [SQL 语句 1] MINUS [SQL 语句 2];举个例子&#xff1a; select 1 from dual minus select 2 from dual--运行结果 1-------------------------------- select 2 from dual minus select 1 from dual--运行结果 2所以&#xff0c;如果想找所有不一致的&a…

软件测试---Linux

Linux命令使用&#xff1a;为了将来工作中与服务器设备进行交互而准备的技能&#xff08;远程连接/命令的使用&#xff09;数据库的使用&#xff1a;MySQL&#xff0c;除了查询动作需要重点掌握以外&#xff0c;其他操作了解即可什么是虚拟机 通过虚拟化技术&#xff0c;在电脑…

富芮坤FR800X系列之按键检测模块设计

FR800X系列按键检测模块 读者对象&#xff1a; 本文档主要适用以下工程师&#xff1a; 嵌入式系统工程师 单片机软件工程师 IOT固件工程师 BLE固件工程师 文章目录 1.概要2.用户如何设计按键检测模块2.1 GPIO初始化2.2按键模块初始化2.3设计中断函数&#xff1a;2.4循环…

【Python面试题收录】Python编程基础练习题①(数据类型+函数+文件操作)

本文所有代码打包在Gitee仓库中https://gitee.com/wx114/Python-Interview-Questions 一、数据类型 第一题&#xff08;str&#xff09; 请编写一个Python程序&#xff0c;完成以下任务&#xff1a; 去除字符串开头和结尾的空格。使用逗号&#xff08;","&#…

【数据库】Quartz2.3 框架 数据库设计说明书

1、 Quartz表说明 2、 quartz 的触发时间的配置 1、 cron 方式&#xff1a;采用cronExpression表达式配置时间。 2、 simple 方式&#xff1a;和JavaTimer差不多&#xff0c;可以指定一个开始时间和结束时间外加一个循环时间。 3、 calendars 方式&#xff1a;可以和cron配合使…

Java-----栈

目录 1.栈&#xff08;Stack&#xff09; 1.1概念 1.2栈的使用 1.3栈的模拟实现 1.4栈的应用场景 1.5栈、虚拟机栈、栈帧有什么区别呢 1.栈&#xff08;Stack&#xff09; 1.1概念 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操…

Centos8 yum 更换源以及安装内核头文件

文章目录 一、简介二、yum 更换源三、安装内核头文件 一、简介 CentOS 是一个开源项目&#xff0c;发布了两个不同的 Linux 发行版——CentOS Stream 和 CentOS Linux 。 CentOS Stream 是即将发布的红帽企业 Linux 产品的上游开发平台。 CentOS 项目将于 2024 年 6 月 30 日…

场外期权如何报价?名义本金是什么?

今天带你了解场外期权如何报价&#xff1f;名义本金是什么&#xff1f;投资者首先需要挑选自己想要进行期权交易的沪深上市公司股票。选出股票后&#xff0c;需要将股票信息、预期的操作时间&#xff08;如期限&#xff09;、看涨或看跌的选择以及预计的交易金额等信息报给场外…

商家虚假发货行为频发,电商平台如何通过物流轨迹来监管?(内附视频号、抖音、京东的发货规则)

近年来&#xff0c;“虚假发货”问题在电商行业中日益凸显。某投诉平台数据显示&#xff0c;截至2024年7月&#xff0c;搜索“虚假发货”显示的投诉高达19万条&#xff0c;如何有效监控卖家发货的合规性与及时性、打击虚假发货行为成为电商平台的重要议题。 为了维护消费者权益…

剧透:巴黎奥运会用上了AI转播

** AI增强技术&#xff0c;让比赛画面变成电影特效。 ** 巴黎奥运会即将开幕&#xff01; 阿里云在奥运转播中应用的AI增强技术 将让比赛画面变成电影特效&#xff01; 剧透如下 &#x1f447;&#x1f3fb; 阿里云为奥运转播提供的高自由度回放“子弹时间”&#xff0c;是…

[Mysql-DDL数据操作语句]

目录 DDL语句操作数据库 库&#xff1a; 查看&#xff1a;show 创建&#xff1a;creat 删除&#xff1a;drop 使用(切换)&#xff1a;use 表&#xff1a; 查看&#xff1a;desc show 创建&#xff1a;create 表结构修改 rename as add drop modify change rename as …

cesium海洋到站提示

项目地址:Every Admin: 用于快速搭建后台管理和其他页面的项目,组件化开发,以及大屏展示. <template> <div class"topbox"> xx海洋管理 </div> <div class"selectbox"> <div class"title"> 航线列表 </div>…

了解Java虚拟机(JVM)

前言&#x1f440;~ 上一章我们介绍网络原理相关的知识点&#xff0c;今天我们浅浅来了解一下java虚拟机JVM JVM&#xff08; Java Virtual Machine &#xff09; JVM内存区域划分 方法区/元数据区&#xff08;线程共享&#xff09; 堆&#xff08;线程共享&#xff09; 虚…

Nginx 配置与优化:常见问题全面解析

文章目录 Nginx 配置与优化:常见问题全面解析一、Nginx 安装与配置问题1.1 Nginx 安装失败问题描述解决方法1.2 Nginx 配置文件语法错误问题描述解决方法二、Nginx 服务启动与停止问题2.1 Nginx 无法启动问题描述解决方法2.2 Nginx 服务无法停止问题描述解决方法三、Nginx 性能…

尚硅谷vue全家桶(vue2+vue3)笔记

Vue2 一、Vue核心 01_简介 1.特点 采用组件化模式&#xff0c;提高代码复用率、且让代码更好维护。声明式编码&#xff0c;让编程人员无需直接操作DOM&#xff08;命令式编码&#xff09;&#xff0c;提高开发效率。使用虚拟DOM优秀的Diff算法&#xff0c;尽量复用DOM节点。…

【日常记录】【JS】JS中查询参数处理工具URLSearchParams

文章目录 1. 引言2. URLSearchParams2.1 URLSearchParams 的构造函数2.2 append() 方法2.3 delete() 方法2.4 entries() 方法2.5 forEach() 方法2.6 get() 方法2.7 getAll() 方法2.8 has() 方法2.9 keys() 方法2.10 set() 方法2.11 toString() 方法2.12 values() 方法 参考链接…

Pytorch深度学习实践(5)逻辑回归

逻辑回归 逻辑回归主要是解决分类问题 回归任务&#xff1a;结果是一个连续的实数分类任务&#xff1a;结果是一个离散的值 分类任务不能直接使用回归去预测&#xff0c;比如在手写识别中&#xff08;识别手写 0 − − 9 0 -- 9 0−−9&#xff09;&#xff0c;因为各个类别…