C语言终篇--基于epoll ET模式 的 非阻塞IO服务器模型

使用技术:

1 epoll事件驱动机制:使用epoll作为IO多路复用的技术,以高效地管理多个socket上的事件。

2 边缘触发(Edge Triggered, ET)模式:epoll事件以边缘触发模式运行,这要求代码必须负责消费所有可用的数据,直到收到EAGAIN或EWOULDBLOCK错误。

3 非阻塞IO:通过设置socket为非阻塞模式,确保IO操作不会导致服务器线程挂起等待。

4 信号处理:捕捉SIGINT信号,以允许服务器通过中断信号优雅地关闭和清理资源。

5 地址重用:设置SO_REUSEADDR套接字选项,允许服务器快速重启而不必等待TIME_WAIT状态的连接自然超时。

6 调整接收缓冲区为1字节,将接收的数据存在内存的容器中,用于模拟大数据来袭,无法全部接收的情景

7 可动态扩容的兴趣事件列表

8 使用whlie()循环,分别包裹ET模式下的accpet(),recv(),send(),确保读尽,写尽,及合适的时机退出

注意事项:

运行环境:

unix-like gun_c 因为涉及到信号需独立终端 不能再IDE环境下运行

地址端口 按需调整 

赠送:

epoll LT模式 服务端 对比用双循环阻塞服务端 测试用客户端

运行效果:

epoll ET模式 服务端:

#define _GNU_SOURCE
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>#define SERVER_IP "192.168.142.132"
#define SERVER_PORT 50001
// 单线程访问count,count表示感兴趣的fd的总数
int count = 0;
// 用于清理函数
void *global_r_events;
char *global_buf_p;
int global_server_sockfd;
// 注册清理函数
void clean_up()
{free(global_r_events);free(global_buf_p);close(global_server_sockfd);printf("clean_up\n");
}
// 如果按了CTRL+C则退出并执行清理函数
void sig_handler(int sig)
{exit(EXIT_SUCCESS);
}
// 此函数功能将socket设为非阻塞
void set_non_blocking(int fd)
{int flags = fcntl(fd, F_GETFL);if (flags == -1){perror("fcntl F_GETFL");}flags |= O_NONBLOCK;if (fcntl(fd, F_SETFL, flags) == -1){perror("fcntl F_SETFL");}
}
// 此函数功能 调用设置非阻塞函数+将sockfd加入兴趣列表+将sockfd设为ET
void add_ins_events(int epoll_fd, int fd)
{// 设成非阻塞set_non_blocking(fd);struct epoll_event ins_event = {0};// 关注读+边缘触发ins_event.events = EPOLLIN | EPOLLET;ins_event.data.fd = fd;if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ins_event) == -1){perror("epoll_ctl");}// count表示感兴趣的fd的总数count++;
}int main()
{int server_sockfd, client_sockfd;struct sockaddr_in server_sockaddr, client_sockaddr;memset(&server_sockaddr, 0, sizeof(server_sockaddr));memset(&client_sockaddr, 0, sizeof(client_sockaddr));socklen_t client_sockaddr_len = sizeof(client_sockaddr);socklen_t server_sockaddr_len = sizeof(server_sockaddr);ssize_t send_bytes, recv_bytes;// 预留char send_buf[1024] = {0};// 调整接收缓冲区为1字节,用于模拟大数据来袭,无法全部接收的情景char recv_buf[1] = {0};// 一个用于添加兴趣事件的结构体struct epoll_event ins_event = {0};// i用于循环,optval套接字属性用,size是用户空间就绪事件列表的长度// 用户空间兴趣事件的计数是count,遍历的上限也是count,就绪事件的个数一定小于count,size只作为容器大小存在int i = 0, optval = 1, size = 1024;// 需要发送的总字节数,已发送字节数,已接收字节总数int send_total, sent, recv_total;// 注册信号if (signal(SIGINT, sig_handler) == SIG_ERR){perror("signal");}// 注册退出清理函数atexit(clean_up);// 创建socket ipv4 tcpserver_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (server_sockfd == -1){perror("socket");}// 向全局变量传递值,便于关闭global_server_sockfd = server_sockfd;// 地址端口复用if (setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1){perror("setsockopt");}// 绑定server_sockaddr.sin_family = AF_INET;server_sockaddr.sin_port = htons(SERVER_PORT);if (inet_pton(AF_INET, SERVER_IP, &server_sockaddr.sin_addr.s_addr) == -1){perror("inet_pton");}if (bind(server_sockfd, (struct sockaddr *)&server_sockaddr, server_sockaddr_len) == -1){perror("bind");}// 监听if (listen(server_sockfd, 1024) == -1){perror("listen");}// 创建epoll实例int epoll_fd = epoll_create1(0);if (epoll_fd == -1){perror("epoll_create1");}// 将server_sockfd设成非阻塞 对读感兴趣 ET模式 count++add_ins_events(epoll_fd, server_sockfd);// 这是一个在堆上分配的就绪事件容器,epoll_wait将填充这个容器的一部分struct epoll_event *r_events = (struct epoll_event *)calloc(size, sizeof(struct epoll_event));if (r_events == NULL){perror("calloc");}else{// 向全局变量传递值,便于释放global_r_events = r_events;}// 这是一个足够大的内存缓冲区 用于储存接收的数据// 因为每次接收被限流为1字节 模拟大数据到来时缓冲区的渺小// 每次接收的数据会用指针算数,拼在已接收数据的后面char *buf_p = (char *)calloc(1024, sizeof(char));if (buf_p == NULL){perror("calloc");}else{// 向全局变量传递值,便于释放global_buf_p = buf_p;}printf("server start ...\n");// 服务器主循环 里面有3个 whlie(1)循环,分别用于包裹ET模式下的accpet,recv,sendwhile (1){// 清零就绪事件的容器memset(r_events, 0, size * sizeof(struct epoll_event));// count表示已添加兴趣列表事件的最大值,size是容器大小,因为就绪事件的大小永远小于等于兴趣事件,所以用count// -1表示没有就绪事件则一直阻塞,为除了算力问题外,整个架构设计的唯一IO阻塞点int r = epoll_wait(epoll_fd, r_events, count, -1);if (r > 0){// r>0,r为就绪事件数量,所以遍历rfor (i = 0; i < r; i++){// 如果读就绪且fd是server_sockfdif ((r_events[i].events & EPOLLIN) && (r_events[i].data.fd == server_sockfd)){// ET模式下包裹accept的循环while (1){// 接收连接client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_sockaddr, &client_sockaddr_len);if (client_sockfd < 0){if (errno == EAGAIN || errno == EWOULDBLOCK){// 读尽,跳出循环break;}else{// 这就是出错了perror("accept");}}else{// 打印日志printf("[+] %u: connected\n", ntohs(client_sockaddr.sin_port));// 将client_sockfd设成非阻塞 对读感兴趣 ET模式 count++add_ins_events(epoll_fd, client_sockfd);// 如果count的值将要接近容器容量,则容器容量翻倍if ((size - count) < 100){size += size;r_events = (struct epoll_event *)realloc(r_events, size * sizeof(struct epoll_event));if (r_events == NULL){perror("realloc");}// 赋值全局变量,方便释放global_r_events = r_events;}}}}// 如果fd读就绪且不是server_sockfd,此时处理读写if ((r_events[i].events & EPOLLIN) && (r_events[i].data.fd != server_sockfd)){// 获取peer端口号,打印日志用getpeername(r_events[i].data.fd, (struct sockaddr *)&client_sockaddr, &client_sockaddr_len);// 打印日志printf("[+] %u: recv ready\n", ntohs(client_sockaddr.sin_port));// 已接收的字节总数,用于在buf_p(一个足够大的内存缓冲区)中定位recv_total = 0;// ET模式下 读尽策略 包裹recv 的循环while (1){// 读缓冲区被限流到1字节recv_bytes = recv(r_events[i].data.fd, recv_buf, sizeof(recv_buf), 0);// 如果读到了数据if (recv_bytes > 0){// 将数据追加在buf_p的最后memcpy(buf_p + recv_total, recv_buf, recv_bytes);// 计数recv_total += recv_bytes;}if (recv_bytes < 0){// 读尽if (errno == EAGAIN || errno == EWOULDBLOCK){// 打印buf_p(一个足够大的内存缓冲区)printf("[+] %u: %s\n", ntohs(client_sockaddr.sin_port), buf_p);/* send */// 每次发送的字节send_bytes = 0;// 需要发送的总字节send_total = strlen(buf_p);// 已发送总字节sent = 0;// ET模式下 包裹send的循环while (1){// buf_p + sent是指针运算表示发送位置,send_total - sent是剩余发送量send_bytes = send(r_events[i].data.fd, buf_p + sent, send_total - sent, 0);if (send_bytes == -1){// 表示发送缓冲区已满if (errno == EAGAIN || errno == EWOULDBLOCK){// 添加对写感兴趣ins_event.events = EPOLLET | EPOLLIN | EPOLLOUT;ins_event.data.fd = r_events[i].data.fd;epoll_ctl(epoll_fd, EPOLL_CTL_MOD, r_events[i].data.fd, &ins_event);// 直接跳出循环,如果写就绪epoll_wait会通知break;}// 对方异常关闭if (errno == ECONNRESET){// 将fd移除兴趣事件列表epoll_ctl(epoll_fd, EPOLL_CTL_DEL, r_events[i].data.fd, NULL);// 关闭fdclose(r_events[i].data.fd);// 兴趣列表最大值-1count--;// 跳出send循环break;}}// 如果发送了一些数据if (send_bytes > 0){// 累加到已发送字节sent += send_bytes;// 如果 已发送字节等于发送总字节 则取消对于写的兴趣// 如果 之前fd没有关注写 则不变if (sent == send_total){ins_event.events = EPOLLET | EPOLLIN;ins_event.data.fd = r_events[i].data.fd;epoll_ctl(epoll_fd, EPOLL_CTL_MOD, r_events[i].data.fd, &ins_event);break;}}}/* send end */// 如果 不发送的话打印完接收数据就可以直接 跳出循环break;}// recv 对方异常终止else if (errno == ECONNRESET){// 打印日志char a[32] = {0};snprintf(a, sizeof(a), "[-] %u", ntohs(client_sockaddr.sin_port));perror(a);// 从感兴趣列表删除epoll_ctl(epoll_fd, EPOLL_CTL_DEL, r_events[i].data.fd, NULL);// 关闭fdclose(r_events[i].data.fd);// 兴趣列表最大值-1count--;break;}else{perror("recv");}}// 如果读到0,表示peer正常关闭了连接if (recv_bytes == 0){getpeername(r_events[i].data.fd, (struct sockaddr *)&client_sockaddr, &client_sockaddr_len);printf("[-] %u: closed\n", ntohs(client_sockaddr.sin_port));// 从兴趣列表删除这个fdepoll_ctl(epoll_fd, EPOLL_CTL_DEL, r_events[i].data.fd, NULL);// 关闭fdclose(r_events[i].data.fd);// 兴趣列表最大值-1count--;// 跳出读循环break;}}}}}if (r == -1){perror("epoll_wait");}}return 0;
}

epoll LT模式 服务端 :

#define _GNU_SOURCE
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <sys/poll.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <aio.h>
#include <signal.h>#define SERVER_IP "192.168.142.132"
#define SERVER_PORT 50001int count = 0;
void *global_r_events;
int global_server_sockfd;
void clean_up()
{free(global_r_events);close(global_server_sockfd);printf("clean_up\n");
}
void sig_handler(int sig)
{exit(EXIT_SUCCESS);
}void set_non_blocking(int fd)
{int flags = fcntl(fd, F_GETFL);if (flags == -1){perror("fcntl F_GETFL");}flags |= O_NONBLOCK;if (fcntl(fd, F_SETFL, flags) == -1){perror("fcntl F_SETFL");}
}void add_ins_events(int epoll_fd, int fd)
{set_non_blocking(fd);struct epoll_event ins_event = {0};ins_event.events = EPOLLIN;ins_event.data.fd = fd;if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ins_event) == -1){perror("epoll_ctl");}count++;
}int main()
{int server_sockfd, client_sockfd;struct sockaddr_in server_sockaddr, client_sockaddr;memset(&server_sockaddr, 0, sizeof(server_sockaddr));memset(&client_sockaddr, 0, sizeof(client_sockaddr));socklen_t client_sockaddr_len = sizeof(client_sockaddr);socklen_t server_sockaddr_len = sizeof(server_sockaddr);ssize_t send_bytes, recv_bytes;char send_buf[1024] = "How can I help you today ?";char recv_buf[1024] = {0};int i = 0, optval = 1, size = 1024;signal(SIGINT, sig_handler);atexit(clean_up);server_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (server_sockfd == -1){perror("socket");}global_server_sockfd = server_sockfd;if (setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1){perror("setsockopt");}server_sockaddr.sin_family = AF_INET;server_sockaddr.sin_port = htons(SERVER_PORT);if (inet_pton(AF_INET, SERVER_IP, &server_sockaddr.sin_addr.s_addr) == -1){perror("inet_pton");}if (bind(server_sockfd, (struct sockaddr *)&server_sockaddr, server_sockaddr_len) == -1){perror("bind");}if (listen(server_sockfd, 1024) == -1){perror("listen");}int epoll_fd = epoll_create1(0);if (epoll_fd == -1){perror("epoll_create1");}add_ins_events(epoll_fd, server_sockfd);struct epoll_event *r_events = (struct epoll_event *)calloc(size, sizeof(struct epoll_event));if (r_events == NULL){perror("calloc");}global_r_events = r_events;printf("server start ...\n");while (1){memset(r_events, 0, size * sizeof(struct epoll_event));// count用于有效遍历,而size则是数组的实际大小int r = epoll_wait(epoll_fd, r_events, count, -1);if (r > 0){for (i = 0; i < r; i++){if ((r_events[i].events & EPOLLIN) && (r_events[i].data.fd == server_sockfd)){printf("accept ready\n");client_sockfd = accept(server_sockfd, NULL, NULL);add_ins_events(epoll_fd, client_sockfd);if ((size - count) < 10){size += size;r_events = (struct epoll_event *)realloc(r_events, size * sizeof(struct epoll_event));if (r_events == NULL){perror("realloc");}global_r_events = r_events;}}if ((r_events[i].events & EPOLLIN) && (r_events[i].data.fd != server_sockfd)){printf("recv ready\n");recv_bytes = recv(r_events[i].data.fd, recv_buf, sizeof(recv_buf), 0);if (recv_bytes < 0){perror("recv");}if (recv_bytes == 0){printf("close by peer\n");close(r_events[i].data.fd);}if (recv_bytes > 0){printf("%s\n", recv_buf);send_bytes = send(r_events[i].data.fd, send_buf, strlen(send_buf), 0);if (send_bytes == -1){perror("send");}}}}}}return 0;
}

对比用双循环阻塞服务端:

#define _GNU_SOURCE
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>#define SERVER_IP "192.168.142.132"
#define SERVER_PORT 50001int main()
{int server_sockfd, client_sockfd;struct sockaddr_in server_sockaddr, client_sockaddr;memset(&server_sockaddr, 0, sizeof(server_sockaddr));memset(&client_sockaddr, 0, sizeof(client_sockaddr));socklen_t client_sockaddr_len = sizeof(client_sockaddr);ssize_t send_bytes, recv_bytes;char send_buf[1024] = "How can I help you today ?";char recv_buf[1024] = {0};server_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (server_sockfd == -1){perror("socket");}int optval = 1;setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));server_sockaddr.sin_family = AF_INET;inet_pton(AF_INET, SERVER_IP, &server_sockaddr.sin_addr.s_addr);server_sockaddr.sin_port = htons(SERVER_PORT);if (bind(server_sockfd, (struct sockaddr *)&server_sockaddr, sizeof(server_sockaddr)) == -1){perror("bind");}if (listen(server_sockfd, 16) == -1){perror("listen");}printf("server start...\n");while (1){client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_sockaddr, &client_sockaddr_len);if (client_sockfd == -1){perror("accept");}while (1){recv_bytes = recv(client_sockfd, recv_buf, sizeof(recv_buf), 0);if (recv_bytes == -1){perror("recv");}else if (recv_bytes == 0){printf("closed by peer\n");break;}else{printf("%s\n", recv_buf);}send_bytes = send(client_sockfd, send_buf, strlen(send_buf), 0);if (send_bytes == -1){perror("send");}}}close(server_sockfd);return 0;
}

测试用客户端:

#define _GNU_SOURCE
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
#include <fcntl.h>
#define SERVER_IP "192.168.142.132"
#define SERVER_PORT 50001
int set_non_blocking(int sockfd)
{int flags = fcntl(sockfd, F_GETFL, 0);if (flags == -1){perror("fcntl(F_GETFL)");}flags |= O_NONBLOCK;if (fcntl(sockfd, F_SETFL, flags) == -1){perror("fcntl(F_SETFL)");}return 0;
}
int main()
{int client_sockfd;struct sockaddr_in server_sockaddr, client_sockaddr;memset(&server_sockaddr, 0, sizeof(server_sockaddr));memset(&client_sockaddr, 0, sizeof(client_sockaddr));socklen_t client_sockaddr_len = sizeof(client_sockaddr);ssize_t send_bytes, recv_bytes;char send_buf[1024] = "he$$$llo ser$$$ver !!!";char recv_buf[1024] = {0};client_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (client_sockfd == -1){perror("socket");}inet_pton(AF_INET, SERVER_IP, &server_sockaddr.sin_addr.s_addr);server_sockaddr.sin_port = htons(SERVER_PORT);server_sockaddr.sin_family = AF_INET;if (connect(client_sockfd, (struct sockaddr *)&server_sockaddr, sizeof(server_sockaddr)) == -1){perror("connect");}while (1){send_bytes = send(client_sockfd, send_buf, strlen(send_buf), 0);if (send_bytes == -1){perror("send");}recv_bytes = recv(client_sockfd, recv_buf, sizeof(recv_buf), 0);if (recv_bytes == -1){perror("recv");}printf("%s\n", recv_buf);sleep(3);}close(client_sockfd);return 0;
}

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

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

相关文章

HarmonyOS 应用开发之非线性容器

非线性容器实现能快速查找的数据结构&#xff0c;其底层通过hash或者红黑树实现&#xff0c;包括HashMap、HashSet、TreeMap、TreeSet、LightWeightMap、LightWeightSet、PlainArray七种。非线性容器中的key及value的类型均满足ECMA标准。 HashMap HashMap 可用来存储具有关联…

非线性SVM模型

通过5个条件判定一件事情是否会发生&#xff0c;5个条件对这件事情是否发生的影响力不同&#xff0c;计算每个条件对这件事情发生的影响力多大&#xff0c;写一个非线性SVM模型程序,最后打印5个条件分别的影响力。 示例一 在非线性支持向量机&#xff08;SVM&#xff09;模型中…

Vue3从入门到实战:路由的query和params参数

在Vue 3中&#xff0c;我们可以通过路由的查询参数来传递数据。这意味着我们可以在不同的页面之间传递一些信息&#xff0c;以便页面可以根据这些信息来显示不同的内容或执行不同的操作。 查询参数的使用方式类似于在URL中添加附加信息&#xff0c;以便页面之间可以根据这些信息…

【GlobalMapper精品教程】073:像素到点(Pixels-to-Points)从无人机图像轻松生成点云

文章目录 一、工具介绍二、生成点云三、生成正射四、生成3D模型五、注意事项一、工具介绍 Global Mapper v19引入的新的像素到点工具使用摄影测量原理,从重叠图像生成高密度点云、正射影像及三维模型。它使LiDAR模块成为已经功能很强大的的必备Global Mapper扩展功能。 打开…

JavaScript高级 —— 学习(三)

目录 一、深入面向对象 &#xff08;一&#xff09;面向对象介绍 &#xff08;二&#xff09;面向对象编程 &#xff08;oop&#xff09; 1.面向对象编程介绍 2.面向对象编程优点 3.面向对象的特征 4.和面向过程编程对比 二、构造函数 &#xff08;一&#xff09;介绍…

【HTB】Trick 靶场

Trick靶场 地址&#xff1a;https://app.hackthebox.com/machines/477 打靶过程 靶机IP:10.129.227.180 1.信息收集 1.1 nmap 端口扫描 ┌──(root㉿kali)-[~/Desktop] └─# nmap -Pn -sC -sV -p- 10.129.227.180 --min-rate5000 Starting Nmap 7.94SVN ( https://nmap…

C++——list类及其模拟实现

前言&#xff1a;这篇文章我们继续进行C容器类的分享——list&#xff0c;也就是数据结构中的链表&#xff0c;而且是带头双向循环链表。 一.基本框架 namespace Mylist {template<class T>//定义节点struct ListNode{ListNode<T>* _next;ListNode<T>* _pre…

根据用户角色权限,渲染菜单的一个问题记录

个人博客&#xff1a;无奈何杨&#xff08;wnhyang&#xff09; 个人语雀&#xff1a;wnhyang 共享语雀&#xff1a;在线知识共享 Github&#xff1a;wnhyang - Overview 背景 之前一直讲过自己独立在做一个中后台管理系统&#xff0c;当然这个只是开始&#xff0c;未来会基…

Java复习第十四天学习笔记(CSS),附有道云笔记链接

【有道云笔记】十四 3.30 CSS https://note.youdao.com/s/3VormGXs 一、CSS定义和基本选择器 CSS定义&#xff1a;cascading style sheet 层叠样式表。 语法&#xff1a; 选择器 { 属性名1:属性值1; 属性名2:属性值2; 属性名3:属性值3; 属性名4:属性值4; } CSS使用&a…

实现顺序表(增、删、查、改)

引言&#xff1a;顺序表是数据结构中的一种形式&#xff0c;就是存储数据的一种结构。 这里会用到动态内存开辟&#xff0c;指针和结构体的知识 1.什么是数据结构 数据结构就是组织和存储数据的结构。 数据结构的特性&#xff1a; 物理结构&#xff1a;在内存中存储的数据是否连…

通过vite创建项目

一、VUE3官网 Vue.js - 渐进式 JavaScript 框架 | Vue.js (vuejs.org) 二、通过Vite创建项目 1、在cmd窗口下&#xff0c;全局安装vite //使用国内镜像源 npm config set registryhttps://registry.npmmirror.com//安装最新版vite npm install -g vitelatest Vite | 下一代…

Pygame基础11-mask 蒙版

蒙版 蒙版是二值化的图像&#xff0c;每个像素的值只能是0或1。 mask(蒙版)的用途&#xff1a; 碰撞检测部分着色 案例 和字母的碰撞检测 当玩家碰到字母 α \alpha α时&#xff0c;改变玩家颜色为绿色&#xff0c;否则为红色。 注意&#xff1a;我们希望碰到字母 α \alp…

考研数学1800还是660还是880?

24考完&#xff0c;大家都发现&#xff0c;没有一本习题册&#xff0c;覆盖了考试的所有知识点。 主流的模拟卷&#xff0c;都没有达到24卷的难度。 这就意味着&#xff1a; 一本习题册不够了&#xff01; 刷主流模拟卷不够了&#xff01; 这会需要整个考研复习的安排&…

C++(set和map详解,包含常用函数的分析)

set set是关联性容器 set的底层是在极端情况下都不会退化成单只的红黑树,也就是平衡树,本质是二叉搜索树. set的性质:set的key是不允许被修改的 使用set需要包含头文件 set<int> s;s.insert(1);s.insert(1);s.insert(1);s.insert(1);s.insert(2);s.insert(56);s.inser…

制造业工厂怎么通过MES系统来升级改造车间管理

在当今高度竞争的市场环境下&#xff0c;制造业企业需要不断提高生产效率&#xff0c;以在激烈的竞争中立于不败之地。而一种被广泛应用的方法就是利用MES控制系统&#xff0c;通过数字化管理和自动化控制来改造生产车间提升生产效率。 1、MES管理系统能够实现对生产过程的全面…

Navicat Premium 16 Mac/win---数据库设计、管理与维护轻松掌握数据库管理精髓

Navicat Premium是一款功能强大的数据库开发工具&#xff0c;支持多种数据库系统&#xff0c;如MySQL、Redis、MariaDB、Oracle等&#xff0c;并可与云数据库兼容&#xff0c;如Amazon RDS、Microsoft Azure等。它提供了直观易用的用户界面&#xff0c;使得开发者能够轻松上手并…

k8s calico由IPIP模式切换为BGP模式

按照官网calico.yaml部署后&#xff0c;默认是IPIP模式 查看route -n &#xff0c; 看到是tunl0口进行转发 怎么切换到BGP模式呢&#xff1f; kubectl edit ippool 将ipipMode由Always修改为Never &#xff0c;修改后保存文件即可。无需做任何操作&#xff0c;自动就切换为BG…

MySql实战--普通索引和唯一索引,应该怎么选择

在前面的基础篇文章中&#xff0c;我给你介绍过索引的基本概念&#xff0c;相信你已经了解了唯一索引和普通索引的区别。今天我们就继续来谈谈&#xff0c;在不同的业务场景下&#xff0c;应该选择普通索引&#xff0c;还是唯一索引&#xff1f; 假设你在维护一个市民系统&…

stm32cubeMX_io输入输出讲解

1创建项目&#xff08;可在专栏里找到&#xff09; 2进入当前页面点击引脚将弹出下图选项选择输入输出 带点击GPIO 点击引脚弹出如下选项根据需求选择 如有需要可以使用外部时钟&#xff1b;设置如图使用外部时钟 生成代码 将会弹出一个提示点击中间项//打开项目

HarmonyOS NEXT应用开发之MVVM模式

应用通过状态去渲染更新UI是程序设计中相对复杂&#xff0c;但又十分重要的&#xff0c;往往决定了应用程序的性能。程序的状态数据通常包含了数组、对象&#xff0c;或者是嵌套对象组合而成。在这些情况下&#xff0c;ArkUI采取MVVM Model View ViewModel模式&#xff0c;其…