【计算机网络】IO多路转接之epoll

文章目录

  • 一、epoll的相关系统调用
  • 二、epoll工作原理
  • 三、epoll的优点(和 select 的缺点对应)
  • 四、epoll工作方式
  • 五、epoll服务器
    • 1.Sock.hpp
    • 2.Log.hpp
    • 3.Err.hpp
    • 4.epollServer.hpp
    • 5.epollServer.cc

一、epoll的相关系统调用

按照man手册的说法: 是为处理大批量句柄而作了改进的poll.

它是在2.5.44内核中被引进的(epoll(4) is a new API introduced in Linux kernel 2.5.44)

它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法.

epoll 有3个相关的系统调用.

epoll_create

int epoll_create(int size);
创建一个epoll的句柄。
自从linux2.6.8之后,size参数是被忽略的。 
用完之后, 必须调用close()关闭。

epoll_ctl

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
op:增删改
EPOLL_CTL_ADD
EPOLL_CTL_MOD
EPOLL_CTL_DEL

epoll的事件注册函数

它不同于select()是在监听事件时告诉内核要监听什么类型的事件, 而是在这里先注册要监听的事件类型.

第一个参数是epoll_create()的返回值(epoll的句柄).

第二个参数表示动作,用三个宏来表示.

第三个参数是需要监听的fd.

第四个参数是告诉内核需要监听什么事

第二个参数的取值:

EPOLL_CTL_ADD :注册新的fd到epfd中;

EPOLL_CTL_MOD :修改已经注册的fd的监听事件;

EPOLL_CTL_DEL :从epfd中删除一个fd;

struct epoll_event结构如下

在这里插入图片描述

events可以是以下几个宏的集合:

EPOLLIN : 表示对应的文件描述符可以读 (包括对端SOCKET正常关闭);

EPOLLOUT : 表示对应的文件描述符可以写;

EPOLLPRI : 表示对应的文件描述符有紧急的数据可读 (这里应该表示有带外数据到来);

EPOLLERR : 表示对应的文件描述符发生错误;

EPOLLHUP : 表示对应的文件描述符被挂断;

EPOLLET : 将EPOLL设为边缘触发(Edge Triggered)模式, 这是相对于水平触发(Level Triggered)来说的.

EPOLLONESHOT:只监听一次事件, 当监听完这次事件之后, 如果还需要继续监听这个socket的话, 需要

再次把这个socket加入到EPOLL队列里

epoll_wait

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

收集在epoll监控的事件中已经发送的事件

参数events是分配好的epoll_event结构体数组.

epoll将会把发生的事件赋值到events数组中 (events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存).

maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size.

参数timeout是超时时间 (毫秒,0会立即返回,-1是永久阻塞).

如果函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时, 返回小于0表示函数失败.

二、epoll工作原理

在这里插入图片描述

当某一进程调用epoll_create方法时,Linux内核会创建一个eventpoll结构体,这个结构体中有两个成员与epoll的使用方式密切相关

struct eventpoll{ 
 .... 
	 /*红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/ 
	 struct rb_root rbr; 
	 /*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/ 
	 struct list_head rdlist; 
	 .... 
};

每一个epoll对象都有一个独立的eventpoll结构体,用于存放通过epoll_ctl方法向epoll对象中添加进来的事件.

这些事件都会挂载在红黑树中,如此,重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插入时间效率是lgn,其中n为树的高度).

而所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当响应的事件发生时会调用这个回调方法.这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中. 在epoll中,对于每一个事件,都会建立一个epitem结构体.

struct epitem { 
 	struct rb_node rbn;//红黑树节点 
 	struct list_head rdllink;//双向链表节点 
 	struct epoll_filefd ffd; //事件句柄信息 
 	struct eventpoll *ep; //指向其所属的eventpoll对象 
 	struct epoll_event event; //期待发生的事件类型 
}

当调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可.

如果rdlist不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户. 这个操作的时间复杂度是O(1).

总结一下, epoll的使用过程就是三部曲:

调用epoll_create创建一个epoll句柄;

调用epoll_ctl, 将要监控的文件描述符进行注册;

调用epoll_wait, 等待文件描述符就绪

三、epoll的优点(和 select 的缺点对应)

接口使用方便: 虽然拆分成了三个函数, 但是反而使用起来更方便高效. 不需要每次循环都设置关注的文件描述符, 也做到了输入输出参数分离开

数据拷贝轻量: 只在合适的时候调用 EPOLL_CTL_ADD 将文件描述符结构拷贝到内核中, 这个操作并不频繁(而select/poll都是每次循环都要进行拷贝)

事件回调机制: 避免使用遍历, 而是使用回调函数的方式, 将就绪的文件描述符结构加入到就绪队列中,

epoll_wait 返回直接访问就绪队列就知道哪些文件描述符就绪. 这个操作时间复杂度O(1). 即使文件描述符数目很多, 效率也不会受到影响.

没有数量限制: 文件描述符数目无上限.

注意!!

网上有些博客说, epoll中使用了内存映射机制

内存映射机制: 内核直接将就绪队列通过mmap的方式映射到用户态. 避免了拷贝内存这样的额外性能开销

这种说法是不准确的. 我们定义的struct epoll_event是我们在用户空间中分配好的内存. 势必还是需要将内核的数据拷贝到这个用户空间的内存中的

四、epoll工作方式

什么叫做事件就绪?底层的I0O条件满足了,可以进行某种IO行为了,就叫做事件就绪

1.只要底层有数据没读完,epoll就会一直通知用户要读取数据LT.

2.只要底层有数据没读完,epoll不在通知用户,除非底层的数据变化的时候(再次增多),才会在通知你一次!ET

你正在吃鸡, 眼看进入了决赛圈, 你妈饭做好了, 喊你吃饭的时候有两种方式:

1.如果你妈喊你一次, 你没动, 那么你妈会继续喊你第二次, 第三次…(亲妈, 水平触发)

2.如果你妈喊你一次, 你没动, 你妈就不管你了(后妈, 边缘触发)

epoll有2种工作方式-水平触发(LT)和边缘触发(ET)

假如有这样一个例子:

我们已经把一个tcp socket添加到epoll描述符

这个时候socket的另一端被写入了2KB的数据

调用epoll_wait,并且它会返回. 说明它已经准备好读取操作

然后调用read, 只读取了1KB的数据

继续调用epoll_wait…

水平触发Level Triggered 工作模式

epoll默认状态下就是LT工作模式

当epoll检测到socket上事件就绪的时候, 可以不立刻进行处理. 或者只处理一部分.

如上面的例子, 由于只读了1K数据, 缓冲区中还剩1K数据, 在第二次调用 epoll_wait 时, epoll_wait

仍然会立刻返回并通知socket读事件就绪.

直到缓冲区上所有的数据都被处理完, epoll_wait 才不会立刻返回.

支持阻塞读写和非阻塞读写

边缘触发Edge Triggered工作模式

如果我们在第1步将socket添加到epoll描述符的时候使用了EPOLLET标志, epoll进入ET工作模式.

当epoll检测到socket上事件就绪时, 必须立刻处理.

如上面的例子, 虽然只读了1K的数据, 缓冲区还剩1K的数据, 在第二次调用 epoll_wait 的时候, epoll_wait 不会再返回了.也就是说, ET模式下, 文件描述符上的事件就绪后, 只有一次处理机会.

ET的性能比LT性能更高( epoll_wait 返回的次数少了很多). Nginx默认采用ET模式使用epoll. 只支持非阻塞的读写

select和poll其实也是工作在LT模式下. epoll既可以支持LT, 也可以支持ET.

对比LT和ET

LT是 epoll 的默认行为. 使用 ET 能够减少 epoll 触发的次数. 但是代价就是强逼着程序猿一次响应就绪过程中就把

所有的数据都处理完.相当于一个文件描述符就绪之后, 不会反复被提示就绪, 看起来就比 LT 更高效一些. 但是在 LT 情况下如果也能做到每次就绪的文件描述符都立刻处理, 不让这个就绪被重复提示的话, 其实性能也是一样的.另一方面, ET 的代码复杂程度更高了

底层只有数据从无到有,从有到多变化的时候,才会通知(rbtcbready queue)上层—>只会通知一次—>倒逼程序员将本轮就绪的数据全部读取到上层-你么知道你把本次就绪的底层的数据读取完毕了呢?循环读取,直到读取不到数据了---->一般的fd,是阻塞式的fd ->ET,对应的fd必须是非阻塞的!!!

将文件fd设置为非阻塞的:

#include <iostream>
#include <unistd.h>
#include <fcntl.h>

void SetNonBlock(int fd)
{
    int fl = fcntl(fd, F_GETFL);
    if (fl < 0)
    {
        perror("fcntl");
        return;
    }
    fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}

int main()
{
    SetNonBlock(0);
    while (true)
    {
        char buffer[1024];
        ssize_t s = read(0, buffer, sizeof(buffer) - 1);
        if (s > 0)
        {
            std::cout << buffer << std::endl;
        }
        else
        {
            perror("read");
            sleep(1);
            continue;
        }
    }
    return 0;
}

LT,阻塞的,非阻塞也可以的

LT模式下,我可不可以模仿ET的工作方式呢?? ?当然可以喽!! ! !

1.不仅仅体现在通知机制上

2.尽快让上层把数据取走->TCP可以给发送对提供一个更大的窗口大小->让对方更新出更大的滑动窗口->提供底层的数据发送效率,更好的理由诸如TCP延迟应答等策略! ! !

3.TCP中的PSH的作用??让底层数据就绪事件,再让上层知道!

理解ET模式和非阻塞文件描述符

使用 ET 模式的 epoll, 需要将文件描述设置为非阻塞. 这个不是接口上的要求, 而是 “工程实践” 上的要求.

假设这样的场景: 服务器接受到一个10k的请求, 会向客户端返回一个应答数据. 如果客户端收不到应答, 不会发送第

二个10k请求

在这里插入图片描述

如果服务端写的代码是阻塞式的read, 并且一次只 read 1k 数据的话(read不能保证一次就把所有的数据都读出来,参考 man 手册的说明, 可能被信号打断), 剩下的9k数据就会待在缓冲区中

在这里插入图片描述

此时由于 epoll 是ET模式, 并不会认为文件描述符读就绪. epoll_wait 就不会再次返回. 剩下的 9k 数据会一直在缓冲区中. 直到下一次客户端再给服务器写数据. epoll_wait 才能返回

但是问题来了.

服务器只读到1k个数据, 要10k读完才会给客户端返回响应数据.

客户端要读到服务器的响应, 才会发送下一个请求

客户端发送了下一个请求, epoll_wait 才会返回, 才能去读缓冲区中剩余的数据.

在这里插入图片描述

所以, 为了解决上述问题(阻塞read不一定能一下把完整的请求读完), 于是就可以使用非阻塞轮训的方式来读缓冲区,保证一定能把完整的请求都读出来.而如果是LT没这个问题. 只要缓冲区中的数据没读完, 就能够让 epoll_wait 返回文件描述符读就绪.

epoll的使用场景

epoll的高性能, 是有一定的特定场景的. 如果场景选择的不适宜, epoll的性能可能适得其反.

对于多连接, 且多连接中只有一部分连接比较活跃时, 比较适合使用epoll. 例如, 典型的一个需要处理上万个客户端的服务器, 例如各种互联网APP的入口服务器, 这样的服务器就很适合epoll.如果只是系统内部, 服务器和服务器之间进行通信, 只有少数的几个连接, 这种情况下用epoll就并不合适. 具体要根据需求和场景特点来决定使用哪种IO模型

epoll中的惊群问题

参考 https://blog.csdn.net/fsmiy/article/details/36873357

epoll参考资料

epoll详情

Apache与Nginx网络模型

五、epoll服务器

在epoll中能不能直接发送呢???不能! ! !用户无法保证发送条件是就绪的!谁最清楚? ? epoll

什么叫做写发送事件就绪?发送缓冲区有空间!
服务器刚开始启动,或者很多情况下,发送事件一直是就绪的!可以直接发送。只不过,如果我们没有发完怎么办?需要下一次发送,这里也有要求每一个sock都要有自己的发送缓冲区

注定你要将sock的EPOLLOUT事件也要有时候注册进epoll,什么时候注册
一般读事件对于epoll我们要常设
写事件的关系,对于epoll,我们要按需设置!

1.Sock.hpp

#pragma once 

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

#include "Log.hpp"
#include "Err.hpp"

static const int backlog = 32;

class Sock
{
public:
    // 1. 创建socket文件套接字对象
    static int Socket()
    {
        int sock = socket(AF_INET,SOCK_STREAM,0);
        if(sock < 0)
        {
            logMessage(FATAL,"socket create error");
            exit(SOCKET_ERR);
        }
        logMessage(NORMAL,"socket create success: %d",sock);

        int opt = 1;
        setsockopt(sock,SOL_SOCKET,SO_REUSEADDR | SO_REUSEPORT,&opt,sizeof opt);

        return sock;    
    }

    // 2.bind自己的网络信息
    static void Bind(int sock,const uint16_t port)
    {
        struct sockaddr_in local;
        memset(&local,0,sizeof local);

        local.sin_family = AF_INET;
        local.sin_addr.s_addr = INADDR_ANY;
        local.sin_port = htons(port);

        int n = bind(sock,(struct sockaddr*)&local,sizeof local);
        if(n < 0)
        {
            logMessage(FATAL,"socket bind error");
            exit(BIND_ERR);
        }
        logMessage(NORMAL,"socket bind success");
    }

    // 3. 设置socket 为监听状态
    static void Listen(int sock)
    {
        int n = listen(sock,backlog);
        if(n < 0)
        {
            logMessage(FATAL,"socket listen error");
            exit(LISTEN_ERR);
        }
        logMessage(NORMAL,"socket listen success");
    }

    static int Accept(int listensock,std::string* clientip,uint16_t* clientport)
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int sock = accept(listensock,(struct sockaddr*)&peer,&len);
        if(sock < 0)
        {
            logMessage(ERROR,"socket accept error,next");
        }
        else
        {
            logMessage(NORMAL,"accept a new link success, get a new sock: %d",sock);
            *clientip = inet_ntoa(peer.sin_addr);
            *clientport = ntohs(peer.sin_port);
        }

        return sock;
    }
};

2.Log.hpp

#pragma once

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <stdarg.h>

#define NORMAL 0
#define DEBUG 1
#define WARNING 2
#define ERROR 3
#define FATAL 4

#define LOG_NORMAL "./log.txt"
#define LOG_ERR "./err.txt"

#define NUM 1024

const char *to_levelstr(int level)
{
    switch (level)
    {
    case DEBUG:
        return "DEBUG";
    case NORMAL:
        return "NORMAL";
    case WARNING:
        return "WARNING";
    case ERROR:
        return "ERROR";
    case FATAL:
        return "FATAL";
    default:
        return nullptr;
    }
}

void logMessage(int level, const char *format, ...)
{
    // [日志等级] [时间戳/时间] [pid] [messge]
    char logprofix[NUM];
    snprintf(logprofix, sizeof logprofix, "[%s][%ld][pid:%d]", to_levelstr(level), (long int)time(nullptr), getpid());

    char logcontent[NUM];
    va_list arg;
    va_start(arg, format);

    vsnprintf(logcontent, sizeof logcontent, format, arg);

    std::cout << logprofix << logcontent << std::endl;

    FILE *log = fopen(LOG_NORMAL, "a");
    FILE *error = fopen(LOG_ERR, "a");

    if (log && error)
    {
        FILE *cur = nullptr;
        if (level == DEBUG || level == NORMAL || level == WARNING)
            cur = log;
        if (level == ERROR || level == FATAL)
            cur = error;
        if (cur)
            fprintf(cur, "%s%s\n", logprofix, logcontent);

        fclose(log);
        fclose(error);
    }
}

3.Err.hpp

#pragma once

enum
{
    USAGE_ERR = 1,
    SOCKET_ERR,
    BIND_ERR,
    LISTEN_ERR,
    EPOLL_CREATE_ERR
};

4.epollServer.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <functional>
#include <sys/epoll.h>

#include "Sock.hpp"
#include "Err.hpp"
#include "Log.hpp"

namespace epollserver
{
    static const uint16_t defaultport = 8080;
    const static int size = 128;
    const static int defaultvalue = -1;
    const static int defaultnum = 64;

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

    class epollServer
    {
    public:
        epollServer(const func_t &func, const uint16_t &port = defaultport)
            : _port(port), _func(func), _listensock(defaultvalue), _epfd(defaultvalue), _num(defaultnum), _revs(nullptr)
        {
        }
        ~epollServer()
        {
            if (_listensock != defaultvalue)
                close(_listensock);
            if (_epfd != defaultvalue)
                close(_epfd);
            if (_revs)
                delete[] _revs;
        }

    public:
        void initServer()
        {
            // 1. 创建socket
            _listensock = Sock::Socket();
            Sock::Bind(_listensock, _port);
            Sock::Listen(_listensock);

            // 2. 创建epoll模型
            _epfd = epoll_create(size);
            if (_epfd < 0)
            {
                logMessage(FATAL, "epoll create error: %s", strerror(errno));
                exit(EPOLL_CREATE_ERR);
            }
            logMessage(NORMAL, "epoll create success");

            // 3. 添加listensock到epoll中!
            struct epoll_event ev;
            ev.events = EPOLLIN;
            ev.data.fd = _listensock;
            epoll_ctl(_epfd, EPOLL_CTL_ADD, _listensock, &ev);

            // 4. 申请就绪事件的空间
            _revs = new struct epoll_event[_num];

            logMessage(NORMAL, "epollserver init success");
        }

        void HandlerEvent(int num)
        {
            logMessage(DEBUG, "HandlerEvent in");
            for (int i = 0; i < num; i++)
            {
                uint32_t events = _revs[i].events;
                int sock = _revs[i].data.fd;

                if (sock == _listensock && (events & EPOLLIN))
                {
                    //_listensock读事件就绪, 获取新连接
                    std::string clientip;
                    uint16_t clientport;
                    int fd = Sock::Accept(sock, &clientip, &clientport);
                    if (fd < 0)
                    {
                        logMessage(ERROR, "accept error");
                        continue;
                    }

                    // 获取fd成功,可以直接读取吗??不可以,放入epoll
                    struct epoll_event ev;
                    ev.events = EPOLLIN;
                    ev.data.fd = fd;
                    epoll_ctl(_epfd, EPOLL_CTL_ADD, fd, &ev);
                }
                else if (events & EPOLLIN)
                {
                    char buffer[1024];
                    ssize_t n = recv(sock, buffer, sizeof(buffer), 0);
                    if (n > 0)
                    {
                        buffer[n] = 0;
                        logMessage(NORMAL, "client# %s", buffer);

                        std::string response = _func(buffer);
                        send(sock, response.c_str(), response.size(), 0);
                    }
                    else if (n == 0)
                    {
                        // 建议先从epoll移除,才close fd
                        epoll_ctl(_epfd, EPOLL_CTL_DEL, sock, nullptr);
                        close(sock);
                        logMessage(NORMAL, "client qiut");
                    }
                    else
                    {
                        // 建议先从epoll移除,才close fd
                        epoll_ctl(_epfd, EPOLL_CTL_DEL, sock, nullptr);
                        close(sock);
                        logMessage(ERROR, "recv error, code: %d, errstring: %s", errno, strerror(errno));
                    }
                }
                else
                {
                }
            }
            logMessage(DEBUG, "HandlerEvent out");
        }
        void start()
        {
            int timeout = -1;
            for (;;)
            {
                int n = epoll_wait(_epfd, _revs, _num, timeout);
                switch (n)
                {
                case 0:
                    logMessage(NORMAL, "timeout...");
                    break;
                case -1:
                    logMessage(WARNING, "epoll_wait failed, code: %d, errstring: %s", errno, strerror(errno));
                    break;
                default:
                    logMessage(NORMAL, "have event ready");
                    HandlerEvent(n);
                    break;
                }
            }
        }

    private:
        uint16_t _port;
        int _listensock;
        int _epfd;
        struct epoll_event *_revs;
        int _num;
        func_t _func;
    };
}

5.epollServer.cc

#include "epollServer.hpp"
#include "Log.hpp"
#include <memory>

using namespace std;
using namespace epollserver;

static void Usage(const string& proc)
{
    std::cerr << "Usage:\n\t" << proc << " port\n\n";
}

string echo(const string& message)
{
    return " I am epollserver, " + message;
}

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

    uint16_t port = atoi(argv[1]);
    unique_ptr<epollServer> esvr(new epollServer(echo,port));

    esvr->initServer();
    esvr->start();

    return 0;
}

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

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

相关文章

iOS小技能:苹果开发者申请材料

文章目录 引言I 个人账号申请资料II 公司账号申请所需资料III duns资料提交操作步骤IV 续费引言 https://developer.apple.com/cn/programs/enroll/ 申请过程只能使用同一台设备注册苹果开发者的Apple ID可以转让。注册苹果开发者的在验证身份证信息的时候,必须使用法定姓名拼…

信呼OA普通用户权限getshell方法

0x01 前言 信呼OA是一款开源的OA系统&#xff0c;面向社会免费提供学习研究使用&#xff0c;采用PHP语言编写&#xff0c;搭建简单方便&#xff0c;在中小企业中具有较大的客户使用量。从公开的资产治理平台中匹配到目前互联中有超过1W的客户使用案例。 信呼OA目前最新的版本是…

Docker_设置docker服务以及容器开机自启

本文目录 docker服务开机自启动查询docker服务开机自启动状态将docker服务设置为开机自启动取消docker服务开机自启动 容器开机自启动修改docker容器为自启动容器启动时设置自启动-docker版容器启动时设置自启动-docker-compose版 docker服务开机自启动 查询docker服务开机自启…

git 命令怎么回退到某个特定的 commit 并将其推送到远程仓库?

问题 不小心把提交的名称写错提交上远程仓库了&#xff0c;这里应该是 【029】的&#xff0c;这个时候我们想回到【028】这一个提交记录&#xff0c;然后再重新提交【029】到远程仓库&#xff0c;该怎么处理。 解决 1、首先我们找到【028】这条记录的提交 hash&#xff0c;右…

【web安全】实战 批量横扫springboot命令执行漏洞

天命&#xff1a;这次目标批量横扫&#xff0c;但是没完全成功&#xff0c;也没完全失败 步骤1&#xff1a;磨刀准备 这次先针对漏洞来寻找目标&#xff0c;所以寻找这种 springboot 的目标 利用CVE漏洞&#xff0c;进行命令执行攻击 先找靶场训练一波&#xff0c;叠加反弹sh…

2024年阿里云域名优惠口令更新,亲测有效口令大全

2024年阿里云域名优惠口令&#xff0c;com域名续费优惠口令“com批量注册更享优惠”&#xff0c;cn域名续费优惠口令“cn注册多个价格更优”&#xff0c;cn域名注册优惠口令“互联网上的中国标识”&#xff0c;阿里云优惠口令是域名专属的优惠码&#xff0c;可用于域名注册、续…

【教育部白名单赛事】C语言编程题解析--软件编程邀请赛(决赛)

文章目录 1、保留12位小数的浮点数2、气温统计3.大写字母的判断4、【递归】母鸡的故事5、小白免再排队 1、保留12位小数的浮点数 输入一个双精度浮点数&#xff0c;保留12位小数&#xff0c;输出这个浮点数。 时间限制&#xff1a;1000 内存限制&#xff1a;65536 【输入】 只…

华为机试 字符串最后一个单词的长度

本题中&#xff0c;我们是要从键盘输入一个字符串&#xff0c;然后返回这个字符串最后一个单词的长度。所以我们需要scancer类。我们需要注意的是&#xff0c;hasnext()和hasnextline()这两个函数的区别。 import java.util.Scanner;// 注意类名必须为 Main, 不要有任何 pack…

24计算机考研调剂 | 北京语言大学

北京语言大学 刘忠宝教授课题组招收计算机学硕调剂生2名 考研调剂招生信息 学校:北京语言大学 专业:工学->计算机科学与技术->计算机应用技术 年级:2023 招生人数:2 招生状态:正在招生中 联系方式:********* (为保护个人隐私,联系方式仅限APP查看) 补充内容 一、…

Android开发五年,职场中的中年危机

前言 Android确实不是当年盛况&#xff0c;已经不再像前几年前那么火爆。一个新行业如果经历过盛极一时&#xff0c;那么必然有这样的一条曲线&#xff0c;像我们学的正弦曲线先急速上升&#xff0c;然后到达顶点&#xff0c;然后再下降&#xff0c;最后再趋近一个平稳的值。那…

【Python--读获取目录下所有csv文件中的均值与偏态】

&#x1f680; 作者 &#xff1a;“码上有前” &#x1f680; 文章简介 &#xff1a;Python &#x1f680; 欢迎小伙伴们 点赞&#x1f44d;、收藏⭐、留言&#x1f4ac; python练习题 读获取目录下所有csv文件中的均值与偏态按照均值和偏态最大值进行排序完整代码 读获取目录下…

RocketMq——Consume相关源码

摘要 RocketMQ只要有CommitLog文件就可以正常运行了&#xff0c;那为何还要维护ConsumeQueue文件呢&#xff1f; ConsumeQueue是消费队列&#xff0c;引入它的目的是为了提高消费者的消费速度。毕竟RocketMQ是基于Topic主题订阅模式的&#xff0c;消费者往往只关心自己订阅的…

184基于matlab的相关向量机(RVM)回归和分类算法

基于matlab的相关向量机&#xff08;RVM&#xff09;回归和分类算法。该算法基于贝叶斯稀疏核⽅法&#xff0c;避免了支持向量机&#xff08;SVM&#xff09;的主要局限性。RVM关键是为每个权参数 都引入一个单独的超参数 &#xff0c;而不是一个共享超参数。程序已调通&#x…

和鲸科技受邀参与湖南省气象信息中心开展人工智能研究型业务支撑平台学术交流

为推进湖南省机器学习统一平台建设&#xff0c;2 月 29 日&#xff0c;湖南省气象信息中心开展学术讲座活动&#xff0c;活动由中心副主任冯冼主持&#xff0c;中心业务骨干、湖南省气象台、湖南分院等技术人员参加。 本次讲座邀请上海和今信息科技有限公司&#xff08;简称“…

MySQL——事务

事务 2024 年 1 月字节后端实习面试&#xff1a;说说对 ACID 的理解&#xff1f; 什么是事务&#xff1f; 事务&#xff08;Transaction&#xff09;是数据库管理系统中一个执行单元&#xff08;unit of work&#xff09;&#xff0c;它由一系列的操作&#xff08;例如读取数…

(文末送书)《低代码平台开发实践:基于React》

最近&#xff0c;我发现了一个超级强大的人工智能学习网站。它以通俗易懂的方式呈现复杂的概念&#xff0c;而且内容风趣幽默。我觉得它对大家可能会有所帮助&#xff0c;所以我在此分享。点击这里跳转到网站。 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&am…

XGboost的整理

XGboost&#xff08;extreme gradient boosting&#xff09;:高效实现了GBDT算法并进行了算法和工程上的许多改进。 XGboost的思路&#xff1a; 目标&#xff1a;建立k个回归树&#xff0c;使得树群的预测尽量接近真实值&#xff08;准确率&#xff09;而且有尽量大的泛化能力…

Docker安装+基础命令

一、检测、配置安装环境 &#xff08;1&#xff09;查看linux版本&#xff0c;是否符合>centos 7 &#xff08;2&#xff09;查看网络是否通畅 &#xff08;3&#xff09;安装gcc&#xff0c;gcc-c编译器 &#xff08;4&#xff09;安装device-mapper-persistent-data和lvm2…

IPsec VPN协议框架

IPsec是IETF&#xff08;Internet Engineering Task Force&#xff09;制定的一组开放的网络安全协议。它并不是一个单独的协议&#xff0c;而是一系列为IP网络提供安全性的协议和服务的集合&#xff0c;包括认证头AH&#xff08;Authentication Header&#xff09;和封装安全载…

LeetCode刷题---填充每个节点的下一个右侧节点指针

官方题解:LeetCode官方题解 解题思想: 因为是一棵满二叉树&#xff0c;所以除了叶子节点外的其他节点都有两个子节点。 可以根据每一层来依次遍历 从根节点开始&#xff0c;根节点的左子节点的next节点就指向根节点的右子节点 因为根节点的next节点为NULL&#xff0c;开始从根…