【Linux篇】网络编程——I/O复用

目录

一、初识复用

1. 认识复用

2. 复用的优点

3. 复用技术在服务端的应用

二、select 技术

1. 设置文件描述符(fd_set)

2. 文件描述符的控制

(1)FD_ZERO

(2)FD_SET

(3)FD_CLR

(4)FD_ISSET

3. select 函数

4. select 实例

(1)客户端

(2)服务器端

三、epoll 技术

1. 认识 epoll

2. epoll 优点

3. epoll 中的事件保存

4. epoll_create

5. epoll_ctl

6. epoll_wait

7. 条件触发和边缘触发

8. epoll 实例

(1)客户端

(2)服务端(边缘触发模式)


一、初识复用

1. 认识复用

        “复用” 的含义:为了提高物理设备的效率,用最少的物理要素传递最多数据时使用的技术。

        上图远距离的 3 人 可以同时通话的 3 方 对话纸杯电话系统。为使 3 人 同时对话,需准备图中所示系统。

        另外,为了完成 3 人 对话,说话时需同时对着两个纸杯,接听时也需要耳朵同时对准两个纸杯。此时引入复用技术会使通话更加方便,如下图。

2. 复用的优点

  • 减少连线长度。
  • 减少纸杯个数。

3. 复用技术在服务端的应用

        纸杯电话系统引入复用技术后,可以减少纸杯数和连线长度。同样,服务器端引入复用技术
可以减少所需进程数。

        下图模型中引入复用技术,可以减少进程数。重要的是,无论连接多少客户端,提供服务的进程只有1个。

二、select 技术

        上图为从调用 select 函数 到获取结果所经过程。

1. 设置文件描述符(fd_set)

        利用 select 函数 可以同时监视多个文件描述符。

        监视文件描述符可以视为监视套接字。此时首先需要将要监视的文件描述符集中到一起。集中时也要按照监视项(接收、传输、异常)进行区分,即按照上述 3 种 监视项分成 3 类。

        图中最左端的位表示文件描述符 0(所在位置)。如果该位设置为 1,则表示该文件描述符是监视对象。

2. 文件描述符的控制

(1)FD_ZERO

FD_ZERO(fd_set * fdset)

        将 fd_set 变量的 所有位初始化为 0。

(2)FD_SET

FD_SET(int fad, fd_set * fdset)

        在参数 fdset 指向的变量中注册文件描述符 fd 的信息。

(3)FD_CLR

FD_CLR(int fd, fd_set * fdset)

        从参数 fdset 指向的变量中清除文件描述符 fd 的信息。

(4)FD_ISSET

FD_ISSET(int fd, fd_set * fdset)

        用于验证select函数的调用结果。若参数 fdset 指向的变量中包含文件描述符 fd 的信息,则返回“真”。

3. select 函数

#include <sys/select.h>
#include<sys/time.h>int select(int maxfd,fd_set * readset,fd_set * writeset,fd_set * exceptset,const struct timeval * timeout);
① maxfd

        监视对象文件 描述符数量。文件描述符的监视范围 与这个参数有关。

② readset

        将所有关注 “是否存在待读取数据” 的文件描述符注册到 fd_set 型变量,并传递其 地址值。

③ writeset

        将所有关注 “是否可传输无阻塞数据” 的文件描述符注册到 fd_set 型变量,并传递其地址值。

④ exceptset

        将所有关注 “是否发生异常” 的文件描述符注册到 fd_set 型变量,并传递其地址值。

⑤ timeout

        调用 select 函数后,为防止 陷入无限阻塞的 状态,传递 超时(time-out)信息。

struct timeval
{long tv_sec;	// seconds(秒)long tv_usec;  // microseconds(毫秒)
}
⑥ 返回值

        发生错误时 返回 -1,超时返回时 返回 0。因发生关注的事件 返回时,返回大于 0 的值,该值是发生事件的 文件描述符数。

补充:

       ① select 函数 用来验证 3 种 监视项的 变化情况。根据 监视项声明 3 个 fd_set 型变量,分别向其注册文件描述符 信息,并把 变量的地址值 传递到上述函数的 第二 到 第四个参数。

       ② select 函数要求通过 第一个参数 传递监视对象文件 描述符的 数量。因此,需要得到 注册在 fd_set 变量中的 文件描述符数。但 每次新建文件描述符时,其值都会 增 1,故 只需将 最大的 文件描述符值 加 1 再传递到 select 函数 即可。加 1 是因为文件描述符的值 从 0 开始。

        ③ 由上图可知,select函数调用完成后,向其传递的 fd_set 变量中将 发生变化。原来为 1 的所有位均变为 0,但发生变化的文件描述符 对应位 除外。因此,可以认为值 仍为 1 的位置上的文件描述符发生了变化。

4. select 实例

(1)客户端

#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <iostream>
#include <unistd.h>
using namespace std;#define MAX_LEN  1024int main(int argc, char const *argv[])
{int client_sock;struct sockaddr_in server_addr;socklen_t server_addr_len;char message[MAX_LEN];if (argc != 3) {cout << "Using: " << argv[0] << " 10.211.55.8 <port>" << endl;exit(1);}// 定义客户端 socketclient_sock = socket(AF_INET, SOCK_STREAM, 0);if (client_sock == -1) {perror("socket()");exit(1);}// 连接服务器memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = inet_addr(argv[1]);server_addr.sin_port = htons(atoi(argv[2]));server_addr_len = sizeof(server_addr);if (connect(client_sock, (struct sockaddr *)&server_addr, server_addr_len) == -1) {perror("connect()");exit(1);}else {cout << "connect success ·······" << endl;}for (int i = 0; i < 3; i++) {memset(&message, 0, sizeof(message));cout << "client send: ";cin >> message;send(client_sock, message, sizeof(message), 0);recv(client_sock, message, MAX_LEN, 0);cout << "client recv: " << message << endl;}close(client_sock);return 0;
}

(2)服务器端

#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <iostream>
#include <unistd.h>
using namespace std;#define MAX_LEN  1024int main(int argc, char const *argv[])
{int server_sock, client_sock;struct sockaddr_in server_addr, client_addr;struct timeval timeset;socklen_t client_addr_len;int fd_max, df_num, str_len;char message[MAX_LEN];if (argc != 2) {cout << "Using: " << argv[0] << " <port>" << endl;exit(1);}// 创建服务端 socketserver_sock = socket(PF_INET, SOCK_STREAM, 0);if (server_sock == -1) {perror("socket()");exit(1);}// 绑定 IP和端口memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = htonl(INADDR_ANY);server_addr.sin_port = htons(atoi(argv[1]));if (bind(server_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {perror("bind()");exit(1);}// 开启监听if (listen(server_sock, 5) == -1) {perror("listen()");exit(1);}// 初始化监听窗口fd_set fdset, copy_fdset;FD_ZERO(&fdset);FD_SET(server_sock, &fdset);fd_max = server_sock;while (1) {// 设置超时// timeset.tv_sec = 5;     // 5秒后超时// timeset.tv_usec = 0; copy_fdset = fdset;if(df_num = select(fd_max+1, &copy_fdset, 0, 0, 0) == -1) {perror("select()");break;} // if (df_num == 0) {//     cout << "not client ·······" << endl;//     continue;// }for (int i = 0; i < fd_max + 1; i++) {  // 循环到最大的文件标识符的索引处if (FD_ISSET(i, &copy_fdset)) {if (i == server_sock) {// 检测到的标识符为 服务端的// 表示服务端的 socket 接收到连接请求client_addr_len = sizeof(client_addr);if ((client_sock = accept(server_sock, (struct sockaddr *)&client_addr, &client_addr_len)) == -1) {perror("accept()");exit(1);}FD_SET(client_sock, &fdset);   // 将新添加的客户端 socket 加入监视队列中if (client_sock > fd_max) fd_max = client_sock;     // 更新最大文件标识符char client_ip[16];inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, client_ip, 16);cout << "connected client IP: " << client_ip << " post: " << ntohs(client_addr.sin_port) << endl;// cout << "continue ··········" << endl;}else {// 检测到的标识符不是服务端// 表示为客户端向服务端 传输数据,或者是 发送的断开连接请求str_len = recv(i, message, MAX_LEN, 0);if (str_len == -1) {perror("recv");exit(1);}else if (str_len > 0) {cout << "receive " << i << " client data: " << message << endl;send(i, message, sizeof(message), 0);}else if (str_len == 0) {// 断开连接FD_CLR(i, &fdset);cout << "closed client: " << i << " successed ········" << endl;}}}}}close(server_sock);return 0;
}

三、epoll 技术

1. 认识 epoll

        每次调用 select 函数时向操作系统传递 监视对象信息。应用程序 向操作系统传递数据 将对程序造成 很大负担,而且 无法通过优化代码解决,因此将成为 性能上的致命弱点

        有些函数 不需要操作系统的帮助 就能完成功能,而有些 则必须借助于操作系统。select 函数与 文件描述符有关,更准确地说,是 监视套接字变化的 函数。而 套接字是由 操作系统管理的,所以 select 函数绝对 需要借助于操作系统 才能完成功能。select 函数的这一缺点可以通过 如下方式弥补:

        仅向操作系统传递1次监视对象,监视范围或内容发生变化时只通知发生变化的事项。

2. epoll 优点

  • 无需编写以 监视状态变化为目的的 针对所有文件描述符的 循环语句。
  • 调用对应于 select 函数的 epoll_wait 函数时无需 每次传递监视对象信息。

3. epoll 中的事件保存

        epoll 方式中通过 如下结构体 epoll_event 将发生变化的(发生事件的)文件描述符单独集中到一起。

struct epoll_event
{__uint32_t events;epoll_data_t data;
}typedef union epol1_data
{void * ptr;int fd;__uint32_t u32;__uint64_t u64;
} epoll_data_t;

        声明足够大的 epoll_event 结构体数组后,传递给 epoll_ wait 函数时,发生变化的 文件描述符信息将被填人该数组。因此,无需像 select 函数那样针 对所有文件描述符进行循环。

4. epoll_create

#include <sys/epoll.h>int epol1_create(int size);
① size        

        epoll 实例的大小。

② 返回

        成功时返回 epoll 文件描述符,失败时返回 -1。

补充:
  1.     调用 epoll_create 函数时创建的文件描述符保存空间称为 “epoll 例程 ” ,但有些情况下名称不同。
  2.     通过 参数 size 传递的值决定 epoll 例程 的大小,但该值 只是向操作系统 提的建议。换言之,size 并非用来决定 epoll 例程 的大小,而 仅供操作系统 参考。
  3.     epoll_create 函数创建的资源 与 套接字相同,也由 操作系统管理。因此,该函数和创建 套接字的 情况相同,也会 返回 文件描述符。也就是说,该函数返回的 文件描述符主要用与于区分 epoll 例程。需要终止时,与 其他文件描述符 相同,也要调用 close 函数。

5. epoll_ctl

#include <sys/epoll.h>int epoll_ctl(int epfd, int op, int fd, struct epoll_event * event);
① epfd

        用于注册监视对象的epoll例程的文件描述符。

② op
EPOLL_CTL_ADD: 将文件描述符注册到epoll例程。
EPOLL_CTL_DEL: 从epoll例程中删除文件描述符。
EPOLL_CTL_MOD: 更改注册的文件描述符的关注事件发生情况。

        用于指定监视对象的添加、删除或更改等操作。

③ fd

        需要注册的监视对象文件描述符。

④ event

         监视对象的事件类型。

⑤ 返回

        成功时返回 0,失败时返回 -1。

补充:
epoll_ctl(A, EPOLL_CTL_ADD, B, C);表示为:从epoll例程A中注册文件描述符B,主要目的是监视参数C中的事件。
struct epoll_event event;
······
event.events = EPOLLIN;//发生需要读取数据的情况(事件)时
event.data.fd = sockfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd,&event);
······
EPOLLIN:需要读取数据的情况。
EPOLLOUT:输出缓冲为空,可以立即发送数据的情况。
EPOLLPRI: 收到OOB数据的情况。
EPOLLRDHUP:断开连接或半关闭的情况,这在边缘触发方式下非常有用。
EPOLLERR:发生错误的情况。
EPOLLET:以边缘触发的方式得到事件通知。
EPOLLONESHOT:发生一次事件后,相应文件描述符不再收到事件通知。因此需要向epoll_ctl函数的第二个参数传递EPOLL_CTL_MOD,再次设置事件。

6. epoll_wait

#include <sys/epoll.h>int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
① epfd

        表示事件发生监视范围的 epoll 例程的 文件描述符。

② events

        保存发生事件的文件描述符集合 的结构体 地址值。

③ maxevents

        第二个参数中 可以保存的 最大事件数。

④ timeout

        以 1/1000 秒 为单位的等待时间,传递 -1 时,一直等待直到 发生事件。

⑤ 返回

        成功时返回发生事件的 文件描述符数,失败时返回 -1。同时在第二个参数指向的缓冲中保存发生事件的文件描述符集合。因此,无需像select那样插入针对所有文件描述符的循环。

补充:

        第二个参数 所指缓冲需要动态分配。

int event_cnt;
struct epoll_event * ep_events;
······
ep_events = malloc(sizeof(struct epoll_event)*EPOLL_SIZE); // EPOLL_SIZE 是宏常量
event_Cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE,-1);
······

7. 条件触发和边缘触发

        条件触发(Level Trigger)方式中,只要输入缓冲有数据就会一直通知该事件,就将以事件方式再次注册。

        边缘触发(Edge Trigger)中输入缓冲收到数据时仅注册 1 次 该事件。即使输入缓冲中还留有数据,也不会再进行注册。

8. epoll 实例

(1)客户端

#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <iostream>
#include <unistd.h>
using namespace std;#define MAX_LEN  1024int main(int argc, char const *argv[])
{int client_sock;struct sockaddr_in server_addr;socklen_t server_addr_len;char message[MAX_LEN];if (argc != 3) {cout << "Using: " << argv[0] << " 10.211.55.8 <port>" << endl;exit(1);}// 定义客户端 socketclient_sock = socket(AF_INET, SOCK_STREAM, 0);if (client_sock == -1) {perror("socket()");exit(1);}// 连接服务器memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = inet_addr(argv[1]);server_addr.sin_port = htons(atoi(argv[2]));server_addr_len = sizeof(server_addr);if (connect(client_sock, (struct sockaddr *)&server_addr, server_addr_len) == -1) {perror("connect()");exit(1);}else {cout << "connect success ·······" << endl;}for (int i = 0; i < 3; i++) {memset(&message, 0, sizeof(message));cout << "client send: ";cin >> message;send(client_sock, message, sizeof(message), 0);recv(client_sock, message, MAX_LEN, 0);cout << "client recv: " << message << endl;}close(client_sock);return 0;
}

(2)服务端(边缘触发模式)

#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/epoll.h>
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
using namespace std;#define MAX_LEN  4
#define EPOLL_SIZE  50void setnonblockingmode(int fd);int main(int argc, char const *argv[])
{int server_sock, client_sock;struct sockaddr_in server_addr, client_addr;socklen_t client_addr_len;int epfd, event_cnt;struct epoll_event * ep_events;     // 单独保存发生的事件struct epoll_event event;   // 单个时间int str_len;char message[MAX_LEN];if (argc != 2) {cout << "Using: " << argv[0] << " <port>" << endl;exit(1);}// 定义服务端 socketserver_sock = socket(PF_INET, SOCK_STREAM, 0);if (server_sock == -1) {perror("socket()");exit(1);}// 绑定 IP和端口memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = htonl(INADDR_ANY);server_addr.sin_port = htons(atoi(argv[1]));if (bind(server_sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {perror("bind()");exit(1);}// 开启监听if (listen(server_sock, 5) == -1) {perror("listen()");exit(1);}epfd = epoll_create(EPOLL_SIZE);    // 创建文件描述符保存空间ep_events = (struct epoll_event *)malloc(sizeof(struct epoll_event) * EPOLL_SIZE);  // 为储存事件的空间开辟内存setnonblockingmode(server_sock);event.events = EPOLLIN;event.data.fd = server_sock;epoll_ctl(epfd, EPOLL_CTL_ADD, server_sock, &event);    // 将服务器的 socket 注册进去while (1) {event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);    // 等待有 socket 被填入到 ep_events 中if (event_cnt == -1) {perror("epoll_wait()");break;}cout << "return epoll_wait" << endl;for (int i = 0; i < event_cnt; i++) {if (ep_events[i].data.fd == server_sock) {// 客户端发来了连接请求client_addr_len = sizeof(client_addr);client_sock = accept(server_sock, (struct sockaddr *)&client_addr, &client_addr_len);if (client_sock == -1) {perror("accept()");exit(1);}setnonblockingmode(client_sock);event.events = EPOLLIN | EPOLLET;   // 边缘触发模式event.data.fd = client_sock;epoll_ctl(epfd, EPOLL_CTL_ADD, client_sock, &event);    // 将新的客户端注册进去char client_ip[16];inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, client_ip, 16);cout << "client connected: IP " << client_ip << " port " << ntohs(client_addr.sin_port) << endl;}else {// 发送信息或者断开连接while (1) {     // 循环把输入缓存中的数据全都读完str_len = recv(ep_events[i].data.fd, message, MAX_LEN, 0);if (str_len == 0) {// 断开连接epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);close(ep_events[i].data.fd);    // 关闭断开连接的 socketcout << "closed client: " << ep_events[i].data.fd << endl;}else if (str_len < 0) {if (errno == EAGAIN) {  // 没有数据可读break;}}else {// cout << "receive client " << ep_events[i].data.fd << " : " << message << endl;send(ep_events[i].data.fd, message, sizeof(message), 0);}}}}}close(server_sock);close(epfd);return 0;
}void setnonblockingmode(int fd) {// 设置非阻塞模式int flag = fcntl(fd, F_GETFL, 0);   // 获取之前设置的属性信息fcntl(fd, F_SETFL, flag | O_NONBLOCK);  // 在此基础上添加非阻塞的 O_NONBLOCK
}

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

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

相关文章

前端使用 Konva 实现可视化设计器(23)- 绘制曲线、属性面板

本章分享一下如何使用 Konva 绘制基础图形&#xff1a;曲线&#xff0c;以及属性面板的基本实现思路&#xff0c;希望大家继续关注和支持哈&#xff08;多求 5 个 Stars 谢谢&#xff09;&#xff01; 请大家动动小手&#xff0c;给我一个免费的 Star 吧~ 大家如果发现了 Bug&a…

SQL常用数据过滤 - EXISTS运算符

SQL查询中的EXISTS运算符用于检查查询子句是否存在满足特定条件的记录&#xff0c;如果有一条或者多条记录存在&#xff0c;则返回True&#xff0c;否则返回False。 语法结构 SELECT column_name(s)FROM table_nameWHERE EXISTS(SELECT column_name FROM table_name WHERE co…

C++实现二叉树的创建删除,dfslfs,求叶子结点个数,求叶子结点个数,求树的高度

C实现二叉树的创建删除&#xff0c;dfs/lfs,求叶子结点个数&#xff0c;求树的高度 基本算法&#xff1a; 用链栈建立二叉树&#xff0c;通过递归实现深度优先的三种遍历&#xff0c;用队列实现广度优先层次遍历。借助递归思想求解叶子结点个数和树的深度。 tree.h定义基本的…

TMR技术的发展及其应用技术的介绍

目录 概述 1 TMR传感器介绍 1.1 原理介绍 1.2 技术演进历史 2 TMR技术的应用 2.1 电阻特性 2.2 技术比较 2.3 磁道特性 3 多维科技的芯片&#xff08;TMR1202&#xff09; 3.1 芯片介绍 3.2 特性 ​3.3 典型应用 参考文献 概述 本文主要介绍TMR技术的发展及其技术…

PyTorch构建卷积神经网络(CNN)训练模型:分步指南

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

基于卷积神经网络的体育运动项目分类识别系统

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长 QQ 名片 :) 1. 项目简介 随着计算机视觉和深度学习技术的快速发展&#xff0c;利用先进的图像处理技术对体育运动进行智能分类与识别已成为研究热点。传统的运动分析方法通常依赖于人工观察和记录&#xff0c;耗时耗力且容…

Golang | Leetcode Golang题解之第440题字典序的第K小数字

题目&#xff1a; 题解&#xff1a; func getSteps(cur, n int) (steps int) {first, last : cur, curfor first < n {steps min(last, n) - first 1first * 10last last*10 9}return }func findKthNumber(n, k int) int {cur : 1k--for k > 0 {steps : getSteps(cu…

基于SSM+小程序的在线课堂微信管理系统(在线课堂1)(源码+sql脚本+视频导入教程+文档)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1 、功能描述 &emsp1、管理员实现了首页、个人中心、用户管理、课程分类管理、课程信息管理、课程订阅管理、课程视频管理、公告栏管理、留言板管理、系统管理。 2、用户实现了首页、课程信息、公…

Windows系统下批量重命名文件的两种实现方法

我们如果有一批文件&#xff0c;想要大批的重命名文件。例如&#xff0c;将下面的这些图片重命名为boot_itc_00001.jpg、boot_itc_00002.jpg、……、boot_itc_01000.jpg。总不能一个一个改吧&#xff1f; 第一种方法&#xff08;也是最灵活的一种&#xff09;&#xff1a; 借助…

机器学习-KNN分类算法

1.1 KNN分类 KNN分类算法&#xff08;K-Nearest-Neighbors Classification&#xff09;&#xff0c;又叫K近邻算法。它是概念极其简单&#xff0c;而效果又很优秀的分类算法。1967年由Cover T和Hart P提出。 KNN分类算法的核心思想&#xff1a;如果一个样本在特征空间中的k个最…

IIs站点发布ERR_UNSAFE_PORT

换个端口&#xff0c;谢谢&#xff01; nice 浏览器对部分端口有特定的保护机制&#xff0c;如果你的应用使用了这些端口&#xff0c;浏览器在发送请求时会触发保护机制&#xff0c;拒绝发送请求&#xff0c;于是&#xff0c;你的服务器应用自然就收不到请求了。 1, // …

树莓派基础命令

目录 1.树莓派简介 2.树莓派使用命令 3.树莓派包管理 4.关于远程连接树莓派的思路&#xff1a; 5.总结 1.树莓派简介 树莓派&#xff08;Raspberry Pi&#xff09;是一款由英国非营利组织树莓派基金会开发的小型、低成本的单板计算机&#xff0c;最初设计目的是为了让学生…

好看的首页展示

代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title><style>/* RESET…

【BUG】静读天下|静读天下无法设置段间距解决方案

【BUG】静读天下&#xff5c;静读天下无法设置段间距解决方案 文章目录 【BUG】静读天下&#xff5c;静读天下无法设置段间距解决方案前言解决办法 凑质量分静读天下的特点与优势功能布局与使用技巧个人使用心得结语 前言 03-23 求助&#xff5c;关于排版的问题【静读天下吧】_…

数字孪生平台,助力制造设备迈入超感知与智控新时代!

痛点剖析 当前&#xff0c;制造业面临系统分散导致的数据孤岛问题&#xff0c;严重阻碍了有效监管与统计分析&#xff1b;同时&#xff0c;设备多样化且兼容性不足&#xff0c;增加了管理难度&#xff1b;台账记录方式混乱&#xff0c;工单审批流程繁琐且效率低下&#xff1b;…

Stable Diffusion零基础学习

Stable Diffusion学习笔记TOP11 _插件篇之ControlNet功能篇 ControlNet目前支持的10多种预处理器&#xff0c;根据数据检测种类可分为两种类型&#xff1a; 1、功能型&#xff1a;拥有着不同的能力 2、构图型&#xff1a;控制着SD扩散图形的构图规则 Shuffle洗牌/转换&#…

基于Ubuntu 20.04 LTS上部署MicroK8s(最小生产的 Kubernetes)

目录 文章目录 目录简介Kubernetes简介MicroK8s简介Ubuntu系统MicroK8s的优势安装环境基本要求执行安装命令加入群组(使用非 root 用户访问)开启 dashboard 仪表盘查看服务名称查看仪表盘开放的端口打开浏览器检查状态打开你想要的服务(使用附加组件)开始使用 microk8s访问 Kub…

【【通信协议之ICMP协议的FPGA实现】】

通信协议之ICMP协议的FPGA实现 整体的实现框图如下所示 arp_rx.v module arp_rx#(//开发板MAC地址 00-11-22-33-44-55parameter BOARD_MAC 48h00_11_22_33_44_55, //开发板IP地址 192.168.1.10 parameter BOARD_IP {8d192,8d168,8d1,8d10} )(input …

RFID手持机——物联网时代的核心工具

一、行业背景 在当今物联网技术高速发展的时代&#xff0c;RFID技术作为核心的数据采集与识别手段&#xff0c;在物流、仓储、资产管理等众多领域发挥着至关重要的作用。以物流行业为例&#xff0c;利用RFID技术能够对货物进行全程精准跟踪&#xff0c;从入库、存储、搬运到出…

Keepalived+Nginx 高可用集群(双主模式)

1.基础环境配置 [rootlb1 ~]# systemctl stop firewalld # 关闭防火墙 [rootlb1 ~]# sed -i s/^SELINUX.*/SELINUXdisabled/ /etc/sysconfig/selinux # 关闭selinux&#xff0c;重启生效 [rootlb1 ~]# setenforce 0          …