【Linux】多路IO复用技术②——poll详解如何使用poll模型实现简易的一对多服务器(附图解与代码实现)
在阅读本篇博客之前,建议大家先去看一下我之前写的这篇博客,否则你很可能会一头雾水
【Linux】多路IO复用技术①——select详解&如何使用select模型在本地主机实现简易的一对多服务器(附图解与代码实现)
http://t.csdnimg.cn/kPjvk
如果你看完了上面这篇博客,或者对select的原理和网络通信方面有一定了解的话,就可以开始阅读下面的内容了。那么废话不多说,我们正式开始了。
目录
poll模型的优缺点
poll模型的相关接口
监听结构体struct pollfd
监听函数poll
poll函数使用时的注意事项
本地主机使用poll模型实现简易一对多服务器的程序实现
程序构成 结果图示
这篇博客为大家讲解的是多路IO复用技术②——poll
PS:由于poll模型的原理和select模型的实现原理基本一致,并且select的原理已经在上述的博客中详细讲过,所以这里不再浪费篇幅再讲一遍了
首先,我们先来了解一下poll模型的优缺点
poll模型的优缺点
优点:
- 在内网场景下,poll也算是不错的IO复用模式
- 对监听集合进行了传入传出分离设置,不需要用户再自己设置传入集合(监听集合)和传出集合(就绪集合)
- 相比select,poll可以监听的事件种类更加丰富(具体可见下面的博客接口部分——struct pollfd)
- 可以为不同的套接字设置不同的监听事件,不像select模型只能批量设置监听事件
- poll模型可以监听的socket数量不受1024的硬限制,允许用户自定义数组作为监听集合,数组想设置多大就设置多大(其实这也不能完全算优点,下面讲缺点时会讲为什么)
缺点:
- poll模型的兼容性极差,甚至部分linux系统都不认识poll模型,更别说windows系统了
- 随着select的持续使用,会产生大量的拷贝开销和挂载开销(原因和select模型一样)
- 与select模型一样,poll的监听也是通过一次次的遍历实现的,非常消耗CPU,会导致服务器吞吐能力会非常差。更可怕的是,select遍历的大小仅为1024,而poll模型遍历的大小是由用户决定的,如果用户设置的监听集合大小为100000,就意味着poll遍历的大小就是100000,服务器很可能会直接瘫痪
在了解了poll模型的优缺点后,我们来了解一下poll模型的相关函数
poll模型的相关接口
以下接口的头文件都是 #include <poll.h>
监听结构体struct pollfd
先来介绍一下poll模型中的监听集合是什么样的
poll中的监听集合是一个结构体数组,这里我们将变量名设为listen_array[size],写法如下所示:
#define SIZE 10000struct pollfd listen_array[SIZE];//用户自定义的监听集合pollfd结构体中的成员
struct pollfd
{int fd; //目标套接字的文件描述符,取消监听就设为-1short events; //想要监听什么事件short revetns;
};
我们来讲解一下revents这个成员的作用
当套接字就绪时触发某相关事件时,系统会将其设置为对应事件的宏定义,用户可以使用该成员判断套接字是否就绪
比如套接字中有数据来了,需要读取处理,触发读事件,系统就会自动将revents设置为读事件对应的常量POLLIN,用户就可以去通过判断revents是不是等于POLLIN来判断是否读事件就绪
events 和 revents 可取的值及对应事件如下图所示:

监听函数poll
先来介绍一下一会会用到的参数:
- #define SIZE 10000; //监听集合的大小
- struct pollfd listen_array[SIZE]; //自定义监听集合
- nfds_t nfds; //最大监听套接字的数量,一般传监听数组的大小
- int timeout; //工作模式
我们来介绍一下这个timeout,timeout有以下几种设置方式:
- timeout = -1:poll 调用后阻塞等待,直到被监视的某个文件描述符上的某个事件就绪。
- timeout = 0:poll 调用后非阻塞等待,无论被监视的文件描述符上的事件是否就绪,poll 检测后都会立即返回。
- timeout = 特定的时间值:poll 调用后在指定的时间内进行阻塞等待,如果被监视的文件描述符上一直没有事件就绪,则在经过长度为timeout的时间后, poll 进行超时返回。(以毫秒为级别)
| 函数 | 功能 | 返回值 |
| int poll(listen_array , nfds , timeout); | 监听集合中是否有套接字就绪 | 1.函数调用成功,则返回就绪的套接字个数 2.如果 timeout 时间耗尽,但没有套接字就绪,则返回 0。 3.如果函数调用失败,则返回 -1,同时会传出错误码。 |
常见的错误码有以下四种:
- EFAULT:数组listen_array不包含在调用程序的地址空间中。
- EINTR:此调用被信号所中断。
- EINVAL:nfds值的大小超过RLIMIT_NOFILE。
- ENOMEM:核心内存不足。
poll函数使用时的注意事项
既然前面都说了:poll模型可以监听的socket数量不受1024的硬限制,允许用户自定义数组作为监听集合,数组想设置多大就设置多大
那我现在写一段小代码,大家看一看这个程序对不对,系统会不会报错(假设已经完成了网络初始化并已经设置了服务器套接字监听)
int ready_num;//阻塞监听socket相关事件
if((ready_num = poll(client_sockfd_array , 4096 , -1)) == -1)
{perror("poll call failed\n");exit(0);
}
else
{printf("1\n");
}
怎么样?有自己的结果了吗?接下来,我们公布正确答案
这个程序是错的!!!!!
为什么呢?这就要牵涉到进程相关的知识了
这是因为一个进程默认打开的最大文件描述符个数就是1024,我们可以通过ulimit -a命令在终端下查看,如下图所示

所以,当我们在poll函数中的最大监听数那个位置,填入比1024更大的数值的话,系统就会报错,警告Invalid argument——无效的参数
想要填入比1024更大的数值,我们就需要去修改默认的文件描述符数量,由于每个系统,甚至每个版本改动文件描述符数量的操作方式不一定一样,所以永久修改文件描述符数量的方式,这里就不多作介绍了,感兴趣的同学可以去查一下对应自己系统、对应自己版本的修改方式
我们这里简单介绍一下只对当前终端生效的修改方式,如下图所示

使用poll模型实现简易一对多服务器的程序实现
程序构成
该服务器与客户端由以下几个程序共同组成:
- func_2th_parcel.h:定义二次包裹的函数名
- func_2th_parcel.c:对网络初始化相关的函数进行二次包裹
- poll_server.c:使用poll模型的服务器程序
- client.c:客户端程序
/*************************************************************************> File Name: func_2th_parcel.h> Author: Nan> Mail: **@qq.com> Created Time: 2023年10月18日 星期三 18时32分22秒************************************************************************/#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <signal.h>
#include <pthread.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <sys/mman.h>
#include <time.h>
#include <ctype.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <poll.h>
#include <sys/epoll.h>//socket函数的二次包裹
int SOCKET(int domain , int type , int protocol);//bind函数的二次包裹
int BIND(int sockfd , const struct sockaddr* addr , socklen_t addrlen);//listen函数的二次包裹
int LISTEN(int sockfd , int backlog);//send函数的二次包裹
ssize_t SEND(int sockfd , const void* buf , size_t len , int flags);//recv函数的二次包裹
ssize_t RECV(int sockfd , void* buf , size_t len , int flags);//connect函数的二次包裹
int CONNECT(int sockfd , const struct sockaddr* addr , socklen_t addrlen);//accept函数的二次包裹
int ACCEPT(int sockfd , struct sockaddr* addr , socklen_t addrlen);//网络初始化函数
int SOCKET_NET_CREATE(const char* ip , int port);//服务端与客户端建立连接并返回客户端套接字文件描述符
int SERVER_ACCEPTING(int server_fd);
/*************************************************************************> File Name: func_2th_parcel.c> Author: Nan> Mail: **@qq.com> Created Time: 2023年10月18日 星期三 18时32分42秒************************************************************************/#include <func_2th_parcel.h>int SOCKET(int domain , int type , int protocol){int return_value;if((return_value = socket(domain , type , protocol)) == -1){perror("socket call failed!\n");return return_value;}return return_value;
}int BIND(int sockfd , const struct sockaddr* addr , socklen_t addrlen){int return_value; if((return_value = bind(sockfd , addr , addrlen)) == -1){perror("bind call failed!\n");return return_value;} return return_value;
}int LISTEN(int sockfd , int backlog){int return_value; if((return_value = listen(sockfd , backlog)) == -1){perror("listen call failed!\n");return return_value;} return return_value;
}ssize_t SEND(int sockfd , const void* buf , size_t len , int flags){ssize_t return_value;if((return_value = send(sockfd , buf , len , flags)) == -1){perror("send call failed!\n");return return_value;}return return_value;
}ssize_t RECV(int sockfd , void* buf , size_t len , int flags){ssize_t return_value; if((return_value = recv(sockfd , buf , len , flags)) == -1){perror("recv call failed!\n");return return_value;} return return_value;
}int CONNECT(int sockfd , const struct sockaddr* addr , socklen_t addrlen){int return_value; if((return_value = connect(sockfd , addr , addrlen)) == -1){perror("connect call failed!\n");return return_value;} return return_value;
}int ACCEPT(int sockfd , struct sockaddr* addr , socklen_t addrlen){int return_value; if((return_value = accept(sockfd , addr , &addrlen)) == -1){perror("accept call failed!\n");return return_value;} return return_value;
}int SOCKET_NET_CREATE(const char* ip , int port){int sockfd;struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(port);inet_pton(AF_INET , ip , &addr.sin_addr.s_addr);sockfd = SOCKET(AF_INET , SOCK_STREAM , 0);BIND(sockfd , (struct sockaddr*)&addr , sizeof(addr));LISTEN(sockfd , 128);return sockfd;
}int SERVER_ACCEPTING(int server_fd)
{int client_sockfd;struct sockaddr_in client_addr;char client_ip[16];char buffer[1500];bzero(buffer , sizeof(buffer));bzero(&client_addr , sizeof(client_addr));socklen_t addrlen = sizeof(client_addr);client_sockfd = ACCEPT(server_fd , (struct sockaddr*)&client_addr , addrlen);bzero(client_ip , 16);//将客户端的IP地址转成CPU可以识别的序列并存储到client_ip数组中inet_ntop(AF_INET , &client_addr.sin_addr.s_addr , client_ip , 16);sprintf(buffer , "Hi , %s welcome tcp test server service..\n" , client_ip);printf("client %s , %d , connection success , client sockfd is %d\n" , client_ip , ntohs(client_addr.sin_port) , client_sockfd);SEND(client_sockfd , buffer , strlen(buffer) , MSG_NOSIGNAL);return client_sockfd;
}
/*************************************************************************> File Name: poll_server.c> Author: Nan> Mail: **@qq.com> Created Time: 2023年10月25日 星期三 18时53分30秒************************************************************************/#include <func_2th_parcel.h>int main(void)
{//一、进行网络初始化int server_sockfd;//服务器套接字文件描述符struct pollfd client_sockfd_array[1024];//存放客户端套接字相关结构体的数组 int client_sockfd;//客户端套接字文件描述符int ready_num;//获取处于就绪状态的套接字数目char rw_buffer[1500];//读写缓冲区int flag;int recv_len;//客户端发来的数据长度//将结构体数组中对应套接字文件描述符的那一位置为-1,方便后面查找就绪套接字for(int i = 1 ; i < 1024 ; i++){//从1开始初始化是因为,0那一位要留给服务器套接字client_sockfd_array[i].fd = -1;client_sockfd_array[i].events = POLLIN;//都设置为监听读事件}bzero(rw_buffer , sizeof(rw_buffer));server_sockfd = SOCKET_NET_CREATE("192.168.79.128" , 6060);//初始化服务器套接字网络信息结构体//将服务器套接字结构体初始化一下client_sockfd_array[0].fd = server_sockfd;client_sockfd_array[0].events = POLLIN;printf("poll_server wait TCP connect\n");//二、启动监听,等待socket相关事件while(1){//阻塞等待socket读相关事件if((ready_num = poll(client_sockfd_array , 1024 , -1)) == -1){perror("poll call failed\n");exit(0);}//printf("readynum = %d\n" , ready_num);while(ready_num){//辨别就绪,如果是服务端套接字就绪if(client_sockfd_array[0].revents == POLLIN){client_sockfd = SERVER_ACCEPTING(client_sockfd_array[0].fd);//与客户端建立TCP链接for(int i = 1 ; i < 1024 ; i++){//将该客户端套接字,放到数组中有空缺的地方if(client_sockfd_array[i].fd == -1){client_sockfd_array[i].fd = client_sockfd;break;}}client_sockfd_array[0].revents = 0;//清0,防止ready_num > 1时会多次误判断就绪套接字为服务器套接字}//如果是客户端套接字就绪else{for(int i = 1 ; i < 1024 ; i++){//检测数组中下标位为i的地方是否存放的有客户端套接字文件描述符if(client_sockfd_array[i].fd != -1){//如果存放的有客户端套接字文件描述符,且该套接字处于就绪状态if(client_sockfd_array[i].revents == POLLIN){recv_len = RECV(client_sockfd_array[i].fd , rw_buffer , sizeof(rw_buffer) , 0);//获取数据长度printf("客户端%d 发来数据 : %s , 现在进行处理\n" , client_sockfd_array[i].fd , rw_buffer);flag = 0;//如果recv_len = 0,就说明与客户端套接字对应的客户端退出了,将对应客户端套接字移出监听集合if(recv_len == 0){printf("客户端%d 已下线\n" , client_sockfd_array[i].fd);close(client_sockfd_array[i].fd);client_sockfd_array[i].fd = -1;break;}//如果recv_len > 0,说明需要进行业务处理:小写字母转大写字母while(recv_len > flag){rw_buffer[flag] = toupper(rw_buffer[flag]);flag++;}printf("已向客户端%d 发送处理后的数据 : %s\n" , client_sockfd_array[i].fd , rw_buffer);SEND(client_sockfd_array[i].fd , rw_buffer , recv_len , MSG_NOSIGNAL);//发送处理后的数据给客户端bzero(rw_buffer , sizeof(rw_buffer));//清空读写缓冲区recv_len = 0;//重置数据长度client_sockfd_array[i].revents = 0;//清0,防止ready_num > 1时会多次误判断就绪套接字为该客户端套接字break;}}}}ready_num--;//已经处理一个,就绪套接字数量-1}}close(server_sockfd);printf("server shutdown\n");return 0;
}
/*************************************************************************> File Name: client.c> Author: Nan> Mail: **@qq.com> Created Time: 2023年10月19日 星期四 18时29分12秒************************************************************************/#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <signal.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <time.h>//服务器实现大小写转换业务int main()
{//1.定义网络信息结构体与读写缓冲区并初始化struct sockaddr_in dest_addr;char buffer[1500];bzero(&dest_addr , sizeof(dest_addr));bzero(buffer , sizeof(buffer));dest_addr.sin_family = AF_INET;dest_addr.sin_port = htons(6060);//字符串ip转大端序列inet_pton(AF_INET , "192.168.79.128" , &dest_addr.sin_addr.s_addr);int sockfd = socket(AF_INET , SOCK_STREAM , 0);int i;//2.判断连接是否成功if((connect(sockfd , (struct sockaddr*) &dest_addr , sizeof(dest_addr))) == -1){perror("connect failed!\n");exit(0);}recv(sockfd , buffer , sizeof(buffer) , 0);printf("%s" , buffer);bzero(buffer , sizeof(buffer));//3.循环读取终端输入的数据while( (fgets(buffer , sizeof(buffer) , stdin) ) != NULL){i = strlen(buffer);buffer[i-1] = '\0';//向服务端发送消息send(sockfd , buffer , strlen(buffer) , MSG_NOSIGNAL);//接收服务端发来的消息recv(sockfd , buffer , sizeof(buffer) , 0);//打印服务端发来的信息printf("response : %s\n" , buffer);//清空读写缓冲区,以便下一次放入数据bzero(buffer , sizeof(buffer));}//4.关闭套接字,断开连接close(sockfd);return 0;
}
结果图示

以上就是本篇博客的全部内容了,大家有什么地方没有看懂的话,可以在评论区留言给我,咱要力所能及的话就帮大家解答解答
今天的学习记录到此结束啦,咱们下篇文章见,ByeBye!

相关文章:
【Linux】多路IO复用技术②——poll详解如何使用poll模型实现简易的一对多服务器(附图解与代码实现)
在阅读本篇博客之前,建议大家先去看一下我之前写的这篇博客,否则你很可能会一头雾水 【Linux】多路IO复用技术①——select详解&如何使用select模型在本地主机实现简易的一对多服务器(附图解与代码实现)http://t.csdnimg.cn/…...
CSS 滚动捕获 Scroll Snap
CSS 滚动捕获 Scroll Snap CSS 滚动捕获允许开发者通过声明一些位置(或叫作捕获位置)来创建精准控制的滚动体验. 通常来说轮播图就是这种体验的例子, 在轮播图中, 用户只能停在图 A 或者图 B, 而不能停在 A 和 B 的中间. 比如平时用淘宝或小红书, 当你上滑到下一个推荐内容时…...
【带头学C++】----- 三、指针章 ---- 3.9 数组作为函数的参数
当数组作为函数参数时,有几种常见的方式可以传递数组给函数: 数组作为指针传递: 数组名在函数调用时会自动转换为指向数组第一个元素的指针。通过指针可以访问数组元素,但无法获取数组的大小。在函数中修改指针指向的值会影响原始…...
完美处理 Android App 的 apk 输出路径与文件名
实现代码 buildTypes {// ...applicationVariants.all {variant ->variant.outputs.all {Calendar calendar Calendar.getInstance(Locale.CHINA);def buildDate String.format(Locale.CHINA, "%04d%02d%02d", calendar.get(Calendar.YEAR), calendar.get(Cale…...
【技术干货】开源库 Com.Gitusme.Net.Extensiones.Core 的使用
目录 1、项目介绍 2、为项目添加依赖 3、代码中导入命名空间 4、代码中使用 示例 1:string转换 示例 2:object转换 1、项目介绍 Com.Gitusme.Net.Extensiones.Core是一个.Net扩展库。当前最新版本1.0.4,提供了常见类型转换,…...
大厂面试题-b树和b+树的理解
为了更清晰的解答这个问题,从三个方面来回答: a.了解二叉树、AVL树、B树的概念 b.B树和B树的应用场景 1.B树是一种多路平衡查找树,为了更形象的理解,我们来看这张图。 二叉树,每个节点支持两个分支的树结构ÿ…...
NeRF-SLAM部署运行(3060Ti)
记录在部署运行期间遇到的一些问题,分享给大家~ 一、环境 RTX 3060 Ti、8G显存、Ubuntu18.04 二、部署 1. 下载代码 git clone https://github.com/jrpowers/NeRF-SLAM.git --recurse-submodules git submodule update --init --recursive cd thirdparty/insta…...
零基础编程入门教程软件推荐,零基础编程自学
零基础编程入门教程软件推荐,零基础编程自学 给大家分享一款中文编程工具,零基础轻松学编程,不需英语基础,编程工具可下载。 这款工具不但可以连接部分硬件,而且可以开发大型的软件,象如图这个实例就是用…...
Amazon EC2 安全可调用的云虚拟主机服务器
💗wei_shuo的个人主页 💫wei_shuo的学习社区 🌐Hello World ! Amazon EC2 打造全新的科技链 Amazon Elastic Compute Cloud(Amazon EC2)提供最广泛、最深入的计算平台,拥有超过 500 个实例&…...
HTTP/HTTPS、SSL/TLS、WS/WSS 都是什么?
有同学问我,HTTP/HTTPS、SSL/TLS、WS/WSS 这都是些什么?那我们就先从概念说起: HTTP 是超文本传输协议,信息是通过明文传输。HTTPS 是在 HTTP 的基础上信息通过加密后再传输。SSL 是实现 HTTPS 信息传输加密的算法。TLS 是 SSL 的…...
软考之系统安全理论基础+例题
系统安全 系统安全性分析与设计 作为全方位的、整体的系统安全防范体系也是分层次的,不同层次反映了不同的安全问题,根据网络的应用现状情况和结构,可以将安全防范体系的层次划分为 物理层安全系统层安全网络层安全应用层安全安全管理。 …...
棱镜七彩亮相工控中国大会,以软件供应链安全助力新型工业化高质量发展
2023年11月1日-3日,2023第三届工控中国大会在苏州国际会议中心举办,本届大会由中国电子信息产业发展研究院、中国工业经济联合会、国家智能制造专家委员会、国家产业基础专家委员会、江苏省工业和信息化厅、江苏省国有资产监督管理委员会、苏州市人民政府…...
数据可视化:动态柱状图
终于来到最后一个数据可视化的文章拿啦~~~ 在这里学习如何绘制动态柱状图 我先整个活 (๑′ᴗ‵๑)I Lᵒᵛᵉᵧₒᵤ❤ 什么是pyecharts? 答: Python的Pyecharts软件包。它是一个用于Python数据可视化和图表绘制的库,可用于制作…...
vue3 自定义loading
使用antdv 后发现只有button支持loaidng属性,而其他元素不能使用loading来显示是否加载中,需要套一层 a-spin 才能支持,非常不方便。 所以写了个自定义的指令来进行处理 新建loading.vue文件用来页面显示 <template><div class&q…...
Ceph-deploy跳过gpg-key验证(离线环境安装Ceph)
问题 CentOS-7.6.1810离线环境搭建Ceph环境时出现gpg-key安装源公钥检查错误。原因是执行ceph-deploy install 命令的服务器无法访问互联网。具体报错如下图: 解决 安装命令后新增--no-adjust-repos参数即可跳过安装 GPG 密钥。 命令如下: ceph-deplo…...
想入行单片机开发的学生们的忠告
想入行单片机开发的学生们的忠告 做嵌入式单片机开发十来年。想给那些想入行单片机开发的同学一些建议。 1.想做这行,做好坚持学习的准备。最近很多小伙伴找我,说想要一些单片机的资料,然后我根据自己从业十年经验,熬夜搞了几个通…...
【番外篇】C++语法学习笔记
学习目标:C的一些高级操作 根据C菜鸟教程自学的笔记,大家有想学习C的话可以根据这个网站进行学习。这个推荐有一定基础的再去进行自学。新手的话还是建议直接看一些视频跟着学 学习内容: 1. 运算符重载 说到C中的运算符重载,首…...
js 字符串转数字
在 JavaScript 中,可以使用以下方法将字符串转换为数字: parseInt parseInt():将字符串转换为整数。它会从字符串的开头开始解析,直到遇到非数字字符为止。如果第一个字符不能转换为数字,则返回 NaN。 let str &qu…...
【NI-DAQmx入门】外部采样时钟相关
1.时钟的作用 时钟在几乎所有测量系统中都起着至关重要的作用。通过硬件定时测量,时钟控制采样或更新的发生时间。与依赖软件计时测量相比,您可以选择硬件定时测量来实现采样或更新之间更一致的时间间隔。以数模转换器特性分析为例。该应用由三个基本部分…...
Amazon EC2 Hpc7g 实例现已在更多区域推出
即日起,Amazon Elastic Compute Cloud (Amazon EC2) Hpc7g 实例将在亚太地区(东京)、欧洲地区(爱尔兰)和 Amazon GovCloud(美国西部)区域推出。Amazon EC2 Hpc7g 实例由 Amazon Graviton 处理器…...
使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...
MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例
一、MySQL 隔离级别 MySQL 提供了四种隔离级别,用于控制事务之间的并发访问以及数据的可见性,不同隔离级别对脏读、幻读、不可重复读这几种并发数据问题有着不同的处理方式,具体如下: 隔离级别脏读不可重复读幻读性能特点及锁机制读未提交(READ UNCOMMITTED)允许出现允许…...
PPT|230页| 制造集团企业供应链端到端的数字化解决方案:从需求到结算的全链路业务闭环构建
制造业采购供应链管理是企业运营的核心环节,供应链协同管理在供应链上下游企业之间建立紧密的合作关系,通过信息共享、资源整合、业务协同等方式,实现供应链的全面管理和优化,提高供应链的效率和透明度,降低供应链的成…...
屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!
5月28日,中天合创屋面分布式光伏发电项目顺利并网发电,该项目位于内蒙古自治区鄂尔多斯市乌审旗,项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站,总装机容量为9.96MWp。 项目投运后,每年可节约标煤3670…...
FFmpeg:Windows系统小白安装及其使用
一、安装 1.访问官网 Download FFmpeg 2.点击版本目录 3.选择版本点击安装 注意这里选择的是【release buids】,注意左上角标题 例如我安装在目录 F:\FFmpeg 4.解压 5.添加环境变量 把你解压后的bin目录(即exe所在文件夹)加入系统变量…...
深入理解Optional:处理空指针异常
1. 使用Optional处理可能为空的集合 在Java开发中,集合判空是一个常见但容易出错的场景。传统方式虽然可行,但存在一些潜在问题: // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...
LangFlow技术架构分析
🔧 LangFlow 的可视化技术栈 前端节点编辑器 底层框架:基于 (一个现代化的 React 节点绘图库) 功能: 拖拽式构建 LangGraph 状态机 实时连线定义节点依赖关系 可视化调试循环和分支逻辑 与 LangGraph 的深…...
【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...
小智AI+MCP
什么是小智AI和MCP 如果还不清楚的先看往期文章 手搓小智AI聊天机器人 MCP 深度解析:AI 的USB接口 如何使用小智MCP 1.刷支持mcp的小智固件 2.下载官方MCP的示例代码 Github:https://github.com/78/mcp-calculator 安这个步骤执行 其中MCP_ENDPOI…...
Qt Quick Controls模块功能及架构
Qt Quick Controls是Qt Quick的一个附加模块,提供了一套用于构建完整用户界面的UI控件。在Qt 6.0中,这个模块经历了重大重构和改进。 一、主要功能和特点 1. 架构重构 完全重写了底层架构,与Qt Quick更紧密集成 移除了对Qt Widgets的依赖&…...
