Linux C IO复用

IO复用

  • 概述
  • IO模型
    • 阻塞式IO
    • 非阻塞式IO
    • IO复用
      • select、poll、epoll异同
    • 信号驱动式IO
    • 异步IO
  • select函数
    • select示例代码
  • poll函数
    • poll示例代码
  • epoll函数
    • 创建  epoll_create
    • 注册、修改、删除  epoll_ctl
    • 轮询 I/O 事件的发生  epoll_wait
    • epoll示例代码
  • 基于TCP和epoll在线多人聊天室服务器例子

概述

  什么是IO复用呢?I/O复用(I/O multiplexing),指的是通过一个支持同时感知多个描述符的函数系统调用,阻塞在这个系统调用上,等待某一个或者几个描述符准备就绪,就返回可读条件。
  IO多路复用解决了什么问题呢?当多个客户端与服务器通信时,若服务器阻塞在其中一个客户的read(sockfd1,…),当另一个客户数据到达sockfd2时,服务器无法及时处理,此时需要用到IO多路复用。即同时监听n个客户,当其中有一个发来消息时就从select的阻塞中返回,然后调用read读取收到消息的sockfd,然后又循环回select阻塞。这样就解决了阻塞在一个消息而无法处理其它的。即用来解决对多个I/O监听时,一个I/O阻塞影响其他I/O的问题。
  这时候大家可能会想到线程和进程也能做到这样的效果啊(基于tcp多线程在线聊天室例子)?但是,CPU切换进程和线程的成本是很高的,这也正是IO复用的优点,可以做到用更少的资源完成更多的事

IO模型

在这里插入图片描述

阻塞式IO

  这种类型的IO会阻塞等待到有输入。这类IO大部分时间处于睡眠态、阻塞态、挂起态。优点是不占用 CPU 宝贵的时间片,但是同一时刻只能处理一个操作、效率比较低。
  如何理解呢?就像你在家里睡觉,等着你的外卖送到,送到了给你打电话你才起床去拿外卖。如果一次性到了很多外卖,你也只能一个电话一个电话去接听,然后挨着拿。
  应用场景多为多进程、多线程的TCP 并发服务器、客户端。

非阻塞式IO

  该种类型的IO需要程序轮询检测输入,如果有输入就调用IO进行读取数据。优点是提高了程序的执行效率,但是需要占用更多的 CPU 和系统资源,使得 CPU 负荷增高。
  我们还举外卖的例子。这种情况就像你非常饿,一直顶着配送地图,一分钟刷新一次地图,看看到了没,到了你就立即下楼去拿,拿完了继续刷新地图看看别的还有多久到。

IO复用

  这类IO采用阻塞式IO+组长机制。多路IO共用一个同步阻塞接口,任意IO可操作都可激活IO操作。它能同时等待多个文件描述符,而这些文件描述符其中的任意一个进入读就绪状态,函数就可以返回。
  IO复用包含了select、poll和epoll。其中select相当于你家楼下放外卖的桌子,你得自己挨个外卖看一遍,直达找到你的外卖。而epoll像外卖柜,你只要看一下手机就知道你的外卖在哪个柜子里面。

select、poll、epoll异同

相同之处:
1 . 都可以用于监听多个文件描述符的读写事件。
2 . 都是阻塞式的,即当没有任何事件发生时,它们都会一直阻塞等待。
3 . 都可以通过设置超时时间来控制阻塞等待的时间。
不同之处:
1 . select和poll使用轮询的方式来检查所有的文件描述符,而epoll使用回调的方式,只有在有事件发生时才会调用回调函数。
2 . select和poll的文件描述符集合是通过参数传递给函数的,而epoll使用epoll_ctl函数来注册和删除文件描述符,通过epoll_wait函数来等待事件。
3 . select和poll对于大量的文件描述符来说,性能会下降,因为每次都需要遍历整个文件描述符集合,而epoll使用红黑树来存储文件描述符,效率更高。
4 . select和poll支持的文件描述符数量有一定的限制,而epoll没有明确的限制。
  总的来说,epoll相对于select和poll来说,在处理大量的并发连接时性能更好,并且使用更方便。

信号驱动式IO

  注册一个IO信号事件,在数据可操作时通过SIGIO信号通知线程,这应该算是一种异步机制.

异步IO

  应用进程通知内核开始一个异步I/O操作,并让内核在整个操作(包含将数据从内核复制到应该进程的缓冲区)完成后通知应用进程。例如POSIX 的&IO _系列函数。

select函数

头文件:
  #include <sys/select.h>
  #include <sys/time.h>
  #include <sys/types.h>
  #include <unistd.h>
函数原型:int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
参数介绍:
  nfds:集合中所有文件描述符的范围,即所有文件描述符的最大值+1。
  readfds:监听的读事件文件描述符集合。
  writefds:监听的写事件文件描述符集合。
  exceptfds:意外文件描述符集合。
  timeout:
>    永远等待--空指针等待固定时间--timeval结构体内的时间

返回值:返回值是一个整数,表示有多少个文件描述符已经就绪。如果返回0,表示在指定的超时时间内没有任何文件描述符就绪;如果返回-1,表示发生错误。

//FD_CLR(inr fd,fd_set* set);用来清除描述词组 set 中相关 fd 的位
//FD_ISSET(int fd,fd_set *set);用来测试描述词组 set 中相关 fd 的位是否为真(遍历检测函数)
//FD_SET(int fd,fd_set*set);用来设置描述词组 set 中相关 fd 的位
//FD_ZERO(fd_set *set);用来清除描述词组 set 的全部位(在初始化时用到以免里面有垃圾值)fd_set readfds;FD_ZERO(&readfds);//清空FD_SET(socketfd,&readfds); //用来设置描述词组 set 中相关 socketfd-fd 的位fd_set changeReadfds = readfds;int count = select(nfds,&useChangeReadfds, NULL,NULL,0);

select示例代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <time.h>
#include <sys/select.h>
typedef struct sockaddr  SA;
typedef struct sockaddr_in  SIN;
#define MAXBACKLOG   100int Socket(int domain,int type,int protocol);
int Bind(int sockfd,struct sockaddr * my_addr,int addrlen);
int Listen(int s,int backlog);
int Accept(int s,struct sockaddr * addr,int * addrlen);
void *  clientRecvDataFunction(void * arg);//运行格式 ./app 192.168.5.166  8888
int main(int argc,char *argv[])
{	int  opt =1;//建立监听套接字int socketfd = Socket(AF_INET,SOCK_STREAM,0);//需要进行重用地址及其端口号setsockopt(socketfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));//绑定信息编写服务器信息SIN   serverinfo;serverinfo.sin_family =AF_INET;		//协议IPV4serverinfo.sin_port   =htons(atoi(argv[2]));	//网络字节序(大端字节序)与主机字节序(小端字节序)  serverinfo.sin_addr.s_addr=  inet_addr(argv[1]);int addrlen = sizeof(SIN);Bind(socketfd,(SA*)&serverinfo,addrlen);//监听Listen(socketfd,MAXBACKLOG);//select函数参数填写int nfds = socketfd+1;fd_set readfds;		//栈区定义  先清空  再写入位FD_ZERO(&readfds);	//清空FD_SET(socketfd,&readfds); //用来设置描述词组set中相关socketfd-fd的位//读写while(1){fd_set useChangeReadfds = readfds;int count = select(nfds,&useChangeReadfds, NULL,NULL,0);if(count >0){//文件描述符socketfd调用acceptif(FD_ISSET(socketfd,&useChangeReadfds)){SIN clientinfo;int  clientaddrlen =sizeof(SA);int newfd = Accept(socketfd,(SA*)&clientinfo,&clientaddrlen);printf("客户端地址:%s 端口号:%d\n",inet_ntoa(clientinfo.sin_addr),ntohs(clientinfo.sin_port));//将新的文件描述符添加至readfdsFD_SET(newfd,&readfds);//调整检测范围newfd >= nfds?(nfds++):(nfds =nfds);}//文件描述符非socketfd调用readelse{for(int startFd = socketfd+1; startFd < nfds;startFd++){if(FD_ISSET(startFd,&useChangeReadfds)){//读取客户端发送来的数据char readbuff[512]={0};int len = read(startFd,readbuff,sizeof(readbuff));if(len > 0){printf("%d:%s\n",startFd,readbuff);}else if(len == 0){printf("%d:客户端退出\n",startFd);FD_CLR(startFd,&readfds);//存在弊端nfds不好调整close(startFd);}else if(len < 0){printf("%d:客户端异常退出\n",startFd);FD_CLR(startFd,&readfds);//存在弊端nfds不好调整close(startFd);}}}}}}//关闭close(socketfd);return 0;
}
int Socket(int domain,int type,int protocol)
{int socketFd = socket(domain,type,protocol);if(socketFd ==-1){perror("socket");exit(1);}return socketFd;
}
int Bind(int sockfd,struct sockaddr * my_addr,int addrlen)
{int val = bind(sockfd,my_addr,addrlen);if(val){perror("bind");exit(1);}return 0;
}
int Listen(int s,int backlog)
{int val = listen(s,backlog);if(val == -1){perror("listen");exit(1);}return val;
}
int Accept(int s,struct sockaddr * addr,int * addrlen)
{int NEWfd= accept(s,addr,addrlen);if(NEWfd == -1){perror("listen");exit(1);}return NEWfd;
}

poll函数

头文件:
  #include <poll.h>
函数原型:int poll(struct pollfd fd[], nfds_t nfds, int timeout);
参数介绍:
  fd[]:文件描述符结构体。
  nfds:指定结构体数组元素个数。
  timeout:
在这里插入图片描述

返回值:成功时 poll() 返回结构体中 revents 域不为 0 的文件描述符个数,如果在超时前没有任何事件发生,poll()返回 0。

//struct pollfd结构体内容
struct pollfd
{int fd; //文件描述符 所要检测的文件描述符short events; //请求的事件 检测文件描述符的事件short revents; //返回的事件(内核给的反馈)
};struct pollfd fds[1025];//清空结构体数组//清空结构体数组for(int i = 0;i < sizeof(fds)/sizeof(struct pollfd);i++){fds[i].fd = -1;}//初始化填入 socketfd;fds[0].fd = socketfd; //文件描述符fds[0].events = POLLIN; //读事件//nfds 用来指定第一个参数数组元素个数;int nfds = 1;int count = poll(fds,nfds, -1);

poll示例代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <time.h>
#include <poll.h>
typedef struct sockaddr  SA;
typedef struct sockaddr_in  SIN;
#define MAXBACKLOG   100int Socket(int domain,int type,int protocol);
int Bind(int sockfd,struct sockaddr * my_addr,int addrlen);
int Listen(int s,int backlog);
int Accept(int s,struct sockaddr * addr,int * addrlen);
void *  clientRecvDataFunction(void * arg);//./app 192.168.5.166  8888
int main(int argc,char *argv[])
{	int  opt =1;//建立监听套接字int socketfd = Socket(AF_INET,SOCK_STREAM,0);//需要进行重用地址及其端口号setsockopt(socketfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));//绑定信息编写服务器信息SIN   serverinfo;serverinfo.sin_family =AF_INET;		//协议IPV4serverinfo.sin_port   =htons(atoi(argv[2]));serverinfo.sin_addr.s_addr=  inet_addr(argv[1]);int addrlen = sizeof(SIN);Bind(socketfd,(SA*)&serverinfo,addrlen);//监听Listen(socketfd,MAXBACKLOG);//pollt函数参数填写struct pollfd fds[1025];//清空结构体数组for(int i = 0;i < sizeof(fds)/sizeof(struct pollfd);i++){fds[i].fd = -1;}//初始化填入socketfd;fds[0].fd = socketfd;		//文件描述符fds[0].events = POLLIN;		//读事件//nfds用来指定第一个参数数组元素个数;int nfds = 1;//读写while(1){int count = poll(fds,nfds, -1);if(count > 0){//检查fds[0]的fd是否发生动作------>acceptif(fds[0].revents == POLLIN){SIN clientinfo;int  clientaddrlen =sizeof(SA);int newfd = Accept(socketfd,(SA*)&clientinfo,&clientaddrlen);printf("客户端地址:%s 端口号:%d\n",inet_ntoa(clientinfo.sin_addr),ntohs(clientinfo.sin_port));//需要将newfd放至fds中for(int i = 0; i < sizeof(fds)/sizeof(struct pollfd);i++){if(fds[i].fd == -1){fds[i].fd = newfd;			//文件描述符fds[i].events = POLLIN;		//读事件i >= nfds?(nfds++):(nfds =nfds);	//nfds与下标有联系break;}}}else{//检查fds[>0]的是否发送动作-------->readfor(int startfd = 1;startfd<nfds;startfd++){if(fds[startfd].revents == POLLIN){char readbuff[512]={0};int len = read(fds[startfd].fd,readbuff,sizeof(readbuff));if(len > 0){printf("%d:%s\n",fds[startfd].fd,readbuff);}else if(len == 0){printf("%d:客户端退出\n",fds[startfd].fd);//归位处理close(fds[startfd].fd);fds[startfd].fd=-1;}else if(len < 0){printf("%d:客户端异常退出\n",fds[startfd].fd);//归位处理close(fds[startfd].fd);fds[startfd].fd=-1;}}}}}}//关闭close(socketfd);return 0;
}
//下面的函数与select相同

epoll函数

创建  epoll_create

头文件:
  #include <sys/epoll.h>
函数原型:int epoll_create(int size)
参数介绍:
  size:标识这个监听的数目最大有多大。
返回值: 成功时,这些系统调用将返回非负文件描述符。如果出错,则返回-1,并且将errno设置为指示错误。

	int epollfd = epoll_create(1024);

注册、修改、删除  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_CTL_MOD 修改、EPOLL_CTL_DEL 删除。
  fd:关联的文件描述符。
  event:指向 epoll_event 的指针。
返回值:成功返回1,失败返回0.

//结构体 epoll_event 被用于注册所感兴趣的事件和回传所发生待处理的事件,定义如下:
typedef union epoll_data_t { //联合体void *ptr;int fd; //比较常用__uint32_t u32;__uint64_t u64;
} epoll_data_t;//保存触发事件的某个文件描述符相关的数据struct epoll_event {__uint32_t events; /* epoll event */epoll_data_t data; /* User data variable */
};
/*其中 events 表示感兴趣的事件和被触发的事件,可能的取值为:
EPOLLIN:表示对应的文件描述符可以读;
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数可读;
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: ET 的 epoll 工作模式;*/struct epoll_event event;event.events = EPOLLIN; //事件成员event.data.fd = socketfd; //数据epoll_ctl(epollfd,EPOLL_CTL_ADD,socketfd, &event);

轮询 I/O 事件的发生  epoll_wait

头文件:
  #include <sys/epoll.h>
函数原型:int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout);
参数介绍:
  epfd:epoll 专用的文件描述符。
  events:回传处理事件的数组。
  maxevents:每次能处理的事件数。
  timeout:等待 I/O 事件发生的超时值。
返回值:返回发生的事件数,失败返回-1。

	int count = epoll_wait(epollfd,events,10,-1);

epoll示例代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <time.h>
#include <sys/epoll.h>typedef struct sockaddr  SA;
typedef struct sockaddr_in  SIN;
#define MAXBACKLOG   100int Socket(int domain,int type,int protocol);
int Bind(int sockfd,struct sockaddr * my_addr,int addrlen);
int Listen(int s,int backlog);
int Accept(int s,struct sockaddr * addr,int * addrlen);
void *  clientRecvDataFunction(void * arg);//./app 192.168.5.166  8888
int main(int argc,char *argv[])
{	int  opt =1;//建立监听套接字int socketfd = Socket(AF_INET,SOCK_STREAM,0);//需要进行重用地址及其端口号setsockopt(socketfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));//绑定信息编写服务器信息SIN   serverinfo;serverinfo.sin_family =AF_INET;		//协议IPV4serverinfo.sin_port   =htons(atoi(argv[2]));serverinfo.sin_addr.s_addr=  inet_addr(argv[1]);int addrlen = sizeof(SIN);Bind(socketfd,(SA*)&serverinfo,addrlen);//监听Listen(socketfd,MAXBACKLOG);//epollt创建根节点int epollfd = epoll_create(1024);//添加socketfd文件描述符至内核 红黑树struct epoll_event event;event.events = EPOLLIN;			//事件成员event.data.fd = socketfd;		//数据epoll_ctl(epollfd,EPOLL_CTL_ADD,socketfd, &event);//读写while(1){struct epoll_event  events[10];int count = epoll_wait(epollfd,events,10,-1);if(count > 0){for(int i = 0; i< count;i++){if(events[i].events == EPOLLIN){if(events[i].data.fd==socketfd){//等待连接SIN clientinfo;struct epoll_event event;int  clientaddrlen =sizeof(SA);int newfd = Accept(socketfd,(SA*)&clientinfo,&clientaddrlen);printf("客户端地址:%s 端口号:%d\n",inet_ntoa(clientinfo.sin_addr),ntohs(clientinfo.sin_port));//需要将newfd放至红黑树中event.events = EPOLLIN;			//事件成员event.data.fd = newfd;			//数据epoll_ctl(epollfd,EPOLL_CTL_ADD,newfd, &event);}else{//readchar readbuff[512]={0};int len = read(events[i].data.fd,readbuff,sizeof(readbuff));if(len > 0){printf("%d:%s\n",events[i].data.fd,readbuff);}else if(len == 0){printf("%d:客户端退出\n",events[i].data.fd);epoll_ctl(epollfd,EPOLL_CTL_DEL,events[i].data.fd,NULL);close(events[i].data.fd);}else if(len < 0){printf("%d:客户端异常退出\n",events[i].data.fd);epoll_ctl(epollfd,EPOLL_CTL_DEL,events[i].data.fd,NULL);close(events[i].data.fd);}}}}}}//关闭close(socketfd);return 0;
}
//下面的函数与select相同

基于TCP和epoll在线多人聊天室服务器例子

点我查看

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

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

相关文章

赞!优雅的Python多环境管理神器!易上手易操作!

前言 Python 的不同版本之间常常存在依赖关系和兼容性问题&#xff0c;为了方便开发人员在 不同项目中使用不同的版本 。 如果大家使用过Python版本管理工具&#xff0c;肯定大多数人使用的都是Anaconda&#xff0c;它是一个优秀的数据科学开发环境&#xff0c;本身也提供了丰…

通达信吊灯止损指标公式,根据波动幅度自动调整止盈止损

吊灯止损指标是由查克勒博(Chuck LeBeau)发明的&#xff0c;亚历山大埃尔德(Alexander Elder)在其著作《走进我的交易室》中介绍了这种止盈止损方法&#xff08;中文版翻译为倒挂式离场法则&#xff09;&#xff0c;它是根据平均真实波幅ATR设置跟踪止损。吊灯止损指标的目的是…

Redis性能压测、监控工具及优化方案

Redis是一款高性能的开源缓存数据库&#xff0c;但是在实际应用中&#xff0c;我们需要对Redis进行性能压测、监控以及优化&#xff0c;以确保其稳定性和高可用性。本文将介绍Redis性能压测、监控工具及优化方案。 01 Redis性能压测 常用的Redis性能压测工具有&#xff1a; …

909-2015-T3

文章目录 1.原题2.算法思想2.1.求树的高度2.2.求路径 3.关键代码4.完整代码5.输出结果 1.原题 试编写算法&#xff0c;求给定二叉树上从根节点到叶子节点的一条路径长度等于树的深度减一的路径&#xff08;即列出从根节点到该叶子节点的节点序列&#xff09;&#xff0c;若这样…

MySQL数据库常见错误及解决方案

“时记数据安全,共享优质资源”,数据库安全是指数据库数据的完整、真实、可靠和可用性。数据库也是一种软件系统,与其他软件系统一样也需要保护,需要采取一定的技术和一定的安全管理策略,保证数据库中的数据不被泄漏、不被破坏、不被修改或删除。本文列举MySQL数据库常见错…

“index“ should always be multi-word

vue报错&#xff1a;Component name “index” should always be multi-word 分析&#xff1a;组件名要以驼峰格式命名&#xff0c;自定义的要以loginIndex.vue等这种方式命名&#xff0c;防止和html标签冲突&#xff0c;所以命名index.vue 会报错 解决&#xff1a;在.eslint…

性能测试:系统架构性能优化思路

今天谈下业务系统性能问题分析诊断和性能优化方面的内容。这篇文章重点还是谈已经上线的业务系统后续出现性能问题后的问题诊断和优化重点。 系统性能问题分析流程 我们首先来分析下如果一个业务系统上线前没有性能问题&#xff0c;而在上线后出现了比较严重的性能问题&#x…

RTS 客户端-服务器网络

Stone Monarch 从一开始就支持多人游戏&#xff0c;但随着时间的推移&#xff0c;网络模型经历了多次迭代。我最初基于这篇著名的帝国时代文章实现了点对点锁步模型。 点对点锁定步骤有一些众所周知的问题。点对点方面使玩家很难相互连接&#xff0c;并增加了每个新玩家的网络…

【无标题】dp80采集机和机器人通信相关框架总结

采血机器人通信解析相关框架总结: 类似于dp80,将整个过程进行了分解如下: 类似于dp80,将整个过程进行了分解如下: 上位机界面在进行点击操作的时候,先是通信协议的解析,解析后改变采血的控制状态如下: Dp80主要框架解析࿱

华为obs上传下载-Java版 2023-11-23

弄了半天&#xff0c;老师帮弄成功了&#xff0c;经过同意&#xff0c;分享到网上&#xff0c;希望能帮助更多人&#xff0c;至于怎么弄的&#xff0c;我也不知道。 创建idea项目后&#xff0c;项目结构&#xff0c;对应文件没有的创一个 pom.xm 注意改Java版本&#xff0c;我…

dvwa-command injection 代码审计(超详细逐行审计)

dvwa-command injection 代码审计 low <?phpif( isset( $_POST[ Submit ] ) ) {// Get input$target $_REQUEST[ ip ];// Determine OS and execute the ping command.if( stristr( php_uname( s ), Windows NT ) ) {// Windows$cmd shell_exec( ping . $target );}…

AMESim|Make failed:Unable to create an excutable for the system

最近在AMESIM与MATLAB进行联合仿真的时候遇到如下问题&#xff1a; Make failed:Unable to create an excutable for the system. 看了网上的解决办法如下 配置环境变量重装AMESIM&#xff0c;有顺序要求&#xff0c;首先是VS&#xff0c;然后是AMESIM与MATLAB。在AMESIM安装…

csdn最新最全pytest系列——pluggy插件源码解读(一)HookspecMarker类和HookimplMarker类分析

简介 pluggy是一个非常优秀的插件系统&#xff0c;它是理解pytest的核心&#xff0c;只有理解了pluggy的原理&#xff0c;才能更好的理解和使用pytest&#xff0c;否则见到了pytest的很多应用都会感觉很难理解 pluggy插件总共的代码量不足一千行&#xff0c;而实现的功能却是…

IDEA 配置maven结合案例使用篇

1. 项目需求和结构分析 需求案例&#xff1a;搭建一个电商平台项目&#xff0c;该平台包括用户服务、订单服务、通用工具模块等。 项目架构&#xff1a; 用户服务&#xff1a;负责处理用户相关的逻辑&#xff0c;例如用户信息的管理、用户注册、登录等。 spring-context 6.0.…

5-2计算pi

#include<stdio.h> #include<math.h>int main(){int sign1;//数值的符号int count0;//累计计算循环的次数double pi0.0;double n1;//分母double term1.0;//当前项的数while(fabs(term)>1e-6){//fabs(trem)|term|pipiterm;nn2;sign-sign;termsign/n;count;}pipi*…

基于Vue3的低代码开发平台——JNPF

目录 一、什么是Vue.js &#xff1f; 二、Jnpf-Web-Vue3 的技术栈介绍 &#xff08;1&#xff09;Vue3.x &#xff08;2&#xff09;Vue-router4.x &#xff08;3&#xff09;Vite4.x &#xff08;4&#xff09;Ant-Design-Vue3.x &#xff08;5&#xff09;TypeScript &#x…

【Java】实现阻塞队列-生产者/消费者模型

上文中我们讲了Java库中自带的阻塞队列&#xff0c;并且讲了如何用阻塞队列来实现生产者消费者模型 【Java】用Java库中自带的阻塞队列以及用阻塞队列实现生产者-消费者模型 下面我们来讲如何用代码实现一个阻塞队列 1、实现一个阻塞队列 阻塞队列 普通队列 线程安全 阻…

机器学习实战第1天:鸢尾花分类任务

专栏介绍 欢迎订阅专栏——机器学习实战 机器学习实战_Nowl的博客-CSDN博客 纸上得来终觉浅 本专栏项目将着重于解决各类实际机器学习问题&#xff0c;带你上手各种场景的实际问题 数据集可以在我的资源中找到&#xff0c;也可以自行搜索 文中导入数据集的路径要改成自己的…

C++学习笔记——C++ deque和vector的区别

C中的std::deque&#xff08;双端队列&#xff09;和std::vector&#xff08;向量&#xff09;是两种不同的容器类型&#xff0c;它们有以下区别&#xff1a; 内部实现方式不同&#xff1a;std::deque使用了一种双端队列的数据结构&#xff0c;它由多个块&#xff08;chunks&am…

【操作系统】文件系统的实现

文章目录 文件系统的层次结构文件系统的实现目录实现线性列表哈希表 文件的实现连续分配链接分配索引分配 文件存储空间管理空闲表法与空闲链表法成组链接法位示图法 文件系统的层次结构 文件系统从上往下分为了五层&#xff0c;分别是用户调用接口、文件目录系统、存取控制模…