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

select,poll,epoll

在 Linux Socket 服务器短编程时,为了处理大量客户的连接请求,需要使用非阻塞I/O和复用,select,poll 和 epoll 是 Linux API 提供的I/O复用方式。

\selectpollepoll
操作方式遍历遍历回调
底层实现数组链表哈希表
IO效率每次调用都进行线性遍历,时间复杂度为O(n)每次调用都进行线性遍历,时间复杂度为O(n)事件通知方式,每当fd就绪,系统注册的回调函数就会被调用,将就绪 fd 放到 rdllist 里面。时间复杂度O(1)
最大连接数1024(x86)或 2048(x64)无上限无上限
fd拷贝每次调用 select,都需要把 fd 集合从用户态拷贝到内核态每次调用 poll,都需要把 fd 集合从用户态拷贝到内核态调用 epoll_ctl 时拷贝进内核并保存,之后每次 epoll_wait 不拷贝

select

在一段指定的时间内,监听用户感兴趣的文件描述符上可读、可写和异常等事件。

select 实现多路复用的方式是,将已连接的 Socket 都放到一个文件描述符集合,然后调用 select 函数将文件描述符集合拷贝到内核里,让内核来检查是否有网络事件产生,检查的方式很粗暴,就是通过遍历文件描述符集合的方式,当检查到有事件产生后,将此 Socket 标记为可读或可写, 接着再把整个文件描述符集合拷贝回用户态里,然后用户态还需要再通过遍历的方法找到可读或可写的 Socket,然后再对其处理。

所以,对于 select 这种方式,需要进行 2 次「遍历」文件描述符集合,一次是在内核态里,一个次是在用户态里 ,而且还会发生 2 次「拷贝」文件描述符集合,先从用户空间传入内核空间,由内核修改后,再传出到用户空间中。

select 使用固定长度的 BitsMap,表示文件描述符集合,而且所支持的文件描述符的个数是有限制的,在 Linux 系统中,由内核中的 FD_SETSIZE 限制, 默认最大值为 1024,只能监听 0~1023 的文件描述符。

#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

select 函数监视的文件描述符分3类,分别是writefds、readfds、和exceptfds。调用后select函数会阻塞,直到有描述副就绪(有数据 可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。当select函数返回后,可以 通过遍历fdset,来找到就绪的描述符。

参数:

nfds : 最大的文件描述符加1。
readfds: 用于检查可读的。
writefds:用于检查可写性。
exceptfds:用于检查异常的数据。
timeout:一个指向 timeval 结构的指针,用于决定 select 等待 I/O 的最长时间。如果为空将一直等待。
timeval 结构的定义:

struct timeval{long tv_sec; // secondslong tv_usec; // microseconds
}

返回值>0 是已就绪的文件句柄的总数, =0 超时,<0表示出错,错误: errno

void FD_CLR(int fd, fd_set *set);	// 把文件描述符集合里fd清0
int  FD_ISSET(int fd, fd_set *set); // 测试文件描述符集合里fd是否置1
void FD_SET(int fd, fd_set *set);   // 把文件描述符集合里fd位置1
void FD_ZERO(fd_set *set);		   //把文件描述符集合里所有位清0

在这里插入图片描述
传统 select/pol l的另一个致命弱点就是当你拥有一个很大的 socket 集合,由于网络得延时,使得任一时间只有部分的 socket 是"活跃" 的,而 select/poll 每次调用都会线性扫描全部的集合,导致效率呈现线性下降

但是 epoll 不存在这个问题,它只会对"活跃"的 socket 进行操作—这是因为在内核实现中 epoll 是根据每个 fd 上面的 callback 函数实现的。于是,只有"活跃"的 socket 才会主动去调用 callback函数,其他idle 状态的 socket 则不会,在这点上,epoll实现了一个 "伪"AIO,因为这时候推动力在os内核。

示例

select_server.c

#include <sys/types.h> 
#include <sys/socket.h> 
#include <stdio.h> 
#include <netinet/in.h> 
#include <sys/time.h> 
#include <sys/ioctl.h> 
#include <unistd.h> 
#include <stdlib.h>int main()
{int server_sockfd, client_sockfd;int server_len, client_len;struct sockaddr_in server_address;struct sockaddr_in client_address;int result;fd_set readfds, testfds;server_sockfd = socket(AF_INET, SOCK_STREAM, 0);//建立服务器端socket server_address.sin_family = AF_INET;server_address.sin_addr.s_addr = htonl(INADDR_ANY);server_address.sin_port = htons(9000);server_len = sizeof(server_address);bind(server_sockfd, (struct sockaddr*)&server_address, server_len);listen(server_sockfd, 5); //监听队列最多容纳5个 FD_ZERO(&readfds);FD_SET(server_sockfd, &readfds);//将服务器端socket加入到集合中while (1){char ch;int fd;int nread;testfds = readfds;//将需要监视的描述符集copy到select查询队列中,select会对其修改,所以一定要分开使用变量 printf("server waiting\n");/*无限期阻塞,并测试文件描述符变动 */result = select(FD_SETSIZE, &testfds, (fd_set*)0, (fd_set*)0, (struct timeval*)0); //FD_SETSIZE:系统默认的最大文件描述符if (result < 1){perror("server5");exit(1);}/*扫描所有的文件描述符*/for (fd = 0; fd < FD_SETSIZE; fd++){/*找到相关文件描述符*/if (FD_ISSET(fd, &testfds)){/*判断是否为服务器套接字,是则表示为客户请求连接。*/if (fd == server_sockfd){client_len = sizeof(client_address);client_sockfd = accept(server_sockfd,(struct sockaddr*)&client_address, &client_len);FD_SET(client_sockfd, &readfds);//将客户端socket加入到集合中printf("adding client on fd %d\n", client_sockfd);}/*客户端socket中有数据请求时*/else{ioctl(fd, FIONREAD, &nread);//取得数据量交给nread/*客户数据请求完毕,关闭套接字,从集合中清除相应描述符 */if (nread == 0){close(fd);FD_CLR(fd, &readfds); //去掉关闭的fdprintf("removing client on fd %d\n", fd);}/*处理客户数据请求*/else{read(fd, &ch, 1);sleep(5);printf("serving client on fd %d\n", fd);ch++;write(fd, &ch, 1);}}}}}return 0;
}

select_client.c

#include <sys/types.h> 
#include <sys/socket.h> 
#include <stdio.h> 
#include <netinet/in.h> 
#include <arpa/inet.h> 
#include <unistd.h> 
#include <stdlib.h>
#include <sys/time.h>int main()
{int client_sockfd;int len;struct sockaddr_in address;//服务器端网络地址结构体 int result;char ch = 'A';client_sockfd = socket(AF_INET, SOCK_STREAM, 0);//建立客户端socket address.sin_family = AF_INET;address.sin_addr.s_addr = inet_addr("127.0.0.1");address.sin_port = htons(9000);len = sizeof(address);result = connect(client_sockfd, (struct sockaddr*)&address, len);if (result == -1){perror("oops: client2");exit(1);}//第一次读写write(client_sockfd, &ch, 1);read(client_sockfd, &ch, 1);printf("the first time: char from server = %c\n", ch);sleep(5);//第二次读写write(client_sockfd, &ch, 1);read(client_sockfd, &ch, 1);printf("the second time: char from server = %c\n", ch);close(client_sockfd);return 0;
}

poll

poll 不再用 BitsMap 来存储所关注的文件描述符,取而代之用动态数组,以链表形式来组织,突破了 select 的文件描述符个数限制,当然还会受到系统文件描述符限制。

但是 poll 和 select 并没有太大的本质区别,都是使用 「线性结构」 存储进程关注的 Socket 集合,因此都需要遍历文件描述符集合来找到可读或可写的 Socket,时间复杂度为 O(n),而且也需要在用户态与内核态之间拷贝文件描述符集合,这种方式随着并发数上来,性能的损耗会呈指数级增长。

poll 和select 区别: select 有文件句柄上线设置,值为 FD_SETSIZE,而poll 理论上没有限制!

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

输入参数:
fds:可以传递多个结构体,也就是说可以监测多个驱动设备所产生的事件,只要有一个产生了请求事件,就能立即返回。

struct pollfd {int fd;           /*文件描述符   open打开的那个*/short events;     /*请求的事件类型,监视驱动文件的事件掩码*/  POLLIN | POLLOUTshort revents;    /*驱动文件实际返回的事件*/
}

nfds:监测驱动文件的个数。
timeout:超时时间,单位是ms。

事件类型 events 可以为下列值:
POLLIN 有数据可读
POLLRDNORM 有普通数据可读,等效与POLLIN
POLLPRI 有紧迫数据可读
POLLOUT 写数据不会导致阻塞
POLLER 指定的文件描述符发生错误
POLLHUP 指定的文件描述符挂起事件
POLLNVAL 无效的请求,打不开指定的文件描述符

返回值:
有事件发生 返回revents域不为0的文件描述符个数
超时:return 0
失败:return -1 错误:errno

示例

poll_server.c

#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdlib.h>
#include <poll.h>#define MAX_FD  8192
struct pollfd  fds[MAX_FD];
int cur_max_fd = 0;int main()
{int server_sockfd, client_sockfd;int server_len, client_len;struct sockaddr_in server_address;struct sockaddr_in client_address;int result;//fd_set readfds, testfds;server_sockfd = socket(AF_INET, SOCK_STREAM, 0);//建立服务器端socketserver_address.sin_family = AF_INET;server_address.sin_addr.s_addr = htonl(INADDR_ANY);server_address.sin_port = htons(9000);server_len = sizeof(server_address);bind(server_sockfd, (struct sockaddr*)&server_address, server_len);listen(server_sockfd, 5); //监听队列最多容纳5个//FD_ZERO(&readfds);//FD_SET(server_sockfd, &readfds);//将服务器端socket加入到集合中fds[server_sockfd].fd = server_sockfd;fds[server_sockfd].events = POLLIN;fds[server_sockfd].revents = 0;if(cur_max_fd <= server_sockfd){cur_max_fd = server_sockfd + 1;}while (1){char ch;int i, fd;int nread;//testfds = readfds;//将需要监视的描述符集copy到select查询队列中,select会对其修改,所以一定要分开使用变量printf("server waiting\n");/*无限期阻塞,并测试文件描述符变动 */result = poll(fds, cur_max_fd, 1000);//result = select(FD_SETSIZE, &testfds, (fd_set*)0, (fd_set*)0, (struct timeval*)0); //FD_SETSIZE:系统默认的最大文件描述符if (result < 0){perror("server5");exit(1);}/*扫描所有的文件描述符*/for (i = 0; i < cur_max_fd; i++){/*找到相关文件描述符*/if (fds[i].revents){fd = fds[i].fd;/*判断是否为服务器套接字,是则表示为客户请求连接。*/if (fd == server_sockfd){client_len = sizeof(client_address);client_sockfd = accept(server_sockfd,(struct sockaddr*)&client_address, &client_len);fds[client_sockfd].fd = client_sockfd;//将客户端socket加入到集合中fds[client_sockfd].events = POLLIN;fds[client_sockfd].revents = 0;if(cur_max_fd <= client_sockfd){cur_max_fd = client_sockfd + 1;}printf("adding client on fd %d\n", client_sockfd);//fds[server_sockfd].events = POLLIN;}/*客户端socket中有数据请求时*/else{//ioctl(fd, FIONREAD, &nread);//取得数据量交给nreadnread = read(fd, &ch, 1);/*客户数据请求完毕,关闭套接字,从集合中清除相应描述符 */if (nread == 0){close(fd);memset(&fds[i], 0, sizeof(struct pollfd)); //去掉关闭的fdprintf("removing client on fd %d\n", fd);}/*处理客户数据请求*/else{//read(fds[fd].fd, &ch, 1);sleep(5);printf("serving client on fd %d, read: %c\n", fd, ch);ch++;write(fd, &ch, 1);//fds[fd].events = POLLIN;}}}}}return 0;
}

epoll

epoll 可以理解为 event poll (基于事件的轮询)

使用场合:

  • 当客户处理多个描述符时(一般是交互式输入和网络套接口),必须使用I/O复用。
  • 当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。
  • 如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。
  • 如果一个服务器既要处理TCP,又要处理UDP,一般要使用I/O复用。
  • 如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。

Reactor 设计模式

在这里插入图片描述
reactor 是一种事件驱动的反应堆模式,高效的事件处理模型

reactor 反应堆:事件来了,执行,事件类型可能不尽相同,所以我们需要提前注册好不同的事件处理函数;事件到来就由 epoll_wait 获取同时到来的多个事件,并且根据数据的不同类型将事件分发给事件处理机制 (事件处理器), 也就是我们提前注册的那些接口函数。

reactor 模型的设计思想和思维方式: 它需要的是事件驱动,相应的事件发生,我们需要根据事件自动的调用相应的函数,所以我们需要提前注册好处理函数的接口到 reactor 中, 函数是由 reactor 去调用的,而不是再主函数中直接进行调用的,所以需要使用回调函数

Reactor 模式是处理并发 I/O 比较常见的一种模式,用于同步 I/O
中心思想将所有要处理的 I/O 事件注册到一个 I/O 多路复用器上,同时主线程/进程阻塞在多路复用器上; 一旦有 I/O 事件到来或是准备就绪(文件描述符或 socket 可读、写),多路复用器返回并将事先注册的相应 I/O 事件分发到对应的处理器中。

流程
1.注册事件和对应的事件处理器;
2.多路复用器等待事件到来;
3.事件到来,激发事件分发器分发事件到对应的处理器;
4.事件处理器处理事件,然后注册新的事件, (比如处理读事件,处理完成之后需要将其设置为写事件再注册,因为读取之后我们需要针对业务需求进行数据处理,之后将其send 回去响应客户端结果,所以自然需要改成写事件,也就需要重新注册)

多路复用器 :由操作系统提供,在 linux 上一般是 select, poll, epoll 等系统调用。
事件分发器 :将多路复用器中返回的就绪事件分到对应的处理函数中,分发给事件处理器。
事件处理器 :处理对应的IO事件。

Reactor优点:
1)响应快,不必为单个同步事件所阻塞,虽然 Reactor 本身依然是同步的;
2)编程相对简单,可以最大程度的避免复杂的多线程及同步问题,并且避免了多线程/进程的切换开销;
3)可扩展性,可以方便的通过增加 Reactor 实例个数来充分利用 CPU 资源;
4)可复用性,reactor 框架本身与具体事件处理逻辑无关,具有很高的复用性;

I/O 多路复用

1.创建 EPOLL 句柄

int epoll_create(int size); 

2.向 EPOLL 对象中添加、修改或者删除感兴趣的事件

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

参数:
op 取值:
EPOLL_CTL_ADD 添加新的事件到epoll中
EPOLL_CTL_MOD 修改EPOLL中的事件
EPOLL_CTL_DEL 删除epoll中的事件
events 取值:
EPOLLIN 表示有数据可以读出(接受连接、关闭连接)
EPOLLOUT 表示连接可以写入数据发送(向服务器发起连接,连接成功事件)
EPOLLERR 表示对应的连接发生错误
EPOLLHUP 表示对应的连接被挂起

3.收集在epoll监控的事件中已经发生的事件

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout); 

参数:
epfd: epoll 的描述符。
events:分配好的 epoll_event 结构体数组,epoll 将会把发生的事件复制到 events 数组中(events 不可以是空指针,内核只负责把数据复制到这个 events 数组中,不会去帮助我们在用户态中分配内存。内核这种做法效率很高)。

struct epoll_event{__uint32_t  events;epoll_data_t data;
}
typedef union epoll_data{void *ptr;int fd;uint32_t u32;uint64_t u64;
}epoll_data_t

maxevents: 本次可以返回的最大事件数目,通常 maxevents 参数与预分配的 events 数组的大小是相等的。
timeout: 表示在没有检测到事件发生时最多等待的时间(单位为毫秒),如果 timeout0,立刻返回,不会等待。-1表示无限期阻塞。

epoll 支持两种事件触发模式:边缘触发(edge-triggered,ET)和水平触发(level-triggered,LT)。
边缘触发:当被监控的文件描述符上有可读写事件发生时,epoll_wait() 会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你!!!
这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符!!!
设置方式: stat->_ev.events = EPOLLIN | EPOLLET
水平触发:当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait()时,它还会通知你在上没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你!!!
如果系统中有大量你不需要读写的就绪文件描述符,而它们每次都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率。

两者的区别:水平触发的意思是只要满足事件的条件,比如内核中有数据需要读,就一直不断地把这个事件传递给用户;而边缘触发的意思是只有第一次满足条件的时候才触发,之后就不会再传递同样的事件了。

epoll为什么高效?

1、红黑树提高 epoll 事件增删查改效率;
2、回调通知机制;
当epoll监听套接字有数据读或者写时,会通过注册到socket的回调函数通知epoll,epoll检测到事件后,将事件存储在就绪队列(rdllist)。

就绪队列
epoll_wait 返回成功后,会将所有就绪事件存储在事件数组,用户不需要进行无效的轮询,从而提高了效率。

示例1:使用 epoll 实现简单的 web 服务器

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/epoll.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<assert.h>
#include<fcntl.h>
#include<unistd.h>//    int fd;
typedef struct _ConnectStat  ConnectStat;typedef void(*response_handler) (ConnectStat * stat);struct _ConnectStat {int fd;char name[64];char  age[64];struct epoll_event _ev;int  status;//0 -未登录   1 - 已登陆response_handler handler;//不同页面的处理函数
};//http协议相关代码
ConnectStat * stat_init(int fd);
void connect_handle(int new_fd);
void do_http_respone(ConnectStat * stat);
void do_http_request(ConnectStat * stat);
void welcome_response_handler(ConnectStat * stat);
void commit_respone_handler(ConnectStat * stat);const char *main_header = "HTTP/1.0 200 OK\r\nServer: Martin Server\r\nContent-Type: text/html\r\nConnection: Close\r\n";static int epfd = 0;void usage(const char* argv)
{printf("%s:[ip][port]\n", argv);
}void set_nonblock(int fd)
{int fl = fcntl(fd, F_GETFL);fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}int startup(char* _ip, int _port)  //创建一个套接字,绑定,检测服务器
{//sock//1.创建套接字int sock = socket(AF_INET, SOCK_STREAM, 0);if (sock < 0){perror("sock");exit(2);}int opt = 1;setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));//2.填充本地 sockaddr_in 结构体(设置本地的IP地址和端口)struct sockaddr_in local;local.sin_port = htons(_port);local.sin_family = AF_INET;local.sin_addr.s_addr = inet_addr(_ip);//3.bind()绑定if (bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0){perror("bind");exit(3);}//4.listen()监听 检测服务器if (listen(sock, 5) < 0){perror("listen");exit(4);}//sleep(1000);return sock;    //这样的套接字返回
}int main(int argc, char *argv[])
{if (argc != 3)     //检测参数个数是否正确{usage(argv[0]);exit(1);}int listen_sock = startup(argv[1], atoi(argv[2]));      //创建一个绑定了本地 ip 和端口号的套接字描述符//1.创建epoll    epfd = epoll_create(256);    //可处理的最大句柄数256个if (epfd < 0){perror("epoll_create");exit(5);}struct epoll_event _ev;       //epoll结构填充 ConnectStat * stat = stat_init(listen_sock);_ev.events = EPOLLIN;         //初始关心事件为读_ev.data.ptr = stat;//_ev.data.fd = listen_sock;    //  //2.托管epoll_ctl(epfd, EPOLL_CTL_ADD, listen_sock, &_ev);  //将listen sock添加到epfd中,关心读事件struct epoll_event revs[64];int timeout = -1;int num = 0;int done = 0;while (!done){//epoll_wait()相当于在检测事件switch ((num = epoll_wait(epfd, revs, 64, timeout)))  //返回需要处理的事件数目  64表示 事件有多大{case 0:                  //返回0 ,表示监听超时printf("timeout\n");break;case -1:                 //出错perror("epoll_wait");break;default:                 //大于零 即就是返回了需要处理事件的数目{struct sockaddr_in peer;socklen_t len = sizeof(peer);int i;for (i = 0; i < num; i++){ConnectStat * stat = (ConnectStat *)revs[i].data.ptr;int rsock = stat->fd; //准确获取哪个事件的描述符if (rsock == listen_sock && (revs[i].events) && EPOLLIN) //如果是初始的 就接受,建立链接{int new_fd = accept(listen_sock, (struct sockaddr*)&peer, &len);if (new_fd > 0){printf("get a new client:%s:%d\n", inet_ntoa(peer.sin_addr), ntohs(peer.sin_port));//sleep(1000);connect_handle(new_fd);}}else // 接下来对num - 1 个事件处理{if (revs[i].events & EPOLLIN){do_http_request((ConnectStat *)revs[i].data.ptr);}else if (revs[i].events & EPOLLOUT){do_http_respone((ConnectStat *)revs[i].data.ptr);}else{}}}}break;}//end switch}//end whilereturn 0;
}ConnectStat * stat_init(int fd) {ConnectStat * temp = NULL;temp = (ConnectStat *)malloc(sizeof(ConnectStat));if (!temp) {fprintf(stderr, "malloc failed. reason: %m\n");return NULL;}memset(temp, '\0', sizeof(ConnectStat));temp->fd = fd;temp->status = 0;//temp->handler = welcome_response_handler;}//初始化连接,然后等待浏览器发送请求
void connect_handle(int new_fd) {ConnectStat *stat = stat_init(new_fd);set_nonblock(new_fd);stat->_ev.events = EPOLLIN;stat->_ev.data.ptr = stat;epoll_ctl(epfd, EPOLL_CTL_ADD, new_fd, &stat->_ev);    //二次托管}void do_http_respone(ConnectStat * stat) {stat->handler(stat);
}void do_http_request(ConnectStat * stat) {//读取和解析http 请求char buf[4096];char * pos = NULL;//while  header \r\n\r\ndatassize_t _s = read(stat->fd, buf, sizeof(buf) - 1);if (_s > 0){buf[_s] = '\0';printf("receive from client:%s\n", buf);pos = buf;//Demo 仅仅演示效果,不做详细的协议解析if (!strncasecmp(pos, "GET", 3)) {stat->handler = welcome_response_handler;}else if (!strncasecmp(pos, "Post", 4)) {//获取 uriprintf("---Post----\n");pos += strlen("Post");while (*pos == ' ' || *pos == '/') ++pos;if (!strncasecmp(pos, "commit", 6)) {//获取名字和年龄int len = 0;printf("post commit --------\n");pos = strstr(buf, "\r\n\r\n");char *end = NULL;if (end = strstr(pos, "name=")) {pos = end + strlen("name=");end = pos;while (('a' <= *end && *end <= 'z') || ('A' <= *end && *end <= 'Z') || ('0' <= *end && *end <= '9'))	end++;len = end - pos;if (len > 0) {memcpy(stat->name, pos, end - pos);stat->name[len] = '\0';}}if (end = strstr(pos, "age=")) {pos = end + strlen("age=");end = pos;while ('0' <= *end && *end <= '9')	end++;len = end - pos;if (len > 0) {memcpy(stat->age, pos, end - pos);stat->age[len] = '\0';}}stat->handler = commit_respone_handler;}else {stat->handler = welcome_response_handler;}}else {stat->handler = welcome_response_handler;}//生成处理结果 html ,writestat->_ev.events = EPOLLOUT;//stat->_ev.data.ptr = stat;epoll_ctl(epfd, EPOLL_CTL_MOD, stat->fd, &stat->_ev);    //二次托管}else if (_s == 0)  //client:close{printf("client: %d close\n", stat->fd);epoll_ctl(epfd, EPOLL_CTL_DEL, stat->fd, NULL);close(stat->fd);free(stat);}else{perror("read");}}void welcome_response_handler(ConnectStat * stat) {const char * welcome_content = "\<html lang=\"zh-CN\">\n\<head>\n\<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\n\<title>This is a test</title>\n\</head>\n\<body>\n\<div align=center height=\"500px\" >\n\<br/><br/><br/>\n\<h2>大家好,欢迎来到奇牛学院VIP 课!</h2><br/><br/>\n\<form action=\"commit\" method=\"post\">\n\尊姓大名: <input type=\"text\" name=\"name\" />\n\<br/>芳龄几何: <input type=\"password\" name=\"age\" />\n\<br/><br/><br/><input type=\"submit\" value=\"提交\" />\n\<input type=\"reset\" value=\"重置\" />\n\</form>\n\</div>\n\</body>\n\</html>";char sendbuffer[4096];char content_len[64];strcpy(sendbuffer, main_header);snprintf(content_len, 64, "Content-Length: %d\r\n\r\n", (int)strlen(welcome_content));strcat(sendbuffer, content_len);strcat(sendbuffer, welcome_content);printf("send reply to client \n%s", sendbuffer);write(stat->fd, sendbuffer, strlen(sendbuffer));stat->_ev.events = EPOLLIN;//stat->_ev.data.ptr = stat;epoll_ctl(epfd, EPOLL_CTL_MOD, stat->fd, &stat->_ev);}void commit_respone_handler(ConnectStat * stat) {const char * commit_content = "\<html lang=\"zh-CN\">\n\<head>\n\<meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\n\<title>This is a test</title>\n\</head>\n\<body>\n\<div align=center height=\"500px\" >\n\<br/><br/><br/>\n\<h2>欢迎学霸同学&nbsp;%s &nbsp;,你的芳龄是&nbsp;%s!</h2><br/><br/>\n\</div>\n\</body>\n\</html>\n";char sendbuffer[4096];char content[4096];char content_len[64];int len = 0;len = snprintf(content, 4096, commit_content, stat->name, stat->age);strcpy(sendbuffer, main_header);snprintf(content_len, 64, "Content-Length: %d\r\n\r\n", len);strcat(sendbuffer, content_len);strcat(sendbuffer, content);printf("send reply to client \n%s", sendbuffer);write(stat->fd, sendbuffer, strlen(sendbuffer));stat->_ev.events = EPOLLIN;//stat->_ev.data.ptr = stat;epoll_ctl(epfd, EPOLL_CTL_MOD, stat->fd, &stat->_ev);
}

示例2:使用 epoll 封装的库的使用

globals.h

#ifndef GLOBALS_H
#define GLOBALS_H#include <sys/time.h>
#include <sys/resource.h>	
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <assert.h>#define FD_DESC_SZ		64#define COMM_OK		  (0)
#define COMM_ERROR	 (-1)
#define COMM_NOMESSAGE	 (-3)
#define COMM_TIMEOUT	 (-4)
#define COMM_SHUTDOWN	 (-5)
#define COMM_INPROGRESS  (-6)
#define COMM_ERR_CONNECT (-7)
#define COMM_ERR_DNS     (-8)
#define COMM_ERR_CLOSING (-9)//调试相关
#define DEBUG_LEVEL  0
#define DEBUG_ONLY   8
#define debug(m, n)    if( m >= DEBUG_LEVEL && n <= DEBUG_ONLY  ) printf#define safe_free(x)	if (x) { free(x); x = NULL; }typedef void PF(int, void *);struct _fde {unsigned int type;__u_short local_port;__u_short remote_port;struct in_addr local_addr;char ipaddr[16];		/* dotted decimal address of peer */PF *read_handler;void *read_data;PF *write_handler;void *write_data;PF *timeout_handler;time_t timeout;void *timeout_data;
};typedef struct _fde fde;extern fde *fd_table;		
extern int Biggest_FD;		/*系统时间相关,设置成全局变量,供所有模块使用*/
extern struct timeval current_time;
extern double current_dtime;
extern time_t sys_curtime;/* epoll 相关接口实现 */
extern void do_epoll_init(int max_fd);
extern void do_epoll_shutdown();
extern void epollSetEvents(int fd, int need_read, int need_write);
extern int do_epoll_select(int msec);/*框架外围接口*/
void comm_init(int max_fd);
extern int comm_select(int msec);
extern inline void comm_call_handlers(int fd, int read_event, int write_event);
void  commUpdateReadHandler(int fd, PF * handler, void *data);
void commUpdateWriteHandler(int fd, PF * handler, void *data);extern const char *xstrerror(void);
int ignoreErrno(int ierrno);#endif /* GLOBALS_H */

comm_epoll.c

#include "globals.h"
#include <sys/epoll.h>#define MAX_EVENTS	256	/* 一次处理的最大的事件 *//* epoll structs */
static int kdpfd;
static struct epoll_event events[MAX_EVENTS];
static int epoll_fds = 0;
static unsigned *epoll_state;	/* 保存每个epoll 的事件状态 */static const char *
epolltype_atoi(int x)
{switch (x) {case EPOLL_CTL_ADD:return "EPOLL_CTL_ADD";case EPOLL_CTL_DEL:return "EPOLL_CTL_DEL";case EPOLL_CTL_MOD:return "EPOLL_CTL_MOD";default:return "UNKNOWN_EPOLLCTL_OP";}
}void do_epoll_init(int max_fd)
{kdpfd = epoll_create(max_fd);if (kdpfd < 0)fprintf(stderr,"do_epoll_init: epoll_create(): %s\n", xstrerror());//fd_open(kdpfd, FD_UNKNOWN, "epoll ctl");//commSetCloseOnExec(kdpfd);epoll_state = calloc(max_fd, sizeof(*epoll_state));
}void do_epoll_shutdown()
{close(kdpfd);kdpfd = -1;safe_free(epoll_state);
}void epollSetEvents(int fd, int need_read, int need_write)
{int epoll_ctl_type = 0;struct epoll_event ev;assert(fd >= 0);debug(5, 8) ("commSetEvents(fd=%d)\n", fd);memset(&ev, 0, sizeof(ev));ev.events = 0;ev.data.fd = fd;if (need_read)ev.events |= EPOLLIN;if (need_write)ev.events |= EPOLLOUT;if (ev.events)ev.events |= EPOLLHUP | EPOLLERR;if (ev.events != epoll_state[fd]) {/* If the struct is already in epoll MOD or DEL, else ADD */if (!ev.events) {epoll_ctl_type = EPOLL_CTL_DEL;} else if (epoll_state[fd]) {epoll_ctl_type = EPOLL_CTL_MOD;} else {epoll_ctl_type = EPOLL_CTL_ADD;}/* Update the state */epoll_state[fd] = ev.events;if (epoll_ctl(kdpfd, epoll_ctl_type, fd, &ev) < 0) {debug(5, 1) ("commSetEvents: epoll_ctl(%s): failed on fd=%d: %s\n",epolltype_atoi(epoll_ctl_type), fd, xstrerror());}switch (epoll_ctl_type) {case EPOLL_CTL_ADD:epoll_fds++;break;case EPOLL_CTL_DEL:epoll_fds--;break;default:break;}}
}int do_epoll_select(int msec)
{int i;int num;int fd;struct epoll_event *cevents;/*if (epoll_fds == 0) {assert(shutting_down);return COMM_SHUTDOWN;}statCounter.syscalls.polls++;*/num = epoll_wait(kdpfd, events, MAX_EVENTS, msec);if (num < 0) {getCurrentTime();if (ignoreErrno(errno))return COMM_OK;debug(5, 1) ("comm_select: epoll failure: %s\n", xstrerror());return COMM_ERROR;}//statHistCount(&statCounter.select_fds_hist, num);if (num == 0)return COMM_TIMEOUT;for (i = 0, cevents = events; i < num; i++, cevents++) {fd = cevents->data.fd;comm_call_handlers(fd, cevents->events & ~EPOLLOUT, cevents->events & ~EPOLLIN);}return COMM_OK;
}

comm.c

#include "globals.h"double current_dtime;
time_t sys_curtime;
struct timeval current_time;int Biggest_FD = 1024;  /*默认的最大文件描述符数量 1024*/
static int MAX_POLL_TIME = 1000;	/* see also comm_quick_poll_required() */
fde *fd_table = NULL;	time_t getCurrentTime(void)
{gettimeofday(&current_time, NULL);current_dtime = (double) current_time.tv_sec +(double) current_time.tv_usec / 1000000.0;return sys_curtime = current_time.tv_sec;
}void
comm_close(int fd)
{assert(fd>0);fde *F = &fd_table[fd];if(F) memset((void *)F,'\0',sizeof(fde));epollSetEvents(fd, 0, 0);close(fd);
}void
comm_init(int max_fd)
{if(max_fd > 0 ) Biggest_FD = max_fd;fd_table = calloc(Biggest_FD, sizeof(fde));do_epoll_init(Biggest_FD);
}void
comm_select_shutdown(void)
{do_epoll_shutdown();if(fd_table) free(fd_table);
}//static int comm_select_handled;inline void
comm_call_handlers(int fd, int read_event, int write_event)
{fde *F = &fd_table[fd];debug(5, 8) ("comm_call_handlers(): got fd=%d read_event=%x write_event=%x F->read_handler=%p F->write_handler=%p\n",fd, read_event, write_event, F->read_handler, F->write_handler);if (F->read_handler && read_event) {PF *hdl = F->read_handler;void *hdl_data = F->read_data;/* If the descriptor is meant to be deferred, don't handle */debug(5, 8) ("comm_call_handlers(): Calling read handler on fd=%d\n", fd);//commUpdateReadHandler(fd, NULL, NULL);hdl(fd, hdl_data);}if (F->write_handler && write_event) {PF *hdl = F->write_handler;void *hdl_data = F->write_data;//commUpdateWriteHandler(fd, NULL, NULL);hdl(fd, hdl_data);}
}int
commSetTimeout(int fd, int timeout, PF * handler, void *data)
{fde *F;debug(5, 3) ("commSetTimeout: FD %d timeout %d\n", fd, timeout);assert(fd >= 0);assert(fd < Biggest_FD);F = &fd_table[fd];if (timeout < 0) {F->timeout_handler = NULL;F->timeout_data = NULL;return F->timeout = 0;}assert(handler || F->timeout_handler);if (handler || data) {F->timeout_handler = handler;F->timeout_data = data;}return F->timeout = sys_curtime + (time_t) timeout;
}void
commUpdateReadHandler(int fd, PF * handler, void *data)
{fd_table[fd].read_handler = handler;fd_table[fd].read_data = data;epollSetEvents(fd,1,0); 
}void
commUpdateWriteHandler(int fd, PF * handler, void *data)
{fd_table[fd].write_handler = handler;fd_table[fd].write_data = data;epollSetEvents(fd,0,1); 
}static void
checkTimeouts(void)
{int fd;fde *F = NULL;PF *callback;for (fd = 0; fd <= Biggest_FD; fd++) {F = &fd_table[fd];/*if (!F->flags.open)continue;*/if (F->timeout == 0)continue;if (F->timeout > sys_curtime)continue;debug(5, 5) ("checkTimeouts: FD %d Expired\n", fd);if (F->timeout_handler) {debug(5, 5) ("checkTimeouts: FD %d: Call timeout handler\n", fd);callback = F->timeout_handler;F->timeout_handler = NULL;callback(fd, F->timeout_data);} else {debug(5, 5) ("checkTimeouts: FD %d: Forcing comm_close()\n", fd);comm_close(fd);}}
}int
comm_select(int msec)
{static double last_timeout = 0.0;int rc;double start = current_dtime;debug(5, 3) ("comm_select: timeout %d\n", msec);if (msec > MAX_POLL_TIME)msec = MAX_POLL_TIME;//statCounter.select_loops++;/* Check timeouts once per second */if (last_timeout + 0.999 < current_dtime) {last_timeout = current_dtime;checkTimeouts();} else {int max_timeout = (last_timeout + 1.0 - current_dtime) * 1000;if (max_timeout < msec)msec = max_timeout;}//comm_select_handled = 0;rc = do_epoll_select(msec);getCurrentTime();//statCounter.select_time += (current_dtime - start);if (rc == COMM_TIMEOUT)debug(5, 8) ("comm_select: time out\n");return rc;
}const char *
xstrerror(void)
{static char xstrerror_buf[BUFSIZ];const char *errmsg;errmsg = strerror(errno);if (!errmsg || !*errmsg)errmsg = "Unknown error";snprintf(xstrerror_buf, BUFSIZ, "(%d) %s", errno, errmsg);return xstrerror_buf;
}int
ignoreErrno(int ierrno)
{switch (ierrno) {case EINPROGRESS:case EWOULDBLOCK:
#if EAGAIN != EWOULDBLOCKcase EAGAIN:
#endifcase EALREADY:case EINTR:
#ifdef ERESTARTcase ERESTART:
#endifreturn 1;default:return 0;}/* NOTREACHED */
}

main.c

#include "globals.h"
typedef struct _ConnectStat  ConnectStat;#define BUFLEN  1024struct _ConnectStat {int fd;char send_buf[BUFLEN];PF *handler;//不同页面的处理函数
};//echo 服务实现相关代码
ConnectStat * stat_init(int fd);
void accept_connection(int fd, void *data);
void do_echo_handler(int fd, void  *data);
void do_echo_response(int fd,void *data);
void do_echo_timeout(int fd, void *data);void usage(const char* argv)
{printf("%s:[ip][port]\n", argv);
}void set_nonblock(int fd)
{int fl = fcntl(fd, F_GETFL);fcntl(fd, F_SETFL, fl | O_NONBLOCK);
}int startup(char* _ip, int _port)  //创建一个套接字,绑定,检测服务器
{//sock//1.创建套接字int sock = socket(AF_INET, SOCK_STREAM, 0);if (sock < 0){perror("sock");exit(2);}int opt = 1;setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));//2.填充本地 sockaddr_in 结构体(设置本地的IP地址和端口)struct sockaddr_in local;local.sin_port = htons(_port);local.sin_family = AF_INET;local.sin_addr.s_addr = inet_addr(_ip);//3.bind()绑定if (bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0){perror("bind");exit(3);}//4.listen()监听 检测服务器if (listen(sock, 5) < 0){perror("listen");exit(4);}return sock;    //这样的套接字返回
}ConnectStat * stat_init(int fd) {ConnectStat * temp = NULL;temp = (ConnectStat *)malloc(sizeof(ConnectStat));if (!temp) {fprintf(stderr, "malloc failed. reason: %m\n");return NULL;}memset(temp, '\0', sizeof(ConnectStat));temp->fd = fd;//temp->status = 0;
}void do_welcome_handler(int fd, void  *data) {const char * WELCOME= "Welcome.\n";int wlen = strlen(WELCOME);int n ;ConnectStat * stat = (ConnectStat *)(data);if( (n = write(fd, "Welcome.\n",wlen)) != wlen ){if(n<=0){fprintf(stderr, "write failed[len:%d], reason: %s\n",n,strerror(errno));}else fprintf(stderr, "send %d bytes only ,need to send %d bytes.\n",n,wlen);}else {commUpdateReadHandler(fd, do_echo_handler,(void *)stat);commSetTimeout(fd, 10, do_echo_timeout, (void *)stat);}
}void do_echo_handler(int fd, void  *data) {ConnectStat * stat = (ConnectStat *)(data);char * p = NULL;assert(stat!=NULL);p = stat->send_buf;*p++ = '-';*p++ = '>';ssize_t _s = read(fd, p, BUFLEN-(p-stat->send_buf)-1); //2字节"->" +字符结束符.if (_s > 0){*(p+_s) = '\0';printf("receive from client: %s\n", p);//_s--;//while( _s>=0 && ( stat->send_buf[_s]=='\r' || stat->send_buf[_s]=='\n' ) ) stat->send_buf[_s]='\0';if(!strncasecmp(p, "quit", 4)){//退出.comm_close(fd);free(stat);return ;}//write(fd,commUpdateWriteHandler(fd, do_echo_response, (void *)stat);commSetTimeout(fd, 10, do_echo_timeout, (void *)stat);}else if (_s == 0)  //client:close{fprintf(stderr,"Remote connection[fd: %d] has been closed\n", fd);comm_close(fd);free(stat);}else //err occurred.{fprintf(stderr,"read faield[fd: %d], reason:%s [%d]\n",fd , strerror(errno), _s);}
}void do_echo_response(int fd, void  *data) {ConnectStat * stat = (ConnectStat *)(data);int len = strlen(stat->send_buf);int _s = write(fd, stat->send_buf, len);if(_s>0){commSetTimeout(fd, 10, do_echo_timeout, (void *)stat);commUpdateReadHandler(fd, do_echo_handler, (void *)stat);}else if(_s==0){fprintf(stderr,"Remote connection[fd: %d] has been closed\n", fd);comm_close(fd);free(stat);}else {fprintf(stderr,"read faield[fd: %d], reason:%s [%d]\n",fd ,_s ,strerror(errno));}
}//read()
//注册写事件//写事件就绪
//write()void accept_connection(int fd, void *data){struct sockaddr_in peer;socklen_t len = sizeof(peer);ConnectStat * stat = (ConnectStat *)data;int new_fd = accept(fd, (struct sockaddr*)&peer, &len);if (new_fd > 0){ConnectStat *stat = stat_init(new_fd);set_nonblock(new_fd);printf("new client: %s:%d\n", inet_ntoa(peer.sin_addr), ntohs(peer.sin_port));commUpdateWriteHandler(new_fd, do_welcome_handler, (void *)stat);commSetTimeout(new_fd, 30,do_echo_timeout, (void *)stat);}
}void do_echo_timeout(int fd, void *data){fprintf(stdout,"---------timeout[fd:%d]----------\n",fd);comm_close(fd);free(data);
}int main(int argc,char **argv){if (argc != 3)     //检测参数个数是否正确{usage(argv[0]);exit(1);}int listen_sock = startup(argv[1], atoi(argv[2]));      //创建一个绑定了本地 ip 和端口号的套接字描述符//初始化异步事件处理框架epollcomm_init(102400);ConnectStat * stat = stat_init(listen_sock);commUpdateReadHandler(listen_sock,accept_connection,(void *)stat);do{//不断循环处理事件comm_select(1000);}while(1==1);comm_select_shutdown();
}

参考:
Select、Poll、Epoll详解
小林coding-I/O 多路复用
epoll高度封装reactor,几乎所有可见服务器的底层框架

相关文章:

select,poll,epoll

在 Linux Socket 服务器短编程时&#xff0c;为了处理大量客户的连接请求&#xff0c;需要使用非阻塞I/O和复用&#xff0c;select&#xff0c;poll 和 epoll 是 Linux API 提供的I/O复用方式。 \selectpollepoll操作方式遍历遍历回调底层实现数组链表哈希表IO效率每次调用都进…...

【48天笔试强训】day18

题目1 描述 有一种兔子&#xff0c;从出生后第3个月起每个月都生一只兔子&#xff0c;小兔子长到第三个月后每个月又生一只兔子。 例子&#xff1a;假设一只兔子第3个月出生&#xff0c;那么它第5个月开始会每个月生一只兔子。 一月的时候有一只兔子&#xff0c;假如兔子都…...

链表经典面试题01

目录 引言 面试题01:返回倒数第k个节点 题目描述: 思路分析: 代码展示: 面试题02:链表的回文结构 题目描述: 描述 思路分析: 代码展示: 面试题03:相交链表 题目描述: 思路分析: 代码展示: 小结: 引言 这次的题均来自力扣和牛客有关链表的经典面试题,代码只会展示…...

基于java的CRM客户关系管理系统的设计与实现(论文 + 源码 )

【免费】基于Java的CRM客户关系管理系统的设计和实现.zip资源-CSDN文库https://download.csdn.net/download/JW_559/89273409 基于Java的CRM客户关系管理系统的设计与实现 摘 要 随着互联网的高速发展&#xff0c;市场经济的信息化&#xff0c;让企业之间的竞争变得&#xff0…...

【动态规划-最长上升子序列模型part2】:拦截导弹、导弹防御系统、最长公共上升子序列【已更新完成】

1、拦截导弹 某国为了防御敌国的导弹袭击&#xff0c;发展出一种导弹拦截系统。 但是这种导弹拦截系统有一个缺陷&#xff1a;虽然它的第一发炮弹能够到达任意的高度&#xff0c;但是以后每一发炮弹都不能高于前一发的高度。 某天&#xff0c;雷达捕捉到敌国的导弹来袭。 由于…...

Spring 如何解决 Bean 循环依赖

循环依赖解释 bean A 属性注入时依赖bean B &#xff0c;并且bean B属性注入时也依赖bean A &#xff0c;造成 bean A 和bean B 都无法完成初始化问题&#xff0c;形成了闭环。 注意 项目中存在Bean的循环依赖&#xff0c;是Bean对象职责划分不明确、代码质量不高的表现&#…...

【driver4】锁,错误码,休眠唤醒,中断,虚拟内存,tasklet

文章目录 1.互斥锁和自旋锁选择&#xff1a;自旋锁&#xff08;开销少&#xff09;的自旋时间和被锁住的代码执行时间成正比关系2.linux错误码&#xff1a;64位系统内核空间最后一页地址为0xfffffffffffff000~0xffffffffffffffff&#xff0c;这段地址是被保留的&#xff0c;如果…...

python之 函数相关知识解析

01 函数的注释与嵌套 1.函数的注释 函数的注释与普通注释的区别&#xff1a;用来说明当前函数的参数含义 param 参数名: 参数的注释信息 return: 函数的返回值 例如&#xff1a; def fun1(name):""":param name: 参数的注释信息:return: 函数的返回值"…...

监视器和显示器的区别,普通硬盘和监控硬盘的区别

监视器与显示器的区别&#xff0c;你真的知道吗&#xff1f; 中小型视频监控系统中&#xff0c;显示系统是最能展现效果的一个重要环节&#xff0c;显示系统的优劣将直接影响视频监控系统的用户体验满意度。 中小型视频监控系统中&#xff0c;显示系统是最能展现效果的一个重要…...

Linux:升级OpenSSL和OpenSSH

原因是现有版本存在安全漏洞&#xff0c;需要升级到新版本 原有版本和升级后的版本 OpenSSL 1.0.2k-fips 26 Jan 2017 -> OpenSSL 1.1.1w 11 Sep 2023OpenSSH_7.4p1, OpenSSL 1.0.2k-fips 26 Jan 2017 -> OpenSSH_9.5p1, OpenSSL 1.1.1w 11 Sep 2023目录 查看现有版…...

方法的入栈和出栈

一.作用域问题 1.全局作用域 在全局都能进行访问的变量 var a 10;function fn() {var b 20;return a b;}console.log(fn()); 2.局部的作用域 只能在限定的范围内进行访问 function fn() {var b 20;}console.log(b); b is not defined 打印的结果是b这个变量没用定义 3…...

PHP介绍

&#x1f40c;博主主页&#xff1a;&#x1f40c;​倔强的大蜗牛&#x1f40c;​ &#x1f4da;专栏分类&#xff1a;PHP❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 目录 一、PHP是什么&#xff1f; 二、 PHP 文件是什么&#xff1f; 三、PHP能做什么&#xff1f; 四、P…...

接口自动化测试之-requests模块详解

一、requests背景 Requests 继承了urllib2的所有特性。Requests支持HTTP连接保持和连接池&#xff0c;支持使用cookie保持会话&#xff0c;支持文件上传&#xff0c;支持自动确定响应内容的编码&#xff0c;支持国际化的 URL 和 POST 数据自动编码。 二、requests安装 利用p…...

低代码+定制物资管理:创新解决方案探析

引言 在当今快速变化的商业环境中&#xff0c;企业面临着不断增长的挑战&#xff0c;如提高效率、降低成本、满足客户需求等。为了应对这些挑战&#xff0c;企业需要不断创新并采用先进的技术解决方案。在这样的背景下&#xff0c;低代码开发和定制化物资管理成为了引领企业变…...

13 【PS作图】人物绘画理论-脸型

三庭五眼 三庭&#xff1a;脸的长度比例 &#xff08;1&#xff09;发际线到眉毛 &#xff08;2&#xff09;眉毛到鼻底 &#xff08;3&#xff09;鼻底到下巴 三个部分大致为三等分 五眼&#xff1a;脸的宽度比例 以眼睛长度为单位&#xff0c;把脸的宽度分成五等分&#x…...

Python批量修改图片文件名中的指定名称

批量处理图像时&#xff0c;图片名有时需要统一&#xff0c;本教程仅针对图片中名如&#xff1a;0001x4.png&#xff0c;批量将图片名中的x4去除&#xff0c;只留下0001.png的情况。 如果想要按照原图片顺序批量修改图片名&#xff0c;参考其它博文&#xff1a;按照原顺序批量…...

STM32点灯大师(点了一颗LED灯,轮询法)

配置操作&#xff1a; 一、使用CubeMX配置到大致的操作 1.1 选择芯片 1.2 选择引脚&#xff08;根据电路图&#xff09; 1.3 配置gpio口 1.4 配置系统 1.5文件项目操作 最后就是点击 二、点击CubeMX生成的代码&#xff0c;并且修改代码 2.1 看看效果 2.2 写代码...

动态规划算法:路径问题

例题一 解法&#xff08;动态规划&#xff09;&#xff1a; 算法思路&#xff1a; 1. 状态表⽰&#xff1a; 对于这种「路径类」的问题&#xff0c;我们的状态表⽰⼀般有两种形式&#xff1a; i. 从 [i, j] 位置出发&#xff0c;巴拉巴拉&#xff1b; ii. 从起始位置出…...

盘一盘接口测试的那些痛点,你现在会解决了吗

前言 说到接口测试&#xff0c;想必大家一定不会陌生。接口测试就是测试系统组件间&#xff0c;接口对接是否顺畅的一种测试。包括测试数据能否交换、能否传递、能否正常控制管理过程&#xff0c;以及系统间的相互逻辑依赖关系&#xff0c;等等。 由于接口测试主要是检测系统…...

基于alpha shapes的边缘点提取(matlab)

1、原理介绍 由Edelsbrunner H提出的alpha shapes算法是一种简单、有效的快速提取边界点算法。其克服了点云边界点形状影响的缺点&#xff0c;可快速准确提取边界点。如下图所示&#xff0c;对于任意形状的平面点云&#xff0c;若一个半径为a的圆&#xff0c;绕其进行滚动&…...

C#三人飞行棋

C#三人飞行棋 #region 1控制台设置int w 50, h 30; ConsoleInit(w, h); #endregion#region 2 场景选择实例//声明一个表示场景标识的变量 E_SceneType nowSceneType new E_SceneType(); while (true) {switch (nowSceneType){case E_SceneType.Begion://开始场景逻辑Consol…...

Notes for the missing semester. Useful and basic knowledge about Linux.

The Shell Contents The first course is to introduce some simple commands. I’ll list some commands that I’m not familiar with: # --silent means dont give log info, # --head means we only want the http head. curl --head --silent bing.com.cn# cut --deli…...

【信息系统项目管理师知识点速记】资源管理基础

项目团队 执行项目工作,实现项目目标的一组人员。成员具备不同技能,可全职或兼职,随项目进展而变化。参与项目规划和决策,贡献专业技能,增强对项目的责任感。项目管理团队 直接参与项目管理活动的成员,负责项目管理和领导。负责项目各阶段的启动、规划、执行、监督、控制…...

Android性能优化面试题汇总

Android的性能优化涉及多个方面,如启动优化、稳定性优化、内存优化、网络优化、电量优化、安全优化等方面。 一、稳定性优化 1.1 你们做了哪些稳定性方面的优化 随着项目的逐渐成熟,用户基数逐渐增多,DAU持续升高,我们遇到了很多稳定性方面的问题,对于我们技术同学遇到…...

Ansible 自动化运维工具 - 了解和模块应用

目录 一. Ansible 的相关知识 1.1 Ansible 工具的简介 1.2 Ansible的四大组件 1.3 运维自动化工具 1.4 Ansible 和其它自动化运维工具对比 1.5 Ansible 的优缺点 二. Ansible 环境安装部署 2.1 管理端安装 ansible 2.2 配置主机清单 三. ansible 命令行模块 3.1 comm…...

AI神助攻!小白也能制作自动重命名工具~

我们平时从网上下载一些文件&#xff0c;文件名很多都是一大串字母和数字&#xff0c;不打开看看&#xff0c;根本不知道里面是什么内容。 我想能不能做个工具&#xff0c;把我们一个文件夹下面的所有word、excel、ppt、pdf文件重命名为文件内容的第一行。 我们有些朋友可能不会…...

(读书笔记-大模型) LLM Powered Autonomous Agents

目录 智能体系统的概念 规划组件 记忆组件 工具组件 案例研究 智能体系统的概念 在大语言模型&#xff08;LLM&#xff09;赋能的自主智能体系统中&#xff0c;LLM 充当了智能体的大脑&#xff0c;其三个关键组件分别如下&#xff1a; 首先是规划&#xff0c;它又分为以下…...

超分辨率重建——BSRN网络训练自己数据集并推理测试(详细图文教程)

目录 一、BSRN网络总结二、源码包准备三、环境准备3.1 报错KeyError: "No object named BSRN found in arch registry!"3.2 安装basicsr源码包3.3 参考环境 四、数据集准备五、训练5.1 配置文件参数修改5.2 启动训练5.2.1 命令方式训练5.2.2 配置Configuration方式训…...

C语言实现贪吃蛇

目录 前言一 . 游戏背景1. 背景介绍2. 项目目标3. 技术要点 二 . 效果演示三 . 游戏的设计与分析1. 核心逻辑2. 设计与分析游戏开始Gamestart()函数游戏运行Gamerun()函数游戏结束Gameend()函数 四 . 参考代码五 . 总结 前言 本文旨在使用C语言和基础数据结构链表来实现贪吃蛇…...

高可用系列四:loadbalancer 负载均衡

负载均衡可以单独使用&#xff0c;也常常与注册中心结合起来使用&#xff0c;其需要解决的问题是流量分发&#xff0c;这是就需要定义分发策略&#xff0c;当然也包括了故障切换的能力。 故障切换 故障切换是负载均衡的基本能力&#xff0c;和注册中心结合时比较简单&#xf…...

苏州营销型网站制作公司/网站编辑seo

GridView中的超级链接&#xff0c;可以设置一个模版列&#xff0c;放入超级链接的控件&#xff0c;设置绑定参数即可。 数据绑定方式有两种&#xff0c;如下示例&#xff1a; Eval方式 &#xff1c;%# Eval("id") %&#xff1e; Bind方式 &#xff1c;%# Bind("…...

小电影网站怎么做的/正规网站优化推广

论文&#xff1a;Semantic Image Synthesis with Spatially-Adaptive Normalization 论文链接&#xff1a;https://arxiv.org/abs/1903.07291 代码链接&#xff1a;https://github.com/NVlabs/SPADE 项目主页链接&#xff1a;https://nvlabs.github.io/SPADE/ 图像生成领域最近…...

wordpress调用数据库文本/云南网络营销公司哪家好

最近项目中使用Hibernate3.2&#xff0c;当代码执行了session.update(obj)或者session.delete(obj)后&#xff0c;数据库并没有更新&#xff0c;打印的sql中也没有update或者delete语句 原因是执行了之后没有session.flush()&#xff0c;查了很久&#xff0c;算是一个hibernat…...

清新太和做网站/高质量外链平台

目录 一、直接面向用户的web页面 二、前端页面缓存 三、用户体验 四、前后端交互 五、i版 六、小程序 七、后端服务 八、数据检查 总结 引言&#xff1a;大致归纳总结了一个立项近半年的一个新业务线测试的场景&#xff0c;及主要产生的一些bug类型&#xff0c;问题大…...

网站开发 php python/百度快速收录接口

Vector不定长数组 例&#xff1a;http://newoj.acmclub.cn/contests/1258/problem/4 1926: 2018蓝桥杯培训-STL应用专题-day 2 vector作业题1 题目描述&#xff1a; 字符串有些是对称的&#xff0c;有些不是对称的&#xff0c;请将那些对称的字符串安从小到大的顺序输出&#x…...

高端网站制作/市场推广外包团队

搜索附加驱动或者drive 然后会出现让你选择nvidia binary driver,然后点击应用更改即可。 转载于:https://www.cnblogs.com/huangshiyu13/p/6209039.html...