linux操作系统网络编程套接字(实现一个udp通讯demo)

news2024/12/26 4:35:27

文章目录

  • 理解源IP地址和目的IP地址
  • 认识端口号
    • 理解 "端口号" 和 "进程ID
    • 理解源端口号和目的端口号
  • 认识TCP协议
  • 认识UDP协议
    • 什么是面向字节流和面向数据报流以及有无连接是什么意思
  • 网络字节序
  • socket编程接口
    • socket 常见API
    • sockaddr结构
    • sockaddr_in 结构
    • in_addr结构
    • 简单的UDP网络程序

理解源IP地址和目的IP地址

在IP数据包头部中, 有两个IP地址, 分别叫做源IP地址, 和目的IP地址.如果以唐僧取经为例,唐僧每次到达一个地方会说什么呢?我从东土大唐而来前往西天取经而去,那么对于唐僧来说东土大唐就是源ip地址西天就是目的ip地址。
思考: 我们光有IP地址就可以完成通信了嘛? 想象一下发qq消息的例子, 有了IP地址能够把消息发送到对方的机器上,但是还需要有一个其他的标识来区分出, 这个数据要给哪个程序进行解析?很明显只有目的ip显然是不够的我们还需要一个能够标识哪个程序的进程的东西,还是以唐僧西天取经为例他说前往西天取经可是只知道一个西天就可以了吗?我们是不是还需要知道去西天之后找谁去求取真经总不能找那灵山脚下妖怪吧。

认识端口号

端口号(port)是传输层协议的内容.

端口号是一个2字节16位的整数;
端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;
一个端口号只能被一个进程占用

其实从这里我们是可以看出来的所谓网络通信本质上其实还是进程间的通信只不过是不同主机的进程在通信,比如说你用qq发消息,本质上其实就是你手机上的qq跟对面手机上的qq在接收和发送消息。

理解 “端口号” 和 "进程ID

我们之前在学习系统编程的时候, 学习了 pid 表示唯一一个进程; 此处我们的端口号也是唯一表示一个进程. 那么这两者之间是怎样的关系?
它们的关系在于它们都是用于标识不同上下文中的实体(进程或网络应用程序),但它们在操作系统内部和网络通信中有不同的应用和含义。
有些同学可能会有疑问端口号是为了在网络通信中找到这台主机上的特定的一个进程那是不是用pid+ip也可以实现呢?其实是的,肯定是可以的,那我们为什么不去使用pid呢而是创建一个端口号呢?其实很简单,因为我们计算机在设计一个东西的时候要讲究低耦合高内聚,假如说我们使用pid来进行标识 那如果未来要改变一下pid的底层源代码那对应的我们是不是也要进行更新因此不符合低耦合高内聚。
另外, 一个进程可以绑定多个端口号; 但是一个端口号不能被多个进程绑定;

理解源端口号和目的端口号

传输层协议(TCP和UDP)的数据段中有两个端口号, 分别叫做源端口号和目的端口号. 就是在描述 “数据是谁发的, 要发给谁”;

认识TCP协议

此处我们先对TCP(Transmission Control Protocol 传输控制协议)有一个直观的认识; 后面我们再详细讨论TCP的一些细节问题.

传输层协议
有连接
可靠传输
面向字节流

认识UDP协议

此处我们也是对UDP(User Datagram Protocol 用户数据报协议)有一个直观的认识; 后面再详细讨论.

传输层协议
无连接
不可靠传输
面向数据报

什么是面向字节流和面向数据报流以及有无连接是什么意思

面向字节流(Stream-Oriented):

特点: 面向字节流是一种流式的数据传输方式,数据被视为一连串的字节,没有明确的分割单位。传输的数据流是连续的,不会有明确的消息界限。
应用场景: 典型的面向字节流的例子是TCP(Transmission Control Protocol)协议。在TCP连接中,数据被视为字节流,而不是消息的集合。应用程序可以读取任意大小的数据块,而不受底层网络数据包的限制。
面向数据报流(Message-Oriented):

特点: 面向数据报流是一种基于消息的数据传输方式,每个消息是一个独立的数据单元,有明确的边界。每个消息都被当作一个独立的数据包来处理,具有自己的标识和大小。
应用场景: 典型的面向数据报流的例子是UDP(User Datagram Protocol)协议。在UDP中,每个数据包被视为一个独立的消息,没有连接的概念,因此不保证数据的有序性和可靠性。
有无连接:

有连接(Connection-Oriented): 在有连接的通信中,通信的两端在交换数据之前需要建立一个连接。TCP是一个典型的有连接协议。在建立连接后,数据在通信的两端之间传输,最后连接被显式地关闭。
无连接(Connectionless): 在无连接的通信中,通信的两端之间没有建立持久的连接。UDP是一个典型的无连接协议。每个数据包在传输时都是独立的,通信的两端不保持持久性的连接状态。
综上所述,面向字节流和面向数据报流描述了数据在传输时的组织方式,而有无连接描述了通信的两端是否在传输前建立了连接。

网络字节序

我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?

发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.
不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可;
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换

在这里插入图片描述

这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。
例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回 ;
如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。

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 Domain
Socket. 然而, 各种网络协议的地址格式并不相同
在这里插入图片描述

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_in 结构

在这里插入图片描述

in_addr结构

在这里插入图片描述

简单的UDP网络程序

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 logmessage(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\n", leftbuffer, rightbuffer);

    //     // printf("%s", logtxt); // 暂时打印
    //     printLog(level, logtxt);
    // }
    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;
};

// int sum(int n, ...)
// {
//     va_list s; // char*
//     va_start(s, n);

//     int sum = 0;
//     while(n)
//     {
//         sum += va_arg(s, int); // printf("hello %d, hello %s, hello %c, hello %d,", 1, "hello", 'c', 123);
//         n--;
//     }

//     va_end(s); //s = NULL
//     return sum;
// }

Main.cc

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

// "120.78.126.148" 点分十进制字符串风格的IP地址

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

std::string Handler(const std::string &str)
{
    std::string res = "Server get a message: ";
    res += str;
    std::cout << res << std::endl;

    // pid_t id = fork();
    // if(id == 0)
    // {
    //     // ls -a -l -> "ls" "-a" "-l"
    //     // exec*();
    // }
    return res;
}

std::string ExcuteCommand(const std::string &cmd)
{
    // SafeCheck(cmd);

    FILE *fp = popen(cmd.c_str(), "r");
    if(nullptr == fp)
    {
        perror("popen");
        return "error";
    }
    std::string result;
    char buffer[4096];
    while(true)
    {
        char *ok = fgets(buffer, sizeof(buffer), fp);
        if(ok == nullptr) break;
        result += buffer;
    }
    pclose(fp);

    return result;
}

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

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

    std::unique_ptr<UdpServer> svr(new UdpServer(port));

    svr->Init(/**/);
    svr->Run(ExcuteCommand);

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

        // std::cout << message << std::endl;
        // 1. 数据 2. 给谁发
        sendto(sockfd, message.c_str(), message.size(), 0, (struct 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;
}

Udpserver.hpp

#pragma once

#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>
#include "Log.hpp"

// using func_t = std::function<std::string(const std::string&)>;
typedef std::function<std::string(const std::string&)> func_t;

Log lg;

enum{
    SOCKET_ERR=1,
    BIND_ERR
};

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

class UdpServer{
public:
    UdpServer(const uint16_t &port = defaultport, const std::string &ip = defaultip):sockfd_(0), port_(port), ip_(ip),isrunning_(false)
    {}
    void Init()
    {
        // 1. 创建udp socket
        sockfd_ = socket(AF_INET, SOCK_DGRAM, 0); // PF_INET
        if(sockfd_ < 0)
        {
            lg(Fatal, "socket create error, sockfd: %d", sockfd_);
            exit(SOCKET_ERR);
        }
        lg(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)
        {
            lg(Fatal, "bind error, errno: %d, err string: %s", errno, strerror(errno));
            exit(BIND_ERR);
        }
        lg(Info, "bind success, errno: %d, err 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);
            ssize_t n = recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&client, &len);
            if(n < 0)
            {
                lg(Warning, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));
                continue;
            }
            inbuffer[n] = 0;
            std::string info = inbuffer;
            std::string echo_string = func(info);
            sendto(sockfd_, echo_string.c_str(), echo_string.size(), 0, (const sockaddr*)&client, len);
        }
    }
    ~UdpServer()
    {
        if(sockfd_>0) close(sockfd_);
    }
private:
    int sockfd_;     // 网路文件描述符
    std::string ip_; // 任意地址bind 0
    uint16_t port_;  // 表明服务器进程的端口号
    bool isrunning_;
};

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

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

相关文章

Leetcode300. 最长递增子序列

Every day a Leetcode 题目来源&#xff1a;300. 最长递增子序列 解法1&#xff1a;递归 枚举 nums[i] 作为最长递增子序列的末尾元素&#xff0c;那么需要枚举 nums[j] 作为最长递增子序列的倒数第二个元素&#xff0c;其中 j<i 并且 nums[j]<nums[i]。 问题转化为…

python-自动化篇-运维-监控-简单实例-道出如何使⽤Python进⾏网络监控?

如何使⽤Python进⾏⽹络监控&#xff1f; 使⽤Python进⾏⽹络监控可以帮助实时监视⽹络设备、流量和服务的状态&#xff0c;以便及时识别和解决问题。 以下是⼀般步骤&#xff0c;说明如何使⽤Python进⾏⽹络监控&#xff1a; 选择监控⼯具和库&#xff1a;选择适合⽹络监控需…

IntelliJ IDEA 2023.2.5安装教程

文章结尾提供安装包获取方式 下载安装包 1. 选择相应的安装包&#xff0c;进行安装。如果已经安装过&#xff0c;可以先卸载&#xff0c;也可以保留。根据个人喜好。 2. 修改安装路径。Next……. 3. 创建桌面打开文件。Next…… 4. 等待安装结束。 5. 安装完成。 获取方式&…

文本检索性能提升 40 倍,Apache Doris 倒排索引深度解读

在 OLAP 领域&#xff0c;Apache Doris 已成为高性能、高并发以及高时效性的代名词。在面向海量数据的复杂查询需求时&#xff0c;除硬件配置、集群规模、网络带宽等因素外&#xff0c;提升性能的核心在于如何最大程度地降低 SQL 执行时的 CPU、内存和 IO 开销&#xff0c;而这…

C++知识点笔记

二维数组 定义方式&#xff1a; 1、数据类型 数组名[行数][列数]; 2、数据类型 数组名[行数][列数]{{数据1,数据2},{数据3,数据4}}; 3、数据类型 数组名[行数][列数]{数据1,数据2,数据3,数据4}; 4、数据类型 数组名[][列数]{数据1,数据2,数据3,数据4}; 建议&#xff1a;以…

在人工智能时代,如何利用AI达到行业领先地位?

人工智能很快将成为企业开展业务的一个必要环节。各企业都会具备AI战略&#xff0c;就像其具有社交媒体战略、品牌战略和人才战略等一样。 因此&#xff0c;如果企业希望在竞争中脱颖而出、获得优势&#xff0c;不能只是使用AI&#xff0c;而是要以AI为先导&#xff0c;创造行业…

OpenHarmony—类型转换仅支持as T语法

规则&#xff1a;arkts-as-casts 级别&#xff1a;错误 在ArkTS中&#xff0c;as关键字是类型转换的唯一语法&#xff0c;错误的类型转换会导致编译时错误或者运行时抛出ClassCastException异常。ArkTS不支持使用语法进行类型转换。 当需要将primitive类型&#xff08;如num…

持续集成工具Jenkins的使用之配置篇(二)

上一篇 &#xff1a;持续集成工具Jenkins的安装配置之安装篇(一)-CSDN博客 接上文 三.Jenkins配置 Jenkins配置主要是针对创建构建任务前做的一些基本配置&#xff0c;这些配置有些是必须的&#xff0c;有些是可以帮我们提高效率的&#xff0c;总之都是Jenkins管理员都要会的…

CHS_06.2.2.4_2+调度算法:时间片轮转、优先级、多级反馈队列

CHS_06.2.2.4_2调度算法&#xff1a;时间片轮转、优先级、多级反馈队列 知识总览时间片轮转&#xff08;RR, Round-Robin&#xff09;例题例题时间片轮转&#xff08;RR, Round-Robin&#xff09;优先级调度算法例题优先级调度算法思考多级反馈队列调度算法多级反馈队列调度算法…

【算法练习】leetcode算法题合集之动态规划篇

普通动规系列 LeetCode343. 整数拆分 LeetCode343. 整数拆分 将10的结果存在索引为10的位置上&#xff0c;需要保证数组长度是n1&#xff0c;索引的最大值是n&#xff0c;索引是从0开始的。 n的拆分&#xff0c;可以拆分为i和n-i&#xff0c;当然i可以继续拆分。而且拆分为n-…

Linux 网络传输学习笔记

这篇是混合《Linux性能优化实战》以及 《Wireshark网络分析就这么简单》的一些关于Linux 网络的学习概念和知识点笔记 &#xff0c;主要记录网络传输流程以及对于TCP和UDP传输的一些影响因素 Linux 网络传输流程 借用一张倪朋飞先生的《Linux性能优化实战》课程中的图片 接收流…

OVL assertion checker

目录 单周期断言&#xff1a; 1、ovl_always&#xff1a; 功能描述&#xff1a; ovl和assert_always例子&#xff1a;​编辑​编辑 详细介绍&#xff1a; 2、ovl_implication&#xff1a; 功能描述&#xff1a; 例子&#xff1a; 详细介绍&#xff1a; 3、ovl_never…

JDWP原理分析与漏洞利用

JDWP(Java DEbugger Wire Protocol):即Java调试线协议,是一个为Java调试而设计的通讯交互协议,它定义了调试器和被调试程序之间传递的信息的格式。说白了就是JVM或者类JVM的虚拟机都支持一种协议,通过该协议,Debugger 端可以和 target VM 通信,可以获取目标 VM的包括类…

Sphinx的原理详解和使用

一、Sphinx介绍 1.1 简介 Sphinx是由俄罗斯人Andrew Aksyonoff开发的一个全文检索引擎。意图为其他应用提供高速、低空间占用、高结果 相关度的全文搜索功能。Sphinx可以非常容易的与SQL数据库和脚本语言集成。当前系统内置MySQL和PostgreSQL 数据库数据源的支持&#xff0c;也…

455. 分发饼干 - 力扣(LeetCode)

题目描述 假设你是一位很棒的家长&#xff0c;想要给你的孩子们一些小饼干。但是&#xff0c;每个孩子最多只能给一块饼干。 对每个孩子 i&#xff0c;都有一个胃口值 g[i]&#xff0c;这是能让孩子们满足胃口的饼干的最小尺寸&#xff1b;并且每块饼干 j&#xff0c;都有一个尺…

作者推荐 | 【深入浅出MySQL】「底层原理」探秘缓冲池的核心奥秘,揭示终极洞察

探秘缓冲池的核心奥秘&#xff0c;揭示终极洞察 缓存池BufferPool机制MySQL缓冲池缓冲池缓冲池的问题 缓冲池的原理数据预读程序的局部性原则&#xff08;集中读写原理&#xff09;时间局部性空间局部性 innodb的数据页查询InnoDB的数据页InnoDB缓冲池缓存数据页InnoDB缓存数据…

区间dp/线性dp,HDU 4293 Groups

一、题目 1、题目描述 After the regional contest, all the ACMers are walking alone a very long avenue to the dining hall in groups. Groups can vary in size for kinds of reasons, which means, several players could walk together, forming a group.   As the …

RuoYi-Vue前后端分离后台开发框架运行详细教程

一、官网下载代码 RuoYi-Vue是一款基于SpringBootVue的前后端分离极速后台开发框架。 若依官网&#xff1a;http://ruoyi.vip演示地址&#xff1a;http://vue.ruoyi.vip代码下载&#xff1a;https://gitee.com/y_project/RuoYi-Vue 下载之后解压&#xff0c;ruoyi-ui是前端代…

【JavaWeb】过滤器 Filter

文章目录 过滤器是什么&#xff1f;一、过滤器概述二、过滤器工作位置图解三、Filter接口API四、过滤器使用4.1 定义一个过滤器类,编写功能代码&#xff1a;4.2 xml配置&#xff1a;4.3 定义 servletG 目标资源 模拟测试 :4.4 过滤图解 五、过滤器生命周期六、过滤器链的使用6.…

香港服务器IP段4c和8c的区别及SEO选择建议

随着互联网的快速发展&#xff0c;服务器IP段的选择对于网站SEO优化至关重要。香港服务器IP段4C和8C是两种常见的IP段&#xff0c;它们在SEO优化中具有不同的特点和优势。本文将详细介绍这两种IP段的区别&#xff0c;并给出相应的SEO选择建议。 一、香港服务器IP段4C和8C的区别…