Libvirt Event Loop简介

news2024/11/24 12:21:08

文章目录

  • 前言
  • 实现原理
    • 处理框架
    • 编程接口
  • 原理验证
    • 事件订阅
    • 服务监听
    • 验证流程

前言

  • Event Loop顾名思义就是事件循环,整个程序是一个大的循环,通过事件来驱动程序要做的事情。传统编程模型是顺序的,程序运行一次然后终止,这种模型简单,常用于实现一个特定功能。但这种模型不适合交互式的程序,比如图形用户程序,用户大多数时候可能不会有输入,一旦有了输入,程序需要根据其类型执行特定的代码以实现特定功能。
  • 事件触发的程序有一个普遍特点,就是会长时间运行并等待用于输入,守护进程或者服务都可以判定为这类程序。Libvirt虽然没有图形接口也不等待用户的直接输入,但它作为服务会长时间监听各种socket的连接,因此也使用这种编程模型。

实现原理

处理框架

在这里插入图片描述

  • 上图是GLib提供的事件循环状态机,可以看到事件循环最核心的动作就是poll fd(Polling)然后执行回调函数(Dispatching),poll通常使用内核提供的poll接口实现,除此以外,GLib还定义了两个阶段,初始化阶段(Initial)和准备阶段(Prepared),对于各阶段GLib实现了对应hooks方便用户实现自己的逻辑。
  • 对更高级的编程用户来说,可以只关心事件循环监听的什么事件、事件触发后执行的什么,Libvirt提供的事件循环接口较GLib更接近用户使用场景,分别是添加回调、更新回调和删除回调。

编程接口

  • Libvirt的开发包(libvirt-devel)以及共享库(libvirt.so)为用户提供编程API,其中libvirt-event.h头文件提供了事件循环的编程接口,因此不仅Libvirt服务本身可以使用事件循环接口,用户程序也可以使用该接口实现自己的功能。头文件如下:

    /*
     * libvirt-event.h
     * Summary: APIs for management of events
     * Description: Provides APIs for the management of events
     *
     * Copyright (C) 2006-2014 Red Hat, Inc.
     *
     * This library is free software; you can redistribute it and/or
     * modify it under the terms of the GNU Lesser General Public
     * License as published by the Free Software Foundation; either
     * version 2.1 of the License, or (at your option) any later version.
     *
     * This library is distributed in the hope that it will be useful,
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     * Lesser General Public License for more details.
     *
     * You should have received a copy of the GNU Lesser General Public
     * License along with this library.  If not, see
     * <http://www.gnu.org/licenses/>.
     */
    
    #ifndef LIBVIRT_EVENT_H
    # define LIBVIRT_EVENT_H
    
    # ifndef __VIR_LIBVIRT_H_INCLUDES__
    #  error "Don't include this file directly, only use libvirt/libvirt.h"
    # endif
    
    
    /**
     * virEventHandleType:
     *
     * a virEventHandleType is used similar to POLLxxx FD events, but is specific
     * to libvirt. A client app must translate to, and from POLL events when using
     * this construct.
     * 
     * 事件循环所监听fd的所有状态
     */
    typedef enum {
        VIR_EVENT_HANDLE_READABLE  = (1 << 0),
        VIR_EVENT_HANDLE_WRITABLE  = (1 << 1),
        VIR_EVENT_HANDLE_ERROR     = (1 << 2),
        VIR_EVENT_HANDLE_HANGUP    = (1 << 3),
    } virEventHandleType;
    
    /**
     * virEventHandleCallback:
     *
     * @watch: watch on which the event occurred
     * @fd: file handle on which the event occurred
     * @events: bitset of events from virEventHandleType constants
     * @opaque: user data registered with handle
     *
     * Callback for receiving file handle events. The callback will
     * be invoked once for each event which is pending.
     *
     * 当事件循环监听的fd中有用户感兴趣的状态后所调用的用户定义的回调
     * 比如用户是一个监听tcp socket的服务程序,对所监听socket fd
     * VIR_EVENT_HANDLE_READABLE事件感兴趣,则需要实现这样一个类
     * 型的回调函数
     */
    typedef void (*virEventHandleCallback)(int watch, int fd, int events, void *opaque);
    
    /**
     * Libvirt默认使用GLib提供的事件循环接口实现事件循环,但用户也可以定制事件循环中的
     * 每个动作(包括增、删、更新事件回调和增、删、更新timer超时回调六种)。用户如果实现
     * 此六种事件循环框架要求的方法,即可使用自己定制的事件循环,但方法的签名必须按照如
     * 下定义来实现
     */
    
    /**
     * virEventAddHandleFunc:
     * @fd: file descriptor to listen on
     * @event: bitset of events on which to fire the callback
     * @cb: the callback to be called when an event occurs
     * @opaque: user data to pass to the callback
     * @ff: the callback invoked to free opaque data blob
     *
     * Part of the EventImpl, this callback adds a file handle callback to
     * listen for specific events. The same file handle can be registered
     * multiple times provided the requested event sets are non-overlapping
     *
     * @fd will always be a C runtime file descriptor. On Windows
     * the _get_osfhandle() method can be used if a HANDLE is required
     * instead.
     *
     * If the opaque user data requires free'ing when the handle
     * is unregistered, then a 2nd callback can be supplied for
     * this purpose. This callback needs to be invoked from a clean stack.
     * If 'ff' callbacks are invoked directly from the virEventRemoveHandleFunc
     * they will likely deadlock in libvirt.
     *
     * Returns -1 if the file handle cannot be registered, otherwise a handle
     * watch number to be used for updating and unregistering for events
     *
     * 事件循环添加回调函数的方法签名
     */
    typedef int (*virEventAddHandleFunc)(int fd, int event,
                                         virEventHandleCallback cb,
                                         void *opaque,
                                         virFreeCallback ff);
    
    /**
     * virEventUpdateHandleFunc:
     * @watch: file descriptor watch to modify
     * @event: new events to listen on
     *
     * Part of the EventImpl, this user-provided callback is notified when
     * events to listen on change
     * 
     * 事件循环更新回调函数的方法签名
     */
    typedef void (*virEventUpdateHandleFunc)(int watch, int event);
    
    /**
     * virEventRemoveHandleFunc:
     * @watch: file descriptor watch to stop listening on
     *
     * Part of the EventImpl, this user-provided callback is notified when
     * an fd is no longer being listened on.
     *
     * If a virEventHandleFreeFunc was supplied when the handle was
     * registered, it will be invoked some time during, or after this
     * function call, when it is safe to release the user data.
     *
     * Returns -1 if the file handle was not registered, 0 upon success
     *
     * 事件循环删除回调函数的方法签名
     */
    typedef int (*virEventRemoveHandleFunc)(int watch);
    
    /**
     * virEventTimeoutCallback:
     *
     * @timer: timer id emitting the event
     * @opaque: user data registered with handle
     *
     * callback for receiving timer events
     */
    typedef void (*virEventTimeoutCallback)(int timer, void *opaque);
    
    /**
     * virEventAddTimeoutFunc:
     * @timeout: The timeout to monitor
     * @cb: the callback to call when timeout has expired
     * @opaque: user data to pass to the callback
     * @ff: the callback invoked to free opaque data blob
     *
     * Part of the EventImpl, this user-defined callback handles adding an
     * event timeout.
     *
     * If the opaque user data requires free'ing when the handle
     * is unregistered, then a 2nd callback can be supplied for
     * this purpose.
     *
     * Returns a timer value
     */
    typedef int (*virEventAddTimeoutFunc)(int timeout,
                                          virEventTimeoutCallback cb,
                                          void *opaque,
                                          virFreeCallback ff);
    
    /**
     * virEventUpdateTimeoutFunc:
     * @timer: the timer to modify
     * @timeout: the new timeout value
     *
     * Part of the EventImpl, this user-defined callback updates an
     * event timeout.
     */
    typedef void (*virEventUpdateTimeoutFunc)(int timer, int timeout);
    
    /**
     * virEventRemoveTimeoutFunc:
     * @timer: the timer to remove
     *
     * Part of the EventImpl, this user-defined callback removes a timer
     *
     * If a virEventTimeoutFreeFunc was supplied when the handle was
     * registered, it will be invoked some time during, or after this
     * function call, when it is safe to release the user data.
     *
     * Returns 0 on success, -1 on failure
     */
    typedef int (*virEventRemoveTimeoutFunc)(int timer);
    
    /** 
     * 如上介绍,如果用户定制了事件循环的实现方法,通过下面函数注册事件循环
     * 的方案,之后Libvirt在启动事件循环时默认使用
     */
    
    void virEventRegisterImpl(virEventAddHandleFunc addHandle,
                              virEventUpdateHandleFunc updateHandle,
                              virEventRemoveHandleFunc removeHandle,
                              virEventAddTimeoutFunc addTimeout,
                              virEventUpdateTimeoutFunc updateTimeout,
                              virEventRemoveTimeoutFunc removeTimeout);
    /**
     * 通常情况下,为快速集成Libvirt的事件循环到用户程序,会选择用Libvirt默认方法
     * 来实现事件循环,因此Libvirt提供下面这个接口让用户将默认方法注册,早期libvirt
     * 使用poll实现,默认方法分别是:
     * virEventPollAddHandle
     * virEventPollUpdateHandle
     * virEventPollRemoveHandle
     * virEventPollAddTimeout
     * virEventPollUpdateTimeout
     * virEventPollRemoveTimeout
     * 新版本libvirt使用Glib提供的事件循环,默认方法分别是:
     * virEventGLibHandleAdd
     * virEventGLibHandleUpdate
     * virEventGLibHandleRemove
     * virEventGLibTimeoutAdd
     * virEventGLibTimeoutUpdate
     * virEventGLibTimeoutRemove
     */
    int virEventRegisterDefaultImpl(void);
    /**
     * 使用Libvirt默认事件框架,需要调用如下接口启动事件循环
     */
    int virEventRunDefaultImpl(void);
    
    /**
     * 向事件循环添加回调函数,该函数返回一个名为watch的整数,作为对更新删除
     * 回调函数的句柄,它实际上是一个索引,libvirt使用这个索引在所有注册
     * 的回调函数形成的数组中找到对应的回调函数
     */
    int virEventAddHandle(int fd, int events,
                          virEventHandleCallback cb,
                          void *opaque,
                          virFreeCallback ff);
    /* 更新已添加的回调函数感兴趣的事件 */
    void virEventUpdateHandle(int watch, int events);
    /* 删除已添加的回调函数 */
    int virEventRemoveHandle(int watch);
    /* 添加一个超时回调函数 
     * 当frequency参数的值为-1时表示仅仅注册,不会触发回调
     * 当frequency参数的值为0时表示每次event loop触发回调
     * 当frequency参数的值大于0时按照该频率触发回调
     */
    int virEventAddTimeout(int frequency,
                           virEventTimeoutCallback cb,
                           void *opaque,
                           virFreeCallback ff);
    void virEventUpdateTimeout(int timer, int frequency);
    int virEventRemoveTimeout(int timer);
    
    #endif /* LIBVIRT_EVENT_H */
    

原理验证

  • 我们通过集成Libvirt event loop实现一个demo,它的主要功能是订阅主机上虚机的生命周期事件并打印,同时还作为一个rpc server,监听一个本地端口响应客户段的rpc request。demo框架如下所示:
    在这里插入图片描述
  • 对于订阅事件,我们可以利用Libvirt现成的API实现,对于rpc server,这是我们新增功能,要求将监听端口的socket fd加入到event loop,并注册rpc server的连接回调以及请求回调。demo代码参考event monitor。

事件订阅

  • 当用户程序对libvirt管理的虚机的某个事件感兴趣,可以订阅该事件,并注册该事件发生后的回调。订阅和取消虚机事件的API在libvirt-domain.h头文件中定义,如下:
/* Use VIR_DOMAIN_EVENT_CALLBACK() to cast the 'cb' parameter  */
int virConnectDomainEventRegisterAny(virConnectPtr conn,
									 /* 感兴趣的虚机,可选 */
                                     virDomainPtr dom, /* Optional, to filter */
                                     /* 感兴趣的事件 */
                                     int eventID,
                                     /* 事件发生后触发的回调 */
                                     virConnectDomainEventGenericCallback cb,
                                     void *opaque,
                                     virFreeCallback freecb);

int virConnectDomainEventDeregisterAny(virConnectPtr conn,
                                       int callbackID);
  • 事件订阅核心逻辑如下:
/* 实现生命周期事件回调逻辑,简单将事件打印出来 */
static int
gemDomainEventLifeCycleCallback(virConnectPtr conn,
                                virDomainPtr dom,
                                int event,
                                int detail,
                                void *opaque)
{
    fprintf(stderr, "%s EVENT: Domain %s(%d) %s %s\n", __func__, virDomainGetName(dom),
           virDomainGetID(dom), gemEventToString(event),
           gemEventDetailToString(event, detail));

    return 0;
}

struct domainEventData domainEvents[] = {
    DOMAIN_EVENT(VIR_DOMAIN_EVENT_ID_LIFECYCLE, gemDomainEventLifeCycleCallback),
};

int
main(int argc, char **argv)
{
    virConnectPtr dconn = NULL;
    /* 注册Libvirt默认方法实现demo程序的事件循环 */
    virEventRegisterDefaultImpl();
	/* 连接libivrtd */
    dconn = virConnectOpenAuth(argc > 1 ? argv[1] : NULL,
                               virConnectAuthPtrDefault,
                               0);
	/* 订阅事件 */
    /* register common domain callbacks */
    for (i = 0; i < G_N_ELEMENTS(domainEvents); i++) {
        struct domainEventData *event = domainEvents + i;

        event->id = virConnectDomainEventRegisterAny(dconn, NULL,
                                                     event->event,
                                                     event->cb,
                                                     strdup(event->name),
                                                     gemFreeFunc);
    }
    /* 设置超时 */
    virConnectSetKeepAlive(dconn, 5, 3);
    /* 启动事件循环 */
    while (run) {
        if (virEventRunDefaultImpl() < 0) {
            fprintf(stderr, "Failed to run event loop: %s\n",
                    virGetLastErrorMessage());
        }
    }
  • 事件订阅的核心实现是向event loop注册一个timer的回调函数virObjectEventTimer,在virConnectDomainEventRegisterAny函数逻辑中实现,但初始化该回调函数时frequency的输入为-1,表示不触发回调,直到有事件发生时才更新frequency为0。
  • demo中注册了两个timer回调:
    在这里插入图片描述
  • 注册了一个server侧有IO事件的回调函数virNetSocketEventHandle,该回调函数会级连触发virNetClientIncomingEvent,该函数会处理server发来的消息,识别是否有event的通知消息(VIR_NET_MESSAGE)到达,如果有会则更新virObjectEventTimer的频率为0,下一次事件循环时则会触发订阅事件的用户注册的回调。
    在这里插入图片描述
  • 初始化timer回调如下:
    在这里插入图片描述
  • 事件发生时更新timer频率如下:
    在这里插入图片描述
  • 我们可以看到,订阅libvirt事件demo和libvirt服务本身并不相关,当Libvirt服务事件发生时,是通过发送通知到客户端,客户端在自己的event loop中触发事件回调。

服务监听

  • 事件订阅利用libvirt提供的API实现了事件循环,并在感兴趣的事件上注册了回调。服务监听更进一步,利用libvirt提供的接口实现对网络端口的监听并提供相应的RPC服务,实现用户定制的功能。RPC服务的注册逻辑如下:
static void
gemRpcServerRegister(jrpc_server_ptr server, int port)
{
	/* 初始化rpc server,创建一个tcp socket监听端口 */
    jrpc_server_init(server, port);
    /* 注册helloworld方法 */
    jrpc_register_procedure(server, helloWorld,
                            "helloworld", NULL);
}

int jrpc_server_init(jrpc_server_ptr server, int port_number) {
    int sockfd;
    struct addrinfo hints, *servinfo, *p;
    struct sockaddr_in sockaddr;
    unsigned int len;
    int yes = 1;
    int rv;
    char PORT[6];

    memset(server, 0, sizeof(jrpc_server));
    server->port_number = port_number;

    sprintf(PORT, "%d", server->port_number);
    memset(&hints, 0, sizeof hints);
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE; // use my IP

    if ((rv = getaddrinfo(NULL, PORT, &hints, &servinfo)) != 0) {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
        return 1;
    }
	
	// loop through all the results and bind to the first we can
    for (p = servinfo; p != NULL; p = p->ai_next) {
    	/* 创建 tcp socket */
        if ((sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol))
                == -1) {
            perror("server: socket");
            continue;
        }

        if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int))
                == -1) {
            perror("setsockopt");
            exit(1);
        }
		/* 绑定对应端口 */
        if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
            close(sockfd);
            perror("server: bind");
            continue;
        }

        len = sizeof(sockaddr);
        if (getsockname(sockfd, (struct sockaddr *) &sockaddr, &len) == -1) {
            close(sockfd);
            perror("server: getsockname");
            continue;
        }
        server->port_number = ntohs( sockaddr.sin_port );

        break;
    }

    freeaddrinfo(servinfo); // all done with this structure
	/* 监听对应端口 */
    if (listen(sockfd, 5) == -1) {
        perror("listen");
        exit(1);
    }
    if (server->debug_level)
        printf("server: waiting for connections...\n");

    server->fd = sockfd;
	/* 将tcp socket fd和对应的回调实现注册到事件循环中,一旦fd上有读事件到达,event loop触发accept_cb回调 */
    if ((server->watch = virEventAddHandle(server->fd,
                         VIR_EVENT_HANDLE_READABLE,
                         accept_cb,
                         server,
                         NULL)) < 0) {
        fprintf(stderr, "failed to register accept connection callback\n");
        return 3;
    }
    return 0;
}
/* RPC 客户端连接到达的回调函数 */
static void accept_cb(int watch, int fd, int events, void *opaque) {
    jrpc_server_ptr rpc_server = opaque;
    char s[INET6_ADDRSTRLEN];
    jrpc_connection_ptr connection; 
    connection = malloc(sizeof(jrpc_connection));
    struct sockaddr_storage their_addr; // connector's address information
    socklen_t sin_size;
    sin_size = sizeof their_addr;
    /* 接受连接,得到RPC客户端的fd */
    connection->fd = accept(fd, (struct sockaddr *) &their_addr,
                            &sin_size);
    if (connection->fd == -1) {
        perror("accept"); 
        free(connection);
    } else { 
        if (rpc_server->debug_level) {
            inet_ntop(their_addr.ss_family,
                    get_in_addr((struct sockaddr *) &their_addr), s, sizeof s);
            printf("server: got connection from %s\n", s);
        }
        //copy pointer to struct jrpc_server
        connection->buffer_size = 1500;
        connection->buffer = malloc(1500);
        memset(connection->buffer, 0, 1500);
        connection->pos = 0;
        //copy debug_level, struct jrpc_connection has no pointer to struct jrpc_server
        connection->debug_level = rpc_server->debug_level;
        connection->server = rpc_server;
        /* 注册RPC客户端消息到达的回调,该回调处理客户端发来的RPC请求 */
        if ((connection->watch = virEventAddHandle(connection->fd,
                                                   VIR_EVENT_HANDLE_READABLE,
                                                   connection_cb,
                                                   connection,
                                                   NULL)) < 0) {
            perror("failed to register rpc request callback");
        }
    }
}

验证流程

  1. 编译gvm-event-monitor服务
./gvm-conf.sh -a
  1. 拷贝二进制程序gvm-event-monitor和服务并启动
cp build/gvm-event-monitor /usr/bin/
cp gvm-event-monitor.service /usr/lib/systemd/system/
systemctl start gvm-event-monitor.service
  1. 验证生命周期事件订阅
  • 在服务所在节点启动一个虚机,服务输出如下:
    在这里插入图片描述
  1. 发起helloworld RPC调用
    在这里插入图片描述

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

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

相关文章

JavaScript Day10 DOM详解

DOM DOM是JS操作网页的接口&#xff0c;全称为“文档对象模型”&#xff08;Document Object Model&#xff09;。它的作用是将网页转为一个JS对象&#xff0c;从而可以用脚本进行各种操作&#xff08;比如增删内容&#xff09;。 • 文档 – 文档表示的就是整个的HTML网页文档…

19-Linux 权限

目录 1.用户操作 1.1.创建用户 1.2.配置密码 1.3. 切换用户 2.三种角色 3.文件类型和访问权限 3.1.文件类型 3.2.基本权限 4.修改文件权限 1.用户操作 Linux下有两种用户&#xff1a; 超级用户&#xff08;root&#xff09;普通用户 超级用户&#xff1a;可以再lin…

【Cache】Redis主从复制哨兵模式集群

文章目录 一、Redis 持久化1. 主从复制2. 哨兵模式3. 集群 二、 Redis 主从复制1. 概述2. 主从复制的作用3. 主从复制流程4. 搭建 Redis 主从复制4.1 环境准备4.2 安装 Redis4.3 修改 Master 节点配置文件4.4 修改Slave节点配置文件&#xff08;Slave1和Slave2配置相同&#xf…

【vant移动端表格数据排版】用vant2简单实现一个把PC端表格数据展示在移动端的排版。上拉加载更多,下拉刷新页面,新增,编辑,删除功能

前言 上次做了一个移动端的表格功能&#xff0c;纯表格的那种。 跟PC一样&#xff0c;但是我一直觉得在移动端上写表格很糟糕的体验&#xff0c;毕竟手机就那么大。这不合理。 但是我这公司又需要把PC端的表格的数据展示在移动端。 导致我只能去试试看怎么排版比较好。由于网上…

【Qt-14】QT小知识点

1、关闭程序时报错 解决方案&#xff1a; 报这个错误可能是内存溢出&#xff0c;申请的空间与注销的空间不一致导致&#xff0c;排查了好久&#xff0c;我不是因为这个原因&#xff0c;我的问题如下&#xff0c;没有new窗体。 2、固定QT窗体大小 this->setMinimumSize(QSi…

NLP实战6:seq2seq翻译实战-Pytorch复现-小白版

目录 一、前期准备 1. 搭建语言类 2. 文本处理函数 3. 文件读取函数 二、Seq2Seq 模型 1. 编码器&#xff08;Encoder&#xff09; 2. 解码器&#xff08;Decoder&#xff09; 三、训练 1. 数据预处理 2. 训练函数 四、训练与评估 &#x1f368; 本文为[&#x1f51…

【算法集训之线性表篇】Day 02

文章目录 题目一思路分析代码实现效果 题目二思路分析代码实现效果 题目一 01.设置一个高效算法&#xff0c;将顺序表L的所有元素逆置&#xff0c;要求其空间复杂度为O(1)。 思路分析 首先&#xff0c;根据题目要求&#xff0c;空间复杂度度为O(1),则不能通过空间换时间的方…

为什么编程更关注内存而很少关注CPU?

我们知道&#xff0c;我们编写的程序&#xff0c;不管是什么编程语言&#xff0c;最后执行的时候&#xff0c;基本上都是CPU在完成。之所以说基本上&#xff0c;是因为还有GPU、FPGA等特殊情况。 但不知道大家发现没有&#xff0c;我们编程的时候&#xff0c;经常在关注内存问…

大促转化率精准预估优化论文随笔记

这是一篇阿里妈妈的论文【KDD’23 | 转化率预估新思路&#xff1a;基于历史数据复用的大促转化率精准预估】 常规的销量预测&#xff0c;遇到一些特大事件&#xff0c;直播、大促&#xff0c;一般很难预估得准确。而且现在电商机制也比较多样&#xff0c;预售、平台折扣等。 本…

初识MySQL:了解MySQL特性、体系结构以及在Linux中部署MySQL

目录 MySQL简介 MySQL特性 MySQL体系结构 SQL的四个层次&#xff1a; 连接层&#xff1a; SQL层&#xff1a; 插件式存储引擎&#xff1a; 物理文件层&#xff1a; 一条SQL语句的执行流程&#xff1a; MySQL在Linux中的安装、部署 首先需要下载mysql软件包&#xff…

月入9000+的CSGO游戏搬砖项目操作细节和疑问 ?给您一一解答

科思创业汇 大家好&#xff0c;这里是科思创业汇&#xff0c;一个轻资产创业孵化平台。赚钱的方式有很多种&#xff0c;我希望在科思创业汇能够给你带来最快乐的那一种&#xff01; 01 海外CSGO游戏搬砖项目是什么&#xff1f; csgo搬砖是在外服steam上购买包含印花枪皮等等…

9.2、增量表数据同步

1、数据通道 2、Flume配置 1&#xff09;Flume配置概述 Flume需要将Kafka中topic_db主题的数据传输到HDFS&#xff0c;故其需选用KafkaSource以及HDFSSink&#xff0c;Channel选用FileChannel。 需要注意的是&#xff0c; HDFSSink需要将不同mysql业务表的数据写到不同的路径…

2023.7.4 Dataloader切分

一、 如果文件夹路径是 path/to/folder with spaces/&#xff0c;使用以下方式输入 path/to/folder\ with\ spaces/或者使用引号包裹路径&#xff1a; "path/to/folder with spaces/"这样可以确保命令行正确解析文件夹路径&#xff0c;并将空格作为路径的一部分进…

ADB自动化测试框架

一、介绍 adb的全称为Android Debug Bridge&#xff0c;就是起到调试桥的作用&#xff0c;利用adb工具的前提是在手机上打开usb调试&#xff0c;然后通过数据线连接电脑。在电脑上使用命令模式来操作手机&#xff1a;重启、进入recovery、进入fastboot、推送文件功能等。简单来…

Intellij IDEA 初学入门图文教程(八) —— IDEA 在提交代码时 Performing Code Analysis 卡死

在使用 IDEA 开发过程中&#xff0c;提交代码时常常会在碰到代码中的 JS 文件时卡死&#xff0c;进度框上显示 Performing Code Analysis&#xff0c;如图&#xff1a; 原因是 IDEA 工具默认提交代码时&#xff0c;分析代码功能是打开的&#xff0c;需要通过配置关闭下就可以了…

Linux高性能网络编程:TCP底层的收发过程

今天探索高性能网络编程&#xff0c;但是我觉得在谈系统API之前可以先讲一些Linux底层的收发包过程&#xff0c;如下这是一个简单的socket编程代码&#xff1a; int main() {... fd socket(AF_INET, SOCKET_STREAM, 0);bind(fd, ...);listen(fd, ...);// 如何建立连接...afd …

冒泡排序法(优化与实例演示)

冒泡排序法 冒泡排序法基本介绍 冒泡排序是一种简单而经典的排序算法&#xff0c;它的原理是通过不断比较相邻元素的大小并交换位置&#xff0c;将较大&#xff08;或较小&#xff09;的元素逐渐“冒泡”到数组的末尾。这个过程持续进行多轮&#xff0c;直到整个数组按照顺序…

【Zabbix 6.0 监控系统安装和部署】

目录 一、Zabbix 介绍1、zabbix 是什么&#xff1f;2、zabbix 监控原理&#xff08;重点&#xff09;3、Zabbix 6.0 新特性4、Zabbix 6.0 功能组件1、Zabbix Server2、数据库3、Web 界面4、Zabbix Agent5、Zabbix Proxy6、Java Gateway 二、Zabbix 6.0 部署1、部署 zabbix 服务…

idea goland 插件 struct to struct

go-struct-to-struct idea goland 插件。实现自动生成 struct 间 转换代码。 https://plugins.jetbrains.com/plugin/22196-struct-to-struct/ IntelliJ plugin that Automatically generate two struct transformations through function declarations Usage define func …

【怎么实现多组输入之EOF】

C语言怎么实现多组输入之EOF C语言之EOF介绍1、什么是EOF&#xff1f;2、EOF的用法3、EOF的扩展3.1、scanf返回值之EOF3.2、scanf函数的返回值有以下几种情况 4、如何是实现多组输入&#xff1f;4.1、多组输入---- 常规写法例程14.2、多组输入---- 实现多组输入的打印例程24.3、…