当前位置: 首页 > news >正文

libevent源码学习1---创建event

libevent源码学习1—创建event

Libevent是一个用于开发可扩展性网络服务器的基于事件驱动(event-driven)模型的非阻塞网络库。安装请参考ubuntu下载安装libevent

event_base

使用 libevent 函数之前需要分配一个或者多个 event_base 结构体。每个 event_base 结构体持有一个事件集合,可以检测以确定哪个事件是激活的。

如果设置 event_base 使用锁,则可以安全地在多个线程中访问它 。然而,其事件循环只能运行在一个线程中。如果需要用多个线程检测 IO,则需要为每个线程使用一个 event_base。

每个 event_base 都有一种用于检测哪种事件已经就绪的 “方法”,或者说后端

1创建event_base:

1.1创建默认的event_base:

event_base_new() 函数分配并且返回一个新的具有默认设置的 event_base。函数会检测环境变量,返回一个到 event_base 的指针。如果发生错误,则返回 NULL。选择各种方法时,函数会选择 OS 支持的最快方法。

struct event_base *event_base_new(void);

也可以使用event_init(void)来初始化一个event_base

两个函数实现:

struct event_base *
event_init(void)
{struct event_base *base = event_base_new_with_config(NULL);if (base == NULL) {event_errx(1, "%s: Unable to construct event_base", __func__);return NULL;}current_base = base;return (base);
}struct event_base *
event_base_new(void)
{struct event_base *base = NULL;struct event_config *cfg = event_config_new();if (cfg) {base = event_base_new_with_config(cfg);event_config_free(cfg);}return base;
}

event_base这个结构体源码实现(感兴趣可以看看):

struct event_base {/** Function pointers and other data to describe this event_base's* backend. */const struct eventop *evsel;/** Pointer to backend-specific data. */void *evbase;/** List of changes to tell backend about at next dispatch.  Only used* by the O(1) backends. */struct event_changelist changelist;/** Function pointers used to describe the backend that this event_base* uses for signals */const struct eventop *evsigsel;/** Data to implement the common signal handler code. */struct evsig_info sig;/** Number of virtual events */int virtual_event_count;/** Maximum number of virtual events active */int virtual_event_count_max;/** Number of total events added to this event_base */int event_count;/** Maximum number of total events added to this event_base */int event_count_max;/** Number of total events active in this event_base */int event_count_active;/** Maximum number of total events active in this event_base */int event_count_active_max;/** Set if we should terminate the loop once we're done processing* events. */int event_gotterm;/** Set if we should terminate the loop immediately */int event_break;/** Set if we should start a new instance of the loop immediately. */int event_continue;/** The currently running priority of events */int event_running_priority;/** Set if we're running the event_base_loop function, to prevent* reentrant invocation. */int running_loop;/** Set to the number of deferred_cbs we've made 'active' in the* loop.  This is a hack to prevent starvation; it would be smarter* to just use event_config_set_max_dispatch_interval's max_callbacks* feature */int n_deferreds_queued;/* Active event management. *//** An array of nactivequeues queues for active event_callbacks (ones* that have triggered, and whose callbacks need to be called).  Low* priority numbers are more important, and stall higher ones.*/struct evcallback_list *activequeues;/** The length of the activequeues array */int nactivequeues;/** A list of event_callbacks that should become active the next time* we process events, but not this time. */struct evcallback_list active_later_queue;/* common timeout logic *//** An array of common_timeout_list* for all of the common timeout* values we know. */struct common_timeout_list **common_timeout_queues;/** The number of entries used in common_timeout_queues */int n_common_timeouts;/** The total size of common_timeout_queues. */int n_common_timeouts_allocated;/** Mapping from file descriptors to enabled (added) events */struct event_io_map io;/** Mapping from signal numbers to enabled (added) events. */struct event_signal_map sigmap;/** Priority queue of events with timeouts. */struct min_heap timeheap;/** Stored timeval: used to avoid calling gettimeofday/clock_gettime* too often. */struct timeval tv_cache;struct evutil_monotonic_timer monotonic_timer;/** Difference between internal time (maybe from clock_gettime) and* gettimeofday. */struct timeval tv_clock_diff;/** Second in which we last updated tv_clock_diff, in monotonic time. */time_t last_updated_clock_diff;#ifndef EVENT__DISABLE_THREAD_SUPPORT/* threading support *//** The thread currently running the event_loop for this base */unsigned long th_owner_id;/** A lock to prevent conflicting accesses to this event_base */void *th_base_lock;/** A condition that gets signalled when we're done processing an* event with waiters on it. */void *current_event_cond;/** Number of threads blocking on current_event_cond. */int current_event_waiters;
#endif/** The event whose callback is executing right now */struct event_callback *current_event;#ifdef _WIN32/** IOCP support structure, if IOCP is enabled. */struct event_iocp_port *iocp;
#endif/** Flags that this base was configured with */enum event_base_config_flag flags;struct timeval max_dispatch_time;int max_dispatch_callbacks;int limit_callbacks_after_prio;/* Notify main thread to wake up break, etc. *//** True if the base already has a pending notify, and we don't need* to add any more. */int is_notify_pending;/** A socketpair used by some th_notify functions to wake up the main* thread. */evutil_socket_t th_notify_fd[2];/** An event used by some th_notify functions to wake up the main* thread. */struct event th_notify;/** A function used to wake up the main thread from another thread. */int (*th_notify_fn)(struct event_base *base);/** Saved seed for weak random number generator. Some backends use* this to produce fairness among sockets. Protected by th_base_lock. */struct evutil_weakrand_state weakrand_seed;/** List of event_onces that have not yet fired. */LIST_HEAD(once_event_list, event_once) once_events;
};

1.2 创建复杂的event_base

要对取得什么类型的 event_base 有更多的控制,就需要使用 event_config

event_config 是一个容纳 event_base 配置信息的结构体。需要 event_base 时,将 event_config 传递给event_base_new_with_config ()。

/** Internal structure: describes the configuration we want for an event_base* that we're about to allocate. */
struct event_config {TAILQ_HEAD(event_configq, event_config_entry) entries;int n_cpus_hint;struct timeval max_dispatch_interval;int max_dispatch_callbacks;int limit_callbacks_after_prio;enum event_method_feature require_features;enum event_base_config_flag flags;
};

创建接口

struct event_config *event_config_new(void);
struct event_base *event_base_new_with_config(const struct event_config *cfg);
void event_config_free(struct event_config *cfg);

要使用这些函数分配 event_base,先调用 event_config_new()分配一个 event_config。 然后,对 event_config 调用其它函数,设置所需要的 event_base 特征。最后,调用 event_base_new_with_config()获取新的 event_base。完成工作后,使用 event_config_free ()释放 event_config

int event_config_avoid_method(struct event_config *cfg, const char *method);
enum event_method_feature {/** Require an event method that allows edge-triggered events with EV_ET. */EV_FEATURE_ET = 0x01,/** Require an event method where having one event triggered among* many is [approximately] an O(1) operation. This excludes (for* example) select and poll, which are approximately O(N) for N* equal to the total number of possible events. */EV_FEATURE_O1 = 0x02,/** Require an event method that allows file descriptors as well as* sockets. */EV_FEATURE_FDS = 0x04,/** Require an event method that allows you to use EV_CLOSED to detect* connection close without the necessity of reading all the pending data.** Methods that do support EV_CLOSED may not be able to provide support on* all kernel versions.**/EV_FEATURE_EARLY_CLOSE = 0x08
};int event_config_require_features(struct event_config *cfg,enum event_method_feature feature);enum event_base_config_flag {/** Do not allocate a lock for the event base, even if we havelocking set up.Setting this option will make it unsafe and nonfunctional to callfunctions on the base concurrently from multiple threads.*/EVENT_BASE_FLAG_NOLOCK = 0x01,/** Do not check the EVENT_* environment variables when configuringan event_base  */EVENT_BASE_FLAG_IGNORE_ENV = 0x02,/** Windows only: enable the IOCP dispatcher at startupIf this flag is set then bufferevent_socket_new() andevconn_listener_new() will use IOCP-backed implementationsinstead of the usual select-based one on Windows.*/EVENT_BASE_FLAG_STARTUP_IOCP = 0x04,/** Instead of checking the current time every time the event loop isready to run timeout callbacks, check after each timeout callback.*/EVENT_BASE_FLAG_NO_CACHE_TIME = 0x08,/** If we are using the epoll backend, this flag says that it issafe to use Libevent's internal change-list code to batch upadds and deletes in order to try to do as few syscalls aspossible.  Setting this flag can make your code run faster, butit may trigger a Linux bug: it is not safe to use this flagif you have any fds cloned by dup() or its variants.  Doing sowill produce strange and hard-to-diagnose bugs.This flag can also be activated by setting theEVENT_EPOLL_USE_CHANGELIST environment variable.This flag has no effect if you wind up using a backend other thanepoll.*/EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST = 0x10,/** Ordinarily, Libevent implements its time and timeout code usingthe fastest monotonic timer that we have.  If this flag is set,however, we use less efficient more precise timer, assuming one ispresent.*/EVENT_BASE_FLAG_PRECISE_TIMER = 0x20
};int event_config_set_flag(struct event_config *cfg,enum event_base_config_flag flag);

调用 event_config_avoid_method ()在配置中输入应避免的事件方法。这可用于避免不支持某些的事件机制文件描述符类型,或用于调试以避免某些事件机制。 应用程序可以利用多个事件基础来适应不兼容的文件描述符类型。

调用 event_config_require_feature ()输入应用程序所需的事件方法功能。

调用 event_config_set_flag()让 libevent 在创建 event_base 时设置一个或者多个将在下面介绍的运行时标志。

event_config_require_features()可识别的特征值有:

  • EV_FEATURE_ET:要求支持边沿触发
  • EV_FEATURE_O1:要求添加、删除单个事件,或者确定哪个事件激活的操作是 O(1)复杂度
  • EV_FEATURE_FDS:要求支持任意文件描述符,而不仅仅是套接字
  • EV_FEATURE_EARLY_CLOSE : 需要一个事件方法,该方法允许您使用 EV_CLOSED 来检测连接关闭,而无需读取所有挂起的数据。

event_config_set_flag()可识别的选项值有:

  • EVENT_BASE_FLAG_NOLOCK:不要为 event_base 分配锁。设置这个选项可以 为 event_base 节省一点用于锁定和解锁的时间,但是让在多个线程中访问 event_base 成为不安全的。
  • EVENT_BASE_FLAG_IGNORE_ENV:配置event_base时,请勿检查 EVENT_* 环境变量。使用这个标志需要三思:这会让用户更难调试你的程序与 libevent 的交互。
  • EVENT_BASE_FLAG_STARTUP_IOCP:仅用于 Windows,让 libevent 在启动时就 启用任何必需的 IOCP 分发逻辑,而不是按需启用。
  • EVENT_BASE_FLAG_NO_CACHE_TIME:不是在事件循环每次准备执行超时回调时 检测当前时间,而是在每次超时回调后进行检测。注意:这会消耗更多的 CPU时间
  • EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST:如果我们使用epoll后端,这个标志表示它是可以安全地使用Libevent的内部change-list代码进行批处理添加和删除,以便尽可能少地执行系统调用,设置此标志可以使代码运行得更快,但是它可能会触发一个Linux错误:使用这个标志是不安全的如果您有任何由dup()或其变体克隆的FDS。这样做会产生奇怪且难以诊断的bug。此标志也可以通过设置
    EVENT_EPOLL_USE_CHANGELIST环境变量。如果您最终使用的后端不是epoll。

上述操作 event_config 的函数都在成功时返回0,失败时返回-1。

2 检查event_base后端

有时候需要检查 event_base 支持哪些特征,或者当前使用哪种方法。

接口1

const char **event_get_supported_methods(void);

event_get_supported_methods()函数返回一个指针 ,指向 libevent 支持的方法名字数组 。 这个数组的最后一个元素是 NULL。

实例:

const char **methods = event_get_supported_methods();
printf("Starting Libevent %s.  Available methods are:\n", event_get_version());
for (int i = 0; methods[i] != NULL; ++i)
{printf("%s\n", methods[i]);
}

输出:

Starting Libevent 2.1.12-stable.  Available methods are:
epoll
poll
select

接口2

const char *event_base_get_method(const struct event_base *base);
enum event_method_feature event_base_get_features(const struct event_base *base);

event_base_get_method()返回 event_base 正在使用的方法。

event_base_get_features ()返回 event_base 支持的特征的比特掩码。

实例:

struct event_base *base = event_base_new();
enum event_method_feature f;
if (!base)
{printf("Couldn't get an event_base!\n");
}
else
{printf("Using Libevent with backend method is %s.\n", event_base_get_method(base));f = (event_method_feature)event_base_get_features(base);if ((f & EV_FEATURE_ET))printf("Edge-triggered events are supported.\n");if ((f & EV_FEATURE_O1))printf("O(1) event notification is supported.\n");if ((f & EV_FEATURE_FDS))printf("All FD types are supported.\n");
}

本机输出结果:

Using Libevent with backend method is epoll.
Edge-triggered events are supported.
O(1) event notification is supported.

3 释放event_base

使用完 event_base 之后,使用 event_base_free()进行释放。

void event_base_free(struct event_base *base);

4 event_base优先级

libevent支持为事件设置多个优先级。然而, event_base默认只支持单个优先级。可以调用 event_base_priority_init()设置 event_base 的优先级数目。

int event_base_priority_init(struct event_base *base, int n_priorities);

成功时这个函数返回 0,失败时返回 -1。base 是要修改的 event_base,n_priorities 是要支持的优先级数目,这个数目至少是 1 。每个新的事件可用的优先级将从 0 (最高) 到 n_priorities-1(最低)。

常量 EVENT_MAX_PRIORITIES 表示 n_priorities 的上限。

// 源码
#define EVENT_MAX_PRIORITIES 256

5 event_base和fork

不是所有事件都在调用 fork()之后可以正确工作。所以,如果在使用 fork()或者其他相关系统调用启动新进程之后,希望在新进程中继续使用 event_base,就需要进行重新初始化。

int event_reinit(struct event_base *base);

未完待续。。。
收藏+关注,后续继续更新

相关文章:

libevent源码学习1---创建event

libevent源码学习1—创建event Libevent是一个用于开发可扩展性网络服务器的基于事件驱动(event-driven)模型的非阻塞网络库。安装请参考ubuntu下载安装libevent event_base 使用 libevent 函数之前需要分配一个或者多个 event_base 结构体。每个 event_base 结构体持有一个…...

Python类的设计

Python类的设计 # 定义一个闹钟类 class Clock:__cureen_keyNone # 私有成员不能改变和使用def __init__(self, id, price): # 类对象是立即自动执行self.id idself.price pricedef ring(self):import winsound # 内置声音方法winsound.Beep(2000,3000)clock1 Clock(…...

微信小程序的项目解构

视频链接 黑马程序员前端微信小程序开发教程,微信小程序从基础到发布全流程_企业级商城实战(含uni-app项目多端部署)_哔哩哔哩_bilibili 接口文档 https://www.escook.cn/docs-uni-shop/mds/1.start.html 1:微信小程序宿主环境 1:常见的宿…...

【Archaius技术专题】「Netflix原生态」动态化配置服务之微服务配置组件变色龙

前提介绍 如果要设计开发一套微服务基础架构,参数化配置是一个非常重要的点,而Netflix也开源了一个叫变色龙Archaius的配置中心客户端,而且Archaius可以说是比其他客户端具备更多生产级特性,也更灵活。*在NetflixOSS微服务技术栈…...

python条件分支和循环语句

python中没有{}的写法,一般时通过缩进的方式来确定分支和循环需要执行的代码块。 if 需要判断的条件表达式:条件成立时的动作 elif 需要判断的条件表达式:条件成立时的动作 else:动作for 变量 in 迭代对象:动作 示例: while 退出条件:动作...

工具推荐:Wireshark网络协议分析工具(对比tcpdump)

文章首发地址 Wireshark是一款开源的网络协议分析工具,可以捕获网络数据包并对其进行详细的分析和解释。下面是Wireshark的详细介绍: Wireshark 工作原理 Wireshark通过捕获网络接口上的数据包,将其转换为可读的格式,并在界面…...

[OnWork.Tools]系列 04-快捷启动

简介 主要功能是将常用的软件拖动到软件中,实现快速点击启动,结合软件设置中的设置的快捷键,可以快速呼出对应的面板,使用快捷键快速启动应用 拖拽内容 拖拽快捷方式到面板,双击快速打开 拖拽文件方式到面板,双击快速打开 拖拽文件夹到面板双击快速打开 拖拽项目调整顺序 右…...

如何将项目挂后台运行?【nohup和tmux】

挂后台运行,防止霸屏。 线上的程序不会将日志输出到控制台,而是输出到日志文件,方便运维查阅信息。 一.nohup--挂后台运行的命令 //nohup--英文全称no hang up,可以后台运行指定命令 //hello.log是指将日志输出到hello.log文件 …...

什么是进程、线程、协程

什么是进程? 我们都知道计算机的核心是CPU,它承担了所有的计算任务;而操作系统是计算机的管理者,它负责任务的调度、资源的分配和管理,统领整个计算机硬件;应用程序则是具有某种功能的程序,程序…...

Python爬虫——selenium_访问元素信息

from selenium import webdriver# 创建浏览器对象 path files/chromedriver.exe browser webdriver.Chrome(path)# 访问地址 url https://www.baidu.com browser.get(url)input browser.find_element_by_id(su)获取元素属性 .get_attribute(class)print(input.get_attribu…...

Linux 文件基本属性

Linux 文件基本属性 Linux 系统是一种典型的多用户系统,不同的用户处于不同的地位,拥有不同的权限。 为了保护系统的安全性,Linux 系统对不同的用户访问同一文件(包括目录文件)的权限做了不同的规定。 在 Linux 中我…...

CSS 盒模型是什么?它包含哪些属性?标准盒模型/怪异盒模型

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 盒模型⭐ 标准盒模型⭐ 怪异盒模型⭐ 写在最后 ⭐ 专栏简介 前端入门之旅:探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅!这个专栏是为那些对Web开发感…...

VB+SQL光盘信息管理系统设计与实现

摘 要 我的毕业设计课题为“光盘管理系统”,该系统完成光盘相关信息的输入、保存和维护、是按照方便用户、容易操作、确保数据一致完整的原则进行设计。这次毕业设计的开发工具是Visual Basic 6.0,操作平台是Windows2000 Professional中文版,选用的数据库后台是SQL server2…...

MySQL5.7数据库、Navicat Premium1.6可视化工具安装教程【详细教程】

文章目录 一、MySQL、Navicat、注册机地址二、安装(一)、MySQL安装(二)、Navicat Premium安装(三)、集活Navicat Premium 三、遇到的问题1、Are you sure your navicat has not beenpatched/modified befor…...

JVM 调优实例

点击下方关注我,然后右上角点击...“设为星标”,就能第一时间收到更新推送啦~~~ JVM提供了多种垃圾回收器,可以根据应用程序的需求选择最适合的垃圾回收器。例如,如果应用程序需要更快的响应时间,可以选择并行垃圾回收…...

Python numpy中的correlate相关性详解

看代码看见这个方法,记录一下,这个是人家官网的链接np.correlate 云里雾里的,其实就是两个数组点乘,不同模式就是错位点乘,直接看代码 a是原本的数组,v就是滤波器,对应相乘 import numpy as …...

用python实现xmind用例转换为excel/csv用例

from xmindparser import xmind_to_dict from openpyxl import Workbook# 解析XMind文件 xmind_file path/to/xmind/file.xmind xmind_data xmind_to_dict(xmind_file)# 创建Excel文件 excel_file path/to/excel/file.xlsx wb Workbook() ws wb.active# 定义用例表格的列名…...

论文浅尝 | 面向多步推理任务专业化较小语言模型

笔记整理:张沈昱,东南大学硕士,研究方向为自然语言处理 链接:https://github.com/FranxYao/FlanT5-CoT-Specialization 动机 本文的动机是探索如何在多步推理任务中通过大型语言模型提升较小的语言模型的性能。作者认为&#xff0…...

基于Java的新闻全文搜索引擎的设计与实现

中文摘要 本文以学术研究为目的,针对新闻行业迫切需求和全文搜索引擎技术的优越性,设计并实现了一个针对新闻领域的全文搜索引擎。该搜索引擎通过Scrapy网络爬虫工具获取新闻页面,将新闻内容存储在分布式存储系统HBase中,并利用倒…...

golang 自定义exporter - 端口连接数 portConnCount_exporter

需求: 1、计算当前6379 、3306 服务的连接数 2、可prometheus 语法查询 下面代码可直接使用: 注: 1、windows 与linux的区分 第38行代码 localAddr : fields[1] //windows为fields[1] , linux为fields[3] 2、如需求 增加/修改/删除…...

Cursor实现用excel数据填充word模版的方法

cursor主页:https://www.cursor.com/ 任务目标:把excel格式的数据里的单元格,按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例,…...

基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容

基于 ​UniApp + WebSocket​实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配​微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...

JVM垃圾回收机制全解析

Java虚拟机(JVM)中的垃圾收集器(Garbage Collector,简称GC)是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象,从而释放内存空间,避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...

cf2117E

原题链接&#xff1a;https://codeforces.com/contest/2117/problem/E 题目背景&#xff1a; 给定两个数组a,b&#xff0c;可以执行多次以下操作&#xff1a;选择 i (1 < i < n - 1)&#xff0c;并设置 或&#xff0c;也可以在执行上述操作前执行一次删除任意 和 。求…...

Mac软件卸载指南,简单易懂!

刚和Adobe分手&#xff0c;它却总在Library里给你写"回忆录"&#xff1f;卸载的Final Cut Pro像电子幽灵般阴魂不散&#xff1f;总是会有残留文件&#xff0c;别慌&#xff01;这份Mac软件卸载指南&#xff0c;将用最硬核的方式教你"数字分手术"&#xff0…...

多模态大语言模型arxiv论文略读(108)

CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题&#xff1a;CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者&#xff1a;Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...

【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具

第2章 虚拟机性能监控&#xff0c;故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令&#xff1a;jps [options] [hostid] 功能&#xff1a;本地虚拟机进程显示进程ID&#xff08;与ps相同&#xff09;&#xff0c;可同时显示主类&#x…...

MySQL 8.0 事务全面讲解

以下是一个结合两次回答的 MySQL 8.0 事务全面讲解&#xff0c;涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容&#xff0c;并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念&#xff08;ACID&#xff09; 事务是…...

Chromium 136 编译指南 Windows篇:depot_tools 配置与源码获取(二)

引言 工欲善其事&#xff0c;必先利其器。在完成了 Visual Studio 2022 和 Windows SDK 的安装后&#xff0c;我们即将接触到 Chromium 开发生态中最核心的工具——depot_tools。这个由 Google 精心打造的工具集&#xff0c;就像是连接开发者与 Chromium 庞大代码库的智能桥梁…...

淘宝扭蛋机小程序系统开发:打造互动性强的购物平台

淘宝扭蛋机小程序系统的开发&#xff0c;旨在打造一个互动性强的购物平台&#xff0c;让用户在购物的同时&#xff0c;能够享受到更多的乐趣和惊喜。 淘宝扭蛋机小程序系统拥有丰富的互动功能。用户可以通过虚拟摇杆操作扭蛋机&#xff0c;实现旋转、抽拉等动作&#xff0c;增…...