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

【Linux后端服务器开发】poll/epoll多路转接IO服务器

目录

一、poll原理

二、poll实现多路转接IO服务器

三、epoll函数接口

四、epoll的工作原理

五、epoll实现多路转接IO服务器


一、poll原理

poll函数接口

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);// pollfd结构
struct pollfd 
{int fd;         /* file descriptor */short events;   /* requested events */short revents;  /* returned events */
}
  • fds是一个poll函数监听的结构列表,每一个元素,包含了三部分内容:文件描述符、监听事件的集合、返回的事件集合
  • nfds表示fds数组的长度
  • timeout表示poll函数的超时时间,单位是毫秒:①timeout > 0 ,timeout时间以内阻塞等待,否则非阻塞返回一次;②timeout == 0,非阻塞等待;③timeout < 0,阻塞等待
events和revents的取值
事件描述是否可作为输入是否可作为输出
POLLIN数据(普通数据、优先数据)可读
POLLRDNORM普通数据可读
POLLRDBAND优先级带数据可读(Linux不支持)
POLLPRI高优先级数据可读,如TCP带外数据
POLLOUT数据(普通数据、优先数据)可写 是
POLLWRNORM普通数据可写
POLLWRBAND优先级带数据可写
POLLRDHUPTCP连续被对方关闭或对方关闭了写操作,它由GNU引入
POLLERR错误
POLLHUP挂起。比如通道的写端被关闭后,读端描述符上将收到POLLHUP事件
POLLVNAL文件描述符没有打开
  • 返回值小于0,表示出错
  • 返回值等于0,表示poll函数等待超时
  • 返回值大于0,表示poll用于监听的文件描述符就绪而返回

poll的优点

poll多路转接的提出是为了解决select存在的部分缺陷,不同于select使用三个位图结构来表示三个fd_set的方式,poll使用一个pollfd的指针实现:

  • pollfd结构包含了要监视的event和发生的event,不再使用select“参数-值”传递的方式,接口使用比select方便,不需要每次调用都重新设置需要关心的fd
  • events参数:用户告诉内核你需要帮我监视哪些fd
  • revents参数:内核告诉用户哪些正在监视的fd已经就绪了
  • poll并没有最大数量限制(但是最大数量限制是由系统能接受的最大数量决定,监视的fd数量过大时性能也会下降)

poll的缺点

poll中监听的文件描述符增多时:

  • 和select一样,poll返回后,需要轮询所有pollfd来获取就绪的描述符
  • 每次调用poll都需要把大把的pollfd结构从用户态拷贝到内核中
  • 同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监听的文件描述符数量的增长,其效率也会线性下降

二、poll实现多路转接IO服务器

Log.hpp、Sock.hpp、main.cc与select中的多路转接是一样的:【Linux后端服务器开发】select多路转接IO服务器_命运on-9的博客-CSDN博客

PollServer.hpp

#pragma once#include <iostream>
#include <string>
#include <functional>
#include <poll.h>#include "Sock.hpp"using namespace std;static const int g_defaultport = 8080;
static const int g_num = 2024;
static const int g_defaultfd = -1;using func_t = function<string (const string&)>;class PollServer
{
public:PollServer(func_t f, int port = g_defaultport): _func(f), _port(port), _listensock(-1), _rfds(nullptr){}void Init(){_listensock = Sock::Socket();Sock::Bind(_listensock, _port);Sock::Listen(_listensock);_rfds = new struct pollfd[g_num];for (int i = 0; i < g_num; ++i)Reset_Item(i);_rfds[0].fd = _listensock;_rfds[0].events = POLLIN;}void Reset_Item(int i){_rfds[i].fd = g_defaultfd;_rfds[i].events = 0;_rfds[i].revents = 0;}void Print_Rfds(){cout << "rfd list: ";for (int i = 0; i < g_num; ++i)if (_rfds[i].fd != g_defaultfd)cout << _rfds[i].fd << " ";cout << endl;}void Accepter(int listensock){Log_Message(DEBUG, "Accepter in");string clientip;uint16_t clientport = 0;int sock = Sock::Accept(listensock, &clientip, &clientport);if (sock < 0)return;Log_Message(NORMAL, "accept success [%s: %d]", clientip.c_str(), clientport);int i = 0;for (; i < g_num; ++i){if (_rfds[i].fd != g_defaultfd)continue;elsebreak;}if (i == g_num){Log_Message(WARNING, "server is full, please wait");close(sock);}else{_rfds[i].fd = sock;_rfds[i].events = POLLIN;_rfds[i].revents = 0;}Print_Rfds();Log_Message(DEBUG, "Accepter out");}void Recver(int pos){Log_Message(DEBUG, "in Recver");// 1. 读取requestchar buffer[1024];ssize_t s = recv(_rfds[pos].fd, buffer, sizeof(buffer) - 1, 0);if (s > 0){buffer[s] = 0;Log_Message(NORMAL, "client# %s", buffer);}else if (s == 0){close(_rfds[pos].fd);Reset_Item(pos);Log_Message(NORMAL, "client quit");return;}else{close(_rfds[pos].fd);Reset_Item(pos);Log_Message(ERROR, "client quit: %s", strerror(errno));return;}// 2. 处理requeststring response = _func(buffer);// 3. 返回responsewrite(_rfds[pos].fd, response.c_str(), response.size());Log_Message(DEBUG, "out Recver");}void Handler_Read_Event(){for (int i = 0; i < g_num; ++i){// 过滤掉非法的fdif (_rfds[i].fd == g_defaultfd)continue;if (!(_rfds[i].events & POLLIN))continue;if (_rfds[i].fd == _listensock && (_rfds[i].revents & POLLIN))Accepter(_listensock);else if (_rfds[i].revents & POLLIN)Recver(i);}}void Start(){int timeout = -1;while (1){int n = poll(_rfds, g_num, timeout);switch (n){case 0:Log_Message(NORMAL, "timeout ...");break;case -1:Log_Message(WARNING, "poll error, code:%d, err string: %s", errno, strerror(errno));break;default:Log_Message(NORMAL, "have event ready!");Handler_Read_Event();break;}}}~PollServer(){if (_listensock < 0)close(_listensock);if (_rfds)delete[] _rfds;}private:int _port;int _listensock;struct pollfd* _rfds;func_t _func;
};

三、epoll函数接口

epoll的官方说法是为了处理大批量句柄而做了改进的poll,但是在底层原理的设计上,epoll与poll有着天差地别的区别(老婆与老婆饼的关系),epoll的设计远远优于poll。

它是在2.5.44内核中被引进的(epoll(4) is a new API introduced in Linux kernel 2.5.44),几乎弥补了前两种多路转接接口(select、poll)的所有缺陷,被公认为Linux2.6之后性能最好的多路I/O就绪通知方法。

epoll_create

int epoll_create(int size);

创建一个epoll模型,自从2.6.8之后size参数是被忽略的,用完之后必须调用close关闭。

epoll_ctl

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

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

第一个参数是epoll_create()的返回值(epoll模型),第二个参数表示动作(用三个宏表示),第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事。

第二个参数的取值:①EPOLL_CTL_ADD,注册新的fd到epfd中;②EPOLL_CTL_MOD,修改已经注册的fd监听事件;③EPOLL_CTL_DEL,从epfd中删除一个fd。

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是超时时间(ms,0会立即返回,-1是阻塞等待)
  • 如果函数调用成功,返回I/O已经准备好的文件描述符数目,如返回0表示已超时,返回值小于0表示报错

struct epoll_event结构

typedef union epoll_data
{void *ptr;int fd;uint32_t u32;uint64_t u64;
} epoll_data_t;struct epoll_event
{uint32_t events;	    /* Epoll events */epoll_data_t data;	/* User data variable */
} __EPOLL_PACKED;

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

  • EPOLLIN:表示对应的文件描述符可以读(包括对端socket的正常关闭)
  • EPOLLOUT:表示对应的文件描述符可以写
  • EPOLLPRI:表示对应的文件描述符有紧急的数据可读(表示有外带数据到来)
  • EPOLLERR:表示对应的文件描述符发生错误
  • EPOLLHUP:表示对应的文件描述符被挂掉
  • EPOLLET:将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的
  • EPOLLONESHOP:只监听一次事件,当监听完这次事件后,如果如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

四、epoll的工作原理

  1. 通过epoll_create()创建epoll模型
  2. 通过epoll_ctl()将需要监视的文件描述符告诉OS,由OS维护红黑树进行监视
  3. 当数据从硬件层传入网卡驱动再写入操作系统中监视的文件缓冲区,会通过每个文件的回调机制将受监视的fd节点传入就绪队列中
  4. 上层直接对就绪队列进行事件处理,而不用对所有监视进行遍历

什么是事件就绪?底层的IO条件满足了,可以进行某种IO行为了,即事件就绪。

select / poll / epoll  ---->   IO事件的通知机制(等)

事件的通知有什么策略呢?LT工作模式和ET工作模式

关于事件的通知,这里有一个取快递的示例:

假设你网购了一些东西,快递到了,这时快递员张三就把快递送到你家楼下,打电话通知你让你下去取快递,你不下去他就一直给你打,直到你去将你的所有快递取完了他才离开。

之后你又网购了一些东西,快递到了,这时换了一个快递员变成李四了,他也将快递送到了你家楼下,但是他不会一直给你打电话,只要你接了电话他就离开了,也不管你有没有下来取快递。

这两种不同的通知策略,对应的就是LT水平触发模式和ET边缘触发模式,张三是LT,李四是ET。

epoll如何进行事件通知呢?通过文件的回调机制将红黑树中的就绪节点添加到就绪队列中

  • LT水平触发:只要就绪队列中的数据没有处理完,epoll就会一直通知用户
  • ET边缘触发:只要epoll将数据放入就绪队列里了,无论用户有没有将就绪队列里的数据读完,epoll就不再通知用户,除非底层的数据变化(数据增多),才会再次进行通知

epoll默认的是LT工作模式,我们也可以设置events将其改为ET模式。

为什么ET模式必须是非阻塞读取呢?

ET模式   --->   底层只有数据从无到有、从有到多变化的时候,才会进行通知上层   --->   只会通知一次   --->   倒逼程序员将本轮就绪的数据全部读取完   --->   你怎么知道你把就绪的数据全部读取完毕了呢?循环读取,直到读取不到数据了   --->   一般的fd,是阻塞式读取,但是ET模式下,必须是非阻塞读取,防止最后一次因读取不到数据而阻塞

为什么ET模式更高效?因为ET高效不仅体现在通知机制上,还会倒逼程序一次把就绪数据读取完,TCP通信时让接收方可以给发送方提供一个更大的窗口大小,即让对端更新出一个更大的滑动窗口,提高网络通信的数据吞吐量。

所以TCP中PSH标志的作用?让底层就绪事件,再次通知给上层。

五、epoll实现多路转接IO服务器

Log.hpp、Sock.hpp、main.cc与poll版本的一样

EpollServer.hpp

#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <functional>
#include <sys/epoll.h>#include "Sock.hpp"using namespace std;static const int g_defaultport = 8080;
static const int g_size = 2024;
static const int g_defaultvalue = -1;
static const int g_defaultnum = 64;using func_t = function<string (const string&)>;class EpollServer
{
public:EpollServer(func_t f, uint16_t port = g_defaultport, int num = g_defaultnum): _func(f), _num(num), _revs(nullptr), _port(port), _listensock(g_defaultvalue), _epfd(g_defaultvalue){}void Init(){// 1. 创建socket_listensock = Sock::Socket();Sock::Bind(_listensock, _port);Sock::Listen(_listensock);// 2. 创建epoll模型_epfd = epoll_create(g_size);if (_epfd < 0){Log_Message(FATAL, "epoll create error: %s", strerror(errno));exit(EPOLL_CREATE_ERR);}// 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];Log_Message(NORMAL, "init server success");}void Handler_Event(int ready_num){Log_Message(DEBUG, "Handler_Event in");// 遍历就绪队列for (int i = 0; i < ready_num; ++i){uint32_t events = _revs[i].events;int sock = _revs[i].data.fd;if (sock == _listensock && (events & EPOLLIN)){// _listensock事件就绪,建立新连接string clientip;uint16_t clientport;int fd = Sock::Accept(sock, &clientip, &clientport);if (fd < 0){Log_Message(WARNING, "accept error");continue;}// 获取fd成功,可以直接读取吗?不可以,放入epollstruct 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];int n = recv(sock, buffer, sizeof(buffer), 0);if (n > 0){buffer[n] = 0;Log_Message(NORMAL, "client say# %s", buffer);// TODOstring response = response = _func(buffer);send(sock, response.c_str(), response.size(), 0);}else if (n == 0){// 先从epoll移除,再close fdepoll_ctl(_epfd, EPOLL_CTL_DEL, sock, nullptr);close(sock);Log_Message(NORMAL, "client quit");}else{epoll_ctl(_epfd, EPOLL_CTL_DEL, sock, nullptr);close(sock);Log_Message(ERROR, "recv error, code: %d, errstring: %s", errno, strerror(errno));}}}Log_Message(DEBUG, "Handler_Event out");}void Start(){int timeout = -1;while (1){int n = epoll_wait(_epfd, _revs, _num, timeout);switch (n){case 0:Log_Message(NORMAL, "timeout ...");break;case -1:Log_Message(WARNING, "epoll_wait failed, code: %d, err string: %s", errno, strerror(errno));break;default:Log_Message(NORMAL, "have event ready");Handler_Event(n);break;}}}private:uint16_t _port;int _listensock;int _epfd;                      // epoll模型struct epoll_event* _revs;      // 就绪队列int _num;func_t _func;
};

相关文章:

【Linux后端服务器开发】poll/epoll多路转接IO服务器

目录 一、poll原理 二、poll实现多路转接IO服务器 三、epoll函数接口 四、epoll的工作原理 五、epoll实现多路转接IO服务器 一、poll原理 poll函数接口 #include <poll.h> int poll(struct pollfd *fds, nfds_t nfds, int timeout);// pollfd结构 struct pollfd …...

【设计模式——学习笔记】23种设计模式——命令模式Command(原理讲解+应用场景介绍+案例介绍+Java代码实现)

文章目录 案例引入介绍基础介绍登场角色 案例实现案例一实现 案例二介绍实现拓展 命令模式在JdbcTemplate源码中的应用总结文章说明 案例引入 有一套智能家电&#xff0c;其中有照明灯、风扇、冰箱、洗衣机&#xff0c;这些智能家电来自不同的厂家&#xff0c;我们不想针对每一…...

Rust中的高吞吐量流处理

本篇文章主要介绍了Rust中流处理的概念、方法和优化。作者不仅介绍了流处理的基本概念以及Rust中常用的流处理库&#xff0c;还使用这些库实现了一个流处理程序。 最后&#xff0c;作者介绍了如何通过测量空闲和阻塞时间来优化流处理程序的性能&#xff0c;并将这些内容同步至…...

探索编程世界的宝藏:程序员必掌握的20大算法

文章目录 1 引言2 冒泡排序算法&#xff1a;编程世界的排序魔法 &#x1f9d9;‍♀️&#x1f522;3 选择排序算法&#xff1a;排序世界的精确挑选器 &#x1f3af;&#x1f522;4 插入排序算法&#xff1a;排序世界的巧妙插珠者 ✨&#x1f522;5 快速排序算法&#xff1a;排序…...

Android NFC通信示例

前言 近距离无线通信 (NFC) 是一组近距离无线技术&#xff0c;通常只有在距离不超过 4 厘米时才能启动连接。借助 NFC&#xff0c;您可以在 NFC 标签与 Android 设备之间或者两台 Android 设备之间共享小型负载。 支持 NFC 的 Android 设备同时支持以下三种主要操作模式&…...

2023年08月IDE流行度最新排名

点击查看最新IDE流行度最新排名&#xff08;每月更新&#xff09; 2023年08月IDE流行度最新排名 顶级IDE排名是通过分析在谷歌上搜索IDE下载页面的频率而创建的 一个IDE被搜索的次数越多&#xff0c;这个IDE就被认为越受欢迎。原始数据来自谷歌Trends 如果您相信集体智慧&am…...

使用Beego和MySQL实现帖子和评论的应用,并进行接口测试(附源码和代码深度剖析)

文章目录 小项目介绍源码分析main.gorouter.gomodels/user.gomodels/Post.gomodels/comment.gocontrollers/post.gocontrollers/comment.go 接口测试测试增加帖子测试查看帖子测试增加评论测试查看评论 小项目介绍 经过对需求的分析&#xff0c;我增加了一些额外的东西&#x…...

物联网潜在的巨大价值在于大数据分析

物联网潜在的巨大价值在于大数据分析 从数据里去挖掘市场或者用户的精准需求。 往小的说&#xff0c;后台可以统计用户家里各各插座一年甚至更久的用电情况&#xff0c;这些数据也可以通过app或者小程序展现给用户。 用户可以很直观看到自己一年的用电情况&#xff0c;哪个家…...

SSL原理详解

SSL协议结构&#xff1a; SSL协议分为两层&#xff0c;下层为SSL记录协议&#xff0c;上层为SSL握手协议、SSL密码变化协议和SSL警告协议。 1.下层为SSL记录协议&#xff0c;主要作用是为高层协议提供基本的安全服务 建立在可靠的传输之上&#xff0c;负责对上层的数据进行分块…...

linux下的etc目录代表什么意思

在Linux系统中&#xff0c;/etc目录是一个非常重要的目录&#xff0c;它包含了系统的配置文件和相关的配置信息。下面是一些/etc目录中常见的文件和目录&#xff1a; 1. /etc/passwd&#xff1a;此文件包含了所有用户账户的信息&#xff0c;包括用户名、用户ID、用户所属的组I…...

iOS 两种方式设置状态栏

1、ios9.0以前设置状态栏字体颜色 ///白色 [[UIApplication sharedApplication]setStatusBarStyle:UIStatusBarStyleLightContent]; ///黑色 [[UIApplication sharedApplication]setStatusBarStyle:UIStatusBarStyleDefault]; 会看到如下提示&#xff1a; setStatusBarSty…...

html5:webSocket 基础使用

一、理解 HTML5 WebSocket HTML5 WebSocket是一种新型的网络协议&#xff0c;它能够在客户端和服务器之间建立实时的双向通信通道&#xff0c;使得浏览器和服务器之间的数据传输更加高效、快速和可靠。相比传统的HTTP协议&#xff0c;WebSocket协议使用更少的网络开销&#xf…...

html学习10-----总结(完)

<!DOCTYPE html> <html><head><meta charset"utf-8"/><title>html总结</title></head><body><h1>HTML总结</h1><br/><h2>文本格式化</h2><hr/><p><b>粗体文本<…...

Spring使用P命名空间实现注入数值信息-----Spring框架

<?xml version"1.0" encoding"UTF-8"?> <beans xmlns"http://www.springframework.org/schema/beans"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xmlns:p"http://www.springframework.org/schema/p"x…...

windows环境下安装RabbitMQ

一、RabbitMq简介1.1消息队列中间件简介消息队列中间件是分布式系统中重要的组件&#xff0c;主要解决应用耦合&#xff0c;异步消息&#xff0c;流量削锋等问题实现高性能&#xff0c;高可用&#xff0c;可伸缩和最终一致性[架构] 使用较多的消息队列有 ActiveMQ(安全)&#x…...

Java源码规则引擎:jvs-rules决策流的自定义权限控制

规则引擎用于管理和执行业务规则。它提供了一个中央化的机制来定义、管理和执行业务规则&#xff0c;以便根据特定条件自动化决策和行为。规则引擎的核心概念是规则。规则由条件和动作组成。条件定义了规则适用的特定情况或规则触发的条件&#xff0c;而动作定义了规则满足时要…...

Python-字符串的世界

Python字符串的世界 在Python编程中&#xff0c;字符串&#xff08;String&#xff09;是一种非常重要的数据类型&#xff0c;用于表示文本信息。字符串可以包含字母、数字、特殊字符以及空格&#xff0c;它们在编程中被广泛用于文本处理、格式化输出、字符串匹配等各种场景。…...

使用上 Spring 的事件机制

本文主要是简单的讲述了Spring的事件机制&#xff0c;基本概念&#xff0c;讲述了事件机制的三要素事件、事件发布、事件监听器。如何实现一个事件机制&#xff0c;应用的场景&#xff0c;搭配Async注解实现异步的操作等等。希望对大家有所帮助。 Spring的事件机制的基本概念 …...

Linux安装QT

//进入qt安装包路径 cd qt安装包路径 //修改权限 sudo chmod x qt-opensource-linux-x64-5.14.2.run //运行qt安装包 sudo ./qt-opensource-linux-x64-5.14.2.run //安装编译器 sudo apt-get install gcc g //安装编译工具 sudo apt-get install build-essential //安装Op…...

如何用arduino uno主板播放自己想要的曲子。《我爱你中国》单片机版本。

目录 一.效果展示 二.基本原理 三.电路图 四.代码 一.效果展示 arduino播放《我爱你中国》 二.基本原理 利用arduino uno单片机实现对蜂鸣器振动频率的调节&#xff0c;基于PWM控制系统通过代码实现控制。 三.电路图 四.代码 //main.uno #define Buzzer 2int PotBuffer …...

网络编程(Modbus进阶)

思维导图 Modbus RTU&#xff08;先学一点理论&#xff09; 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议&#xff0c;由 Modicon 公司&#xff08;现施耐德电气&#xff09;于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...

React 第五十五节 Router 中 useAsyncError的使用详解

前言 useAsyncError 是 React Router v6.4 引入的一个钩子&#xff0c;用于处理异步操作&#xff08;如数据加载&#xff09;中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误&#xff1a;捕获在 loader 或 action 中发生的异步错误替…...

Flask RESTful 示例

目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题&#xff1a; 下面创建一个简单的Flask RESTful API示例。首先&#xff0c;我们需要创建环境&#xff0c;安装必要的依赖&#xff0c;然后…...

可靠性+灵活性:电力载波技术在楼宇自控中的核心价值

可靠性灵活性&#xff1a;电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中&#xff0c;电力载波技术&#xff08;PLC&#xff09;凭借其独特的优势&#xff0c;正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据&#xff0c;无需额外布…...

【网络安全产品大调研系列】2. 体验漏洞扫描

前言 2023 年漏洞扫描服务市场规模预计为 3.06&#xff08;十亿美元&#xff09;。漏洞扫描服务市场行业预计将从 2024 年的 3.48&#xff08;十亿美元&#xff09;增长到 2032 年的 9.54&#xff08;十亿美元&#xff09;。预测期内漏洞扫描服务市场 CAGR&#xff08;增长率&…...

抖音增长新引擎:品融电商,一站式全案代运营领跑者

抖音增长新引擎&#xff1a;品融电商&#xff0c;一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中&#xff0c;品牌如何破浪前行&#xff1f;自建团队成本高、效果难控&#xff1b;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...

Cinnamon修改面板小工具图标

Cinnamon开始菜单-CSDN博客 设置模块都是做好的&#xff0c;比GNOME简单得多&#xff01; 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

【单片机期末】单片机系统设计

主要内容&#xff1a;系统状态机&#xff0c;系统时基&#xff0c;系统需求分析&#xff0c;系统构建&#xff0c;系统状态流图 一、题目要求 二、绘制系统状态流图 题目&#xff1a;根据上述描述绘制系统状态流图&#xff0c;注明状态转移条件及方向。 三、利用定时器产生时…...

C# 类和继承(抽象类)

抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...

工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配

AI3D视觉的工业赋能者 迁移科技成立于2017年&#xff0c;作为行业领先的3D工业相机及视觉系统供应商&#xff0c;累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成&#xff0c;通过稳定、易用、高回报的AI3D视觉系统&#xff0c;为汽车、新能源、金属制造等行…...