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

网络io与select,poll,epoll

前言

网络 IO,会涉及到两个系统对象,一个是用户空间调用 IO 的进程或者线程,另一个是内核空间的内核系统,比如发生 IO 操作 read 时,它会经历两个阶段:

1. 等待数据准备就绪

2. 将数据从内核拷贝到进程或者线程中

因为在以上两个阶段上各有不同的情况,所以出现了多种网络 IO 模型

一、五种 IO 网络模型

1.1、阻塞 IO(blocking IO)

所谓阻塞型接口是指系统调用(一般是 IO 接口)不返回调用结果并让当前线程一直阻塞,只有当该系统调用获得结果或者超时出错时才返回。一个典型的读操作流程如下:

除非特别指定,几乎所有的 IO 接口 ( 包括 socket 接口 ) 都是阻塞型的。这给网络编程带来了一个很大的问题,如在调用 send()的同时,线程将被阻塞,在此期间,线程将无法执行任何运算或响应任何的网络请求

一个简单的改进方案是在服务器端使用多线程(或多进程)。多线程(或多进程)的目的是让每个连接都拥有独立的线程(或进程),这样任何一个连接的阻塞都不会影响其他的连接。多线程的服务器模型似乎完美的解决了为多个客户机提供问答服务的要求,但其实并不尽然。如果要同时响应成百上千路的连接请求,则无论多线程还是多进程都会严重占据系统资源,降低系统对外界响应效率,而线程与进程本身也更容易进入假死状态。多线程模型可以方便高效的解决小规模的服务请求,但面对大规模的服务请求,多线程模型也会遇到瓶颈,可以用非阻塞接口来尝试解决这个问题

1.2、非阻塞 IO(non-blocking IO)

Linux 下,可以通过设置 socket 使其变为 non-blocking。当对一个 non-blocking socket 执行读操作时,流程是这个样子:

循环调用 read()将大幅度推高 CPU 占用率;此外,在这个方案中 read()更多的是起到检测“操作是否完成”的作用,实际操作系统提供了更为高效的检测“操作是否完成“作用的接口,例如 select()多路复用模式,可以一次检测多个连接是否活跃。

1.3、多路复用 IO(IO multiplexing)

IO multiplexing 这个词可能有点陌生,但是提到 select/epoll,大概就都能明白了。有些地方也称这种 IO 方式为事件驱动 IO(event driven IO)。我们都知道,select/epoll 的好处就在于单个 process 就可以同时处理多个网络连接的 IO。它的基本原理就是 select/epoll 这个 function会不断的轮询所负责的所有 socket,当某个 socket 有数据到达了,就通知用户进程。它的流程如图:

当用户进程调用了 select,那么整个进程会被 block,而同时,kernel 会“监视”所有 select 负责的 socket,当任何一个 socket 中的数据准备好了,select 就会返回。这个时候用户进程再调用 read 操作,将数据从 kernel 拷贝到用户进程。

使用 select 以后最大的优势是用户可以在一个线程内同时处理多个 socket 的 IO 请求。用户可以注册多个 socket,然后不断地调用 select 读取被激活的 socket,即可达到在同一个线程内同时处理多个 IO 请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的

这种模型的特征在于每一个执行周期都会探测一次或一组事件,一个特定的事件会触发某个特定的响应。我们可以将这种模型归类为“事件驱动模型”。相比其他模型,使用 select() 的事件驱动模型只用单线程(进程)执行,占用资源少,不消耗太多 CPU,同时能够为多客户端提供服务。如果试图建立一个简单的事件驱动的服务器程序,这个模型有一定的参考价值。

大部分 Unix/Linux 都支持 select 函数,该函数用于探测多个文件句柄的状态变化。

下面给出 select 接口的原型:

FD_ZERO(int fd, fd_set* fds)
FD_SET(int fd, fd_set* fds)
FD_ISSET(int fd, fd_set* fds)
FD_CLR(int fd, fd_set* fds)
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,struct timeval *timeout)

在 select()函数中,readfds、writefds 和exceptfds 同时作为输入参数和输出参数

但这个模型依旧有着很多问题。首先 select()接口并不是实现“事件驱动”的最好选择。因为当需要探测的句柄值较大时,select()接口本身需要消耗大量时间去轮询各个句柄。很多操作系统提供了更为高效的接口,如linux提供了epoll,BSD提供了kqueue,Solaris提供了/dev/poll

其次,该模型将事件探测和事件响应夹杂在一起,一旦事件响应的执行体庞大,则对整个模型是灾难性的。如下例,庞大的执行体 1 的将直接导致响应事件 2 的执行体迟迟得不到执行,并在很大程度上降低了事件探测的及时性

幸运的是,有很多高效的事件驱动库可以屏蔽上述的困难,常见的事件驱动库有libevent 库,还有作为 libevent 替代者的 libev 库

select存在一个bug:当某个socket接收缓冲区有新数据到达,然后select报告这个socket描述符可读,但随后,协议栈检查到这个新分节检验和错误时,会丢弃这个分节,这时再调用read则无数据可读,如果socket没有被设置成nonblocking,此read将阻塞当前线程。需要将socket设置成nonblocking

在select中关闭socket时需要注意:关闭socket需要及时更新监测socket集合。当一个socket被关闭之后,我们需要将其对应的文件描述符从select的监测集合中删除。否则,会导致select函数在下一次调用时伪唤醒,也就是说,调用后立即返回,并且一直返回“有事件发生”,而实际上这个已关闭的文件描述符并没有事件可处理。

1.4、异步 IO(Asynchronous I/O)

Linux 下的 asynchronous IO 用在磁盘 IO 读写操作,不用于网络 IO,从内核 2.6 版本才开始引入。先看一下它的流程:

用户进程发起 read 操作之后,立刻就可以开始去做其它的事。而另一方面,从 kernel的角度,当它受到一个 asynchronous read 之后,首先它会立刻返回,所以不会对用户进程产生任何 block。然后,kernel 会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel 会给用户进程发送一个 signal,告诉它 read 操作完成了。

异步 IO 是真正非阻塞的,它不会对请求进程产生任何的阻塞,因此对高并发的网络服务器实现至关重要

1.5、信号驱动 IO(signal driven I/O, SIGIO)

首先我们允许套接口进行信号驱动 I/O,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个 SIGIO 信号,可以在信号处理函数中调用 I/O 操作函数处理数据。当数据报准备好读取时,内核就为该进程产生一个 SIGIO 信号。我们随后既可以在信号处理函数中调用 read 读取数据报,并通知主循环数据已准备好待处理,也可以立即通知主循环,让它来读取数据报。无论如何处理 SIGIO 信号,这种模型的优势在于等待数据报到达(第一阶段)期间,进程可以继续执行,不被阻塞。免去了 select 的阻塞与轮询,当有活跃套接字时,由注册的 handler 处理

经过上面的介绍,会发现 non-blocking IO 和 asynchronous IO 的区别还是很明显的。在non-blocking IO 中,虽然进程大部分时间都不会被 block,但是它仍然要求进程去主动的 check,并且当数据准备完成以后,也需要进程主动的再次调用 recvfrom 来将数据拷贝到用户内存。

而 asynchronous IO 则完全不同。它就像是用户进程将整个 IO 操作交给了他人(kernel)完成,然后他人做完后发信号通知。在此期间,用户进程不需要去检查 IO 操作的状态,也不需要主动的去拷贝数据。

总结

五种I/O模型的比较: 

除了异步I/O模型,前四种I/O模型都是按顺序分步执行,且需要通过阻塞应用进程来完成数据的复制,因此前四种I/O模型被成为同步I/O模型。

问题:blocking 和 non-blocking 的区别在哪,synchronous IO 和 asynchronous IO 的区别在哪?

blocking 与 non-blocking:前面的介绍中其实已经很明确的说明了这两者的区别。调用 blocking IO 会一直 block 住对应的进程直到操作完成,而non-blocking IO 在 kernel 还在准备数据的情况下会立刻返回。

synchronous IO 和 asynchronous IO:两者的区别就在于 synchronous IO 做”IO operation”的时候会将 process 阻塞。按照这个定义,之前所述的 blocking IO,non-blocking IO,IO multiplexing 都属于synchronous IO。有人可能会说,non-blocking IO 并没有被 block 啊。这里有个非常“狡猾”的地方,定义中所指的”IO operation”是指真实的 IO 操作,就是例子中的 read 这个系统调用。non-blocking IO 在执行 read 这个系统调用的时候,如果 kernel 的数据没有准备好,这时候不会 block 进程。但是当 kernel 中数据准备好的时候,read 会将数据从 kernel 拷贝到用户内存中,这个时候进程是被 block 了,在这段时间内进程是被 block的。而 asynchronous IO 则不一样,当进程发起 IO 操作之后,就直接返回再也不理睬了,直到 kernel 发送一个信号,告诉进程说 IO 完成。在这整个过程中,进程完全没有被 block。

二、简单并发服务器实现示例

2.1 多进程或多线程方式

01-thread_process_tcpserver

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <memory.h>
#include <signal.h>
#include <time.h>#define BUFFER_LENGTH    128
int listenfd;void sig_handler(int signo)
{if(signo == SIGINT){printf("server close\n");close(listenfd);exit(1);}
}void *routine(void *arg) 
{int clientfd = *(int *)arg;while (1) {unsigned char buffer[BUFFER_LENGTH] = {0};int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0);if (ret == 0) { //返回0表示客户端连接断开,服务端也需要调用close断开close(clientfd);break;}printf("buffer : %s, ret: %d\n", buffer, ret);ret = send(clientfd, buffer, ret, 0);}
}// socket --> 
// bash --> execve("./server", "");
// 
// 0, 1, 2
// stdin, stdout, stderr
int main() 
{//注册ctrl+c的信号,用于终止服务器if(signal(SIGINT, sig_handler) == SIG_ERR){perror("signal sigint error");exit(1);}//处理僵尸进程if(signal(SIGCHLD, SIG_IGN) == SIG_ERR){perror("signal sigint error");exit(1);}/*步骤1:创建socket*注:socket创建在内核中,是一个结构体*AF_INET:IPV4*SOCK_STREAM:TCP协议*/listenfd = socket(AF_INET, SOCK_STREAM, 0); if (listenfd == -1) return -1;/*步骤2:调用bind函数将socket和地址(ip、port)进行绑定*/struct sockaddr_in servaddr;servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(9999);if (-1 == bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr))) {return -2;}#if 0 // nonblockint flag = fcntl(listenfd, F_GETFL, 0);flag |= O_NONBLOCK;fcntl(listenfd, F_SETFL, flag);
#endif/*步骤3:调用listen启动监听(指定port监听)*通知系统去接收来自客户端的连接请求(将接受到的客户端连接请求放置到对应的队列中)*第二个参数:指定队列的长度*/listen(listenfd, 10);struct sockaddr_in client;socklen_t len = sizeof(client);while (1) {/*步骤4:调用accept函数从队列中获得一个客户端的请求连接,并返回新的socket描述符*/int clientfd = accept(listenfd, (struct sockaddr*)&client, &len);/*步骤5:调用IO函数和连接的客户端进行双向的通信*/#if 0//(1)多线程实现多并发pthread_t threadid;pthread_create(&threadid, NULL, routine, &clientfd);#else//(2)多进程实现多并发pid_t pid;if((pid=fork()) < 0){perror("fork error");exit(1);}else if (pid == 0)//子进程,处理客户端的具体消息,不需要父进程的套接字{close(listenfd);routine(&clientfd);break;}else //父进程,不需要子进程的套接字{close(clientfd);}
#endif}return 0;
}

2.2 select I/O多路复用

02_select_tcpserver

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <memory.h>
#include <signal.h>
#include <time.h>#define BUFFER_LENGTH    128
int listenfd;void sig_handler(int signo)
{if(signo == SIGINT){printf("server close\n");close(listenfd);exit(1);}
}void *routine(void *arg) 
{int clientfd = *(int *)arg;while (1) {unsigned char buffer[BUFFER_LENGTH] = {0};int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0);if (ret == 0) { //返回0表示客户端连接断开,服务端也需要调用close断开close(clientfd);break;}printf("buffer : %s, ret: %d\n", buffer, ret);ret = send(clientfd, buffer, ret, 0);}
}// socket --> 
// bash --> execve("./server", "");
// 
// 0, 1, 2
// stdin, stdout, stderr
int main() 
{//注册ctrl+c的信号,用于终止服务器if(signal(SIGINT, sig_handler) == SIG_ERR){perror("signal sigint error");exit(1);}//处理僵尸进程if(signal(SIGCHLD, SIG_IGN) == SIG_ERR){perror("signal sigint error");exit(1);}/*步骤1:创建socket*注:socket创建在内核中,是一个结构体*AF_INET:IPV4*SOCK_STREAM:TCP协议*/listenfd = socket(AF_INET, SOCK_STREAM, 0); if (listenfd == -1) return -1;/*步骤2:调用bind函数将socket和地址(ip、port)进行绑定*/struct sockaddr_in servaddr;servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(9999);if (-1 == bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr))) {return -2;}#if 0 // nonblockint flag = fcntl(listenfd, F_GETFL, 0);flag |= O_NONBLOCK;fcntl(listenfd, F_SETFL, flag);
#endif/*步骤3:调用listen启动监听(指定port监听)*通知系统去接收来自客户端的连接请求(将接受到的客户端连接请求放置到对应的队列中)*第二个参数:指定队列的长度*/listen(listenfd, 10);fd_set rfds, wfds, rset, wset;FD_ZERO(&rfds);FD_SET(listenfd, &rfds);FD_ZERO(&wfds);int maxfd = listenfd;unsigned char buffer[BUFFER_LENGTH] = {0};int ret = 0, i;while (1) {rset = rfds;wset = wfds;//调用select监听设置的可读、可写集合中的socket是否有可读活可写事件发生//select相当于一个秘书int nready = select(maxfd+1, &rset, &wset, NULL, NULL);//判断listenfd是否有可读事件发生,也就是是否有新的client连接if (FD_ISSET(listenfd, &rset)) {struct sockaddr_in client;socklen_t len = sizeof(client);int clientfd = accept(listenfd, (struct sockaddr*)&client, &len);FD_SET(clientfd, &rfds); //将新的client连接设置到可读集合中,下次一起监听printf("listenfd accept new client(fd = %d)--> \n", clientfd);if (clientfd > maxfd) maxfd = clientfd;} //处理连接的client的读写事件for (i = listenfd+1; i <= maxfd;i ++) {if (FD_ISSET(i, &rset)){ ret = recv(i, buffer, BUFFER_LENGTH, 0);if (ret == 0) {close(i);FD_CLR(i, &rfds); //客户端主动close后,需要从集合中将其清除} else if (ret > 0) {printf("buffer : %s, ret: %d\n", buffer, ret);//接受到client的数据后,将其设置到可写集合中,下次select监听时会立马检测到io的可写事件(因为内核的写缓存未满,就会触发可写事件)//然后执行FD_ISSET(i, &wset),也就实现了读和写的分离FD_SET(i, &wfds); }} else if (FD_ISSET(i, &wset)) {ret = send(i, buffer, ret, 0); FD_CLR(i, &wfds); //发送完数据后,要将其从可写集合中清除,不然会一直触发!!!!!}}}
}

2.3 epoll I/O多路复用

03_epoll_tcpserver

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <memory.h>
#include <signal.h>
#include <time.h>#define BUFFER_LENGTH    128
#define EVENTS_LENGTH    128int listenfd;
char rbuffer[BUFFER_LENGTH] = {0};
char wbuffer[BUFFER_LENGTH] = {0};void sig_handler(int signo)
{if(signo == SIGINT){printf("server close\n");close(listenfd);exit(1);}
}void *routine(void *arg) 
{int clientfd = *(int *)arg;while (1) {unsigned char buffer[BUFFER_LENGTH] = {0};int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0);if (ret == 0) { //返回0表示客户端连接断开,服务端也需要调用close断开close(clientfd);break;}printf("buffer : %s, ret: %d\n", buffer, ret);ret = send(clientfd, buffer, ret, 0);}
}// socket --> 
// bash --> execve("./server", "");
// 
// 0, 1, 2
// stdin, stdout, stderr
int main() 
{//注册ctrl+c的信号,用于终止服务器if(signal(SIGINT, sig_handler) == SIG_ERR){perror("signal sigint error");exit(1);}//处理僵尸进程if(signal(SIGCHLD, SIG_IGN) == SIG_ERR){perror("signal sigint error");exit(1);}/*步骤1:创建socket*注:socket创建在内核中,是一个结构体*AF_INET:IPV4*SOCK_STREAM:TCP协议*/listenfd = socket(AF_INET, SOCK_STREAM, 0); if (listenfd == -1) return -1;/*步骤2:调用bind函数将socket和地址(ip、port)进行绑定*/struct sockaddr_in servaddr;servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(9999);if (-1 == bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr))) {return -2;}#if 0 // nonblockint flag = fcntl(listenfd, F_GETFL, 0);flag |= O_NONBLOCK;fcntl(listenfd, F_SETFL, flag);
#endif/*步骤3:调用listen启动监听(指定port监听)*通知系统去接收来自客户端的连接请求(将接受到的客户端连接请求放置到对应的队列中)*第二个参数:指定队列的长度*/listen(listenfd, 10);//创建epollint epfd = epoll_create(1);struct epoll_event ev, events[EVENTS_LENGTH];ev.events = EPOLLIN;ev.data.fd = listenfd;epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev); //添加listenfdwhile (1) { int nready = epoll_wait(epfd, events, EVENTS_LENGTH, -1); // -1, ms printf("epoll_wait -------> %d ready\n", nready);int i = 0;for (i = 0;i < nready;i ++) {int clientfd = events[i].data.fd;if (listenfd == clientfd) // accept{struct sockaddr_in client;socklen_t len = sizeof(client);int connfd = accept(listenfd, (struct sockaddr*)&client, &len);if (connfd == -1) break;printf("accept: clientfd=%d\n\n", connfd);ev.events = EPOLLIN;ev.data.fd = connfd;epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);} else if (events[i].events & EPOLLIN) //clientfd{ printf("epoll_wait -------> fd=%d EPOLLIN\n", clientfd);int n = recv(clientfd, rbuffer, BUFFER_LENGTH, 0);if (n > 0) {printf("recv: %s, n: %d\n", rbuffer, n);memset(wbuffer, 0, BUFFER_LENGTH);memcpy(wbuffer, rbuffer, n);memset(rbuffer, 0, BUFFER_LENGTH);//send(clientfd, wbuffer, strlen(wbuffer), 0);//接收完数据后,修改fd的监听事件为EPOLLOUT,这样会立即触发fd的可写事件//当然这里也可以直接sendev.events = EPOLLOUT;ev.data.fd = clientfd;epoll_ctl(epfd, EPOLL_CTL_MOD, clientfd, &ev);} }else if (events[i].events & EPOLLOUT) {printf("epoll_wait -------> fd=%d EPOLLOUT\n", clientfd);int sent = send(clientfd, wbuffer, strlen(wbuffer), 0);printf("send: %d\n\n", sent);//发送完数据后,修改fd的监听事件为EPOLLIN,这样会继续等待fd的可读事件ev.events = EPOLLIN;ev.data.fd = clientfd;epoll_ctl(epfd, EPOLL_CTL_MOD, clientfd, &ev);}}}
}

参考链接

epoll的本质

Linux内核编程--常见IO模型与select/poll/epoll编程

一道搜狗面试题:IO多路复用中select、poll、epoll之间的区别

相关文章:

网络io与select,poll,epoll

前言 网络 IO&#xff0c;会涉及到两个系统对象&#xff0c;一个是用户空间调用 IO 的进程或者线程&#xff0c;另一个是内核空间的内核系统&#xff0c;比如发生 IO 操作 read 时&#xff0c;它会经历两个阶段&#xff1a; 1. 等待数据准备就绪 2. 将数据从内核拷贝到进程或…...

【Linux】多线程(一万六千字)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 文章目录 前言 线程的概念 线程的理解(Linux系统为例) 在Linux系统里如何保证让正文部分的代码可以并发的去跑呢&#xff1f; 为什么要有多进程呢&#xff1f; 为…...

sh脚本笔记2

test条件测试 语法 条件测试语法说明语法1&#xff1a;test <测试表达式>这是利用test命令进行条件测试表达式的方法。test命令和“<测试表达式>”之间至少有一个空格语法2&#xff1a;[ <测试表达式> ]这是通过[]&#xff08;单中括号&#xff09;进行条件…...

js替换对象里面的对象名称

data为数组&#xff0c;val为修改前的名称&#xff0c;name为修改后的名称 JSON.parse(JSON.stringify(data).replace(/val/g, name)) &#xff1b; 1.替换data里面的对象tenantInfoRespVO名称替换成tenantInfoUpdateReqVO 2.替换语句&#xff1a; 代码可复制 let tenantInf…...

鸿蒙开发设备管理:【@ohos.settings (设置数据项名称)】

设置数据项名称 说明&#xff1a; 本模块首批接口从API version 8开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 本模块提供设置数据项的访问功能相关接口的说明及示例。 导入模块 import settings from ohos.settings;settings.getUri…...

STM32之五:TIM定时器(2-通用定时器)

目录 通用定时器&#xff08;TIM2~5&#xff09;框图 1、 输入时钟源选择 2、 时基单元 3 、输入捕获&#xff1a;&#xff08;IC—Input Capture&#xff09; 3.1 输入捕获通道框图&#xff08;TI1为例&#xff09; 3.1.1 滤波器&#xff1a; 3.1.2 边沿检测器&#xf…...

【分布式系统】监控平台Zabbix对接grafana

以前两篇博客为基础 【分布式系统】监控平台Zabbix介绍与部署&#xff08;命令截图版&#xff09;-CSDN博客 【分布式系统】监控平台Zabbix自定义模版配置-CSDN博客 一.安装grafana并启动 添加一台服务器192.168.80.104 初始化操作 systemctl disable --now firewalld set…...

操作系统真象还原:编写硬盘驱动程序

第13章-编写硬盘驱动程序 这是一个网站有所有小节的代码实现&#xff0c;同时也包含了Bochs等文件 13.1 硬盘及分区表 13.1.1 创建从盘及获取安装的磁盘数 要实现文件系统&#xff0c;必须先有个磁盘介质&#xff0c;虽然咱们己经有个虚拟磁盘 hd60M.img&#xff0c;但它只…...

firewalld防火墙(二)

一&#xff1a;firewalld高级配置 1&#xff1a;关于iptables的知识 iptables 是Linux系统中传统的命令行防火墙管理工具&#xff0c;它基于内核的netfilter框架工作&#xff0c;用于配置和管理网络规则集&#xff0c;比如过滤&#xff08;允许/拒绝&#xff09;进出的数据包…...

Android-悬浮窗口

在Android系统中&#xff0c;如果应用需要弹出一个悬浮窗口&#xff0c;就需要申请一项特殊权限 <uses-permission android:name"android.permission.SYSTEM_ALERT_WINDOW"/>在Android O之前的系统中申请了该权限后&#xff0c;再给对应的window设置 WindowM…...

打破僵局:Foxit Reader无法打开的终极解决方案

打破僵局&#xff1a;Foxit Reader无法打开的终极解决方案 在数字化阅读时代&#xff0c;Foxit Reader作为一款广受欢迎的PDF阅读器&#xff0c;其打不开的问题无疑会给用户带来诸多不便。本文将为您提供全面的解决方案&#xff0c;从基础检查到高级技巧&#xff0c;确保您能够…...

[调试] JTAG下运行正常,从QSPI或者SD卡启动则无响应,如何查找问题

[调试] JTAG下运行正常&#xff0c;从QSPI或者SD卡启动则无响应&#xff0c;如何查找问题 一、问题现象二、用自定义fsbl替代系统默认的fsbl1. 新建fsbl_new2. 如果提示缺少xilffs库3. 使能调试信息输出 三. 启动成功和失败情况下的典型输出1. JTAG启动模式: 正常加载2. QSPI启…...

Linux内核 -- 多线程之wait_event用法

Linux Kernel 中 wait_event 的高级用法及注意事项 在Linux内核编程中&#xff0c;wait_event 系列函数是用于实现进程等待和事件通知机制的重要工具。本文将详细介绍 wait_event 的高级用法以及注意事项。 1. 基本用法 wait_event 系列宏主要包括以下几种形式&#xff1a; …...

双指针系列第 8 篇:盛水最多的容器。几句话讲明白!

Leetcode 题目链接 思路 取首尾双指针和水量如下所示&#xff0c;设高度函数为 h ( i ) h(i) h(i)&#xff0c;在下图中 h ( l ) < h ( r ) h(l) < h(r) h(l)<h(r)。 观察以 l l l 为左边界所能构成的其他水量&#xff0c;与矮的右边界搭配结果如下。 与高的…...

c++高阶-1-模板

文章目录 模板一、模板基本语法二、函数模板1.基本语法2.函数模板注意事项3.普通函数和函数模板区别4.普通函数和函数模板调用规则 三、类模板1.基本语法2.类模板和函数模板的区别3.类模板中成员函数调用时机4.类模板对象做函数参数5.类模板与继承6.成员函数的类外实现 模板 一…...

.net core 的 winform 的 浏览器控件 WebView2

在.NET Core WinForms应用程序中&#xff0c;没有直接的“浏览器控件”&#xff0c;因为WinForms不支持像WebBrowser控件那样的功能。但是&#xff0c;你可以使用WebView2控件&#xff0c;它是一个基于Chromium的浏览器内核&#xff0c;可以在WinForms应用程序中嵌入Web内容。 …...

Django QuerySet对象,all()方法

all()方法 在Django中&#xff0c;all()方法是QuerySet对象的一个方法&#xff0c;用于获取模型的所有实例。 当你调用ModelName.objects.all()时&#xff0c;Django会生成一个SQL查询&#xff0c;从数据库中获取该模型的所有记录&#xff0c;并返回一个QuerySet对象&#xf…...

自动生成网站sitemap

要在 Next.js 和 Contentlayer 项目中实现自动生成 Sitemap 的功能&#xff0c;你可以编写一个脚本&#xff0c;在每次生成文档后自动生成 Sitemap。以下是一个示例脚本&#xff0c;你可以根据自己的需求进行调整。 步骤 1&#xff1a;安装必要的依赖 首先&#xff0c;你需要…...

中国经济昆虫志(55卷)

中国经济昆虫志&#xff0c;共55卷&#xff0c;内容包括概述、形态特征、分类等。各级分类单元均编有检索表&#xff0c;每个种有特征描述、地理分布&#xff0c;有的还记载有生活习性和防治方法。为便于鉴定&#xff0c;绘制有特征图和彩色图。 包括鞘翅目天牛科、半翅目蝽科、…...

linux环境安装elasticsearch缓存数据库和Kibana客户端

linux环境安装elasticsearch缓存数据库&#xff0c;今天我们安装7.17.18版本&#xff0c;并分析遇到的问题。 一、elasticsearch安装运行 1、直接下载 wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.17.18-linux-x86_64.tar.gz2、解压 tar -…...

OpenSSL的一些使用案例

目录 一、介绍 二、基本使用 1、Shell &#xff08;1&#xff09;文件加解密 &#xff08;2&#xff09;生成密钥文件 2、API &#xff08;1&#xff09;md5sum &#xff08;2&#xff09;AES256加解密 一、介绍 本篇博客重点不是详细描述 OpenSSL 的用法&#xff0c;只…...

常用字符串方法<python>

导言 在python中内置了许多的字符串方法&#xff0c;使用字符串方法可以方便快捷解决很多问题&#xff0c;所以本文将要介绍一些常用的字符串方法。 目录 导言 string.center(width[,fillchar]) string.capitalize() string.count(sub[,start[,end]]) string.join(iterabl…...

线程池666666

1. 作用 线程池内部维护了多个工作线程&#xff0c;每个工作线程都会去任务队列中拿取任务并执行&#xff0c;当执行完一个任务后不是马上销毁&#xff0c;而是继续保留执行其它任务。显然&#xff0c;线程池提高了多线程的复用率&#xff0c;减少了创建和销毁线程的时间。 2…...

Python28-5 k-means算法

k-means 算法介绍 k-means 算法是一种经典的聚类算法&#xff0c;其目的是将数据集分成 ( k ) 个不同的簇&#xff0c;每个簇内的数据点尽可能接近。算法的基本思想是通过反复迭代优化簇中心的位置&#xff0c;使得每个簇内的点与簇中心的距离之和最小。k-means 算法的具体步骤…...

主流国产服务器操作系统技术分析

主流国产服务器操作系统 信创 "信创"&#xff0c;即信息技术应用创新&#xff0c;作为科技自立自强的核心词汇&#xff0c;在我国信息化建设的进程中扮演着至关重要的角色。自2016年起步&#xff0c;2020年开始蓬勃兴起&#xff0c;信创的浪潮正席卷整个信息与通信技…...

【Linux】线程封装与互斥(万字)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 文章目录 前言 C多线程的用法 对原生线程进行一次封装 理解pthread线程 Linux线程互斥 进程线程间的互斥相关背景概念 互斥量mutex 操作共享变量会有问题的售票…...

5分钟教你部署MySQL8.0环境

此方法基于Windows操作系统&#xff01; 一、在MySQL官网单击downloads&#xff08;下载&#xff09;MySQLhttps://www.mysql.com/cn/ 选择在Windows操作系统下载 二、选择合适的版本 推荐下载第二种&#xff0c;安装时离线安装即可 三、安装MySQL8.0 1、找到MySQL下载完成…...

LLM应用:传统NLP任务

LLM出来以后&#xff0c;知乎上就出现了“传统NLP已死”的言论&#xff0c;但是传统NLP真的就被扔进历史的垃圾桶了吗&#xff1f; 其实&#xff0c;尽管LLM具有出色的通用能力&#xff0c;但仍然无法有效应对低资源领域的自然语言处理任务&#xff0c;如小语种翻译。为了更好地…...

基于Hadoop平台的电信客服数据的处理与分析③项目开发:搭建Kafka大数据运算环境---任务11:基础环境准备

任务描述 任务主要是安装配置基础环境&#xff0c;主要内容包括&#xff1a; 1、安装java Kafka和ZooKeeper都需要安装Java环境&#xff0c;推荐至少Java8及以上版本 2、安装ZooKeeper ZooKeeper是Kafka集群的必要组件 3、安装kafka Kafka版本包括使用的scala语言版本和kafka版…...

Golang中swtich中如何强制执行下一个代码块

switch 语句中的 case 代码块会默认带上 break&#xff0c;但可以使用 fallthrough 来强制执行下一个 case 代码块。 package mainimport ("fmt" )func main() {isSpace : func(char byte) bool {switch char {case : // 空格符会直接 break&#xff0c;返回 false…...

读书笔记-Java并发编程的艺术-第4章(Java并发编程基础)-第2节(启动和终止线程)

文章目录 4.2 启动和终止线程4.2.1 构造线程4.2.2 启动线程4.2.3 理解中断4.2.4 过期的suspend()、resume()和stop()4.2.5 安全地终止线程 4.2 启动和终止线程 在前面章节的示例中通过调用线程的start()方法进行启动&#xff0c;随着run()方法的执行完毕&#xff0c;线程也随之…...

通俗大白话理解Docker

什么是Docker Docker本质上是一种容器化技术&#xff0c;用于将应用程序及其所有依赖打包到一个标准化的单元中。这些单元&#xff08;容器&#xff09;可以在任何运行Docker的机器上运行。每个容器是相互隔离的&#xff0c;具有自己的文件系统、网络和进程空间。 以下是大白话…...

题解:CF1981C(Turtle and an Incomplete Sequence)

题解&#xff1a;CF1981C&#xff08;Turtle and an Incomplete Sequence&#xff09; Part 1&#xff1a;题意理解 地址链接&#xff1a;CF、洛谷。题面翻译&#xff1a;给定一个长度为 n n n 的序列 a a a&#xff0c;其中有一些元素未知&#xff0c;用 − 1 -1 −1 表示…...

Swift 中强大的 Key Paths(键路径)机制趣谈(上)

概览 小伙伴们可能不知道&#xff1a;在 Swift 语言中隐藏着大量看似“其貌不扬”实则却让秃头码农们“高世骇俗”&#xff0c;堪称卧虎藏龙的各种秘技。 其中&#xff0c;有一枚“不起眼”的小家伙称之为键路径&#xff08;Key Paths&#xff09;。如若将其善加利用&#xff…...

(十二)纹理和采样

纹理 在绘制三角形的过程中&#xff0c;将图片贴到三角形上进行显示的过程&#xff0c;就是纹理贴图的过程 uv坐标 如果如果图片尺寸和实际贴图尺寸不一致&#xff0c;就会导致像素不够用了的问题 纹理与采样 纹理对象(Texture)&#xff1a;在GPU端&#xff0c;用来以一…...

QT创建地理信息shp文件编辑器shp_editor

空闲之余创建一个简单的矢量shp文件编辑器&#xff0c;加深对shp文件的理解。 一、启动程序 二、打开shp文件 三、显示shp文件的几何图形 四、双击右边表格中的feature&#xff0c;主窗体显示选中feature的各个节点。 五、鼠标在主窗体中选中feature的节点&#xff0c;按鼠标左…...

解析Kotlin中扩展函数与扩展属性【笔记摘要】

1.扩展函数 1.1 作用域&#xff1a;扩展函数写的位置不同&#xff0c;作用域就也不同 扩展函数可以写成顶层函数&#xff08;Top-level Function&#xff09;&#xff0c;此时它只属于它所在的 package。这样你就能在任何类里使用它&#xff1a; package com.rengwuxianfun …...

【Java学习笔记】java图形界面编程

在前面的章节中&#xff0c;我们开发运行的应用程序都没有图形界面&#xff0c;但是很多应用软件&#xff0c;如Windows下的Office办公软件、扑克牌接龙游戏软件、企业进销存ERP系统等&#xff0c;都有很漂亮的图形界面。素以需要我们开发具有图形界面的软件。 Java图形界面编程…...

STM32入门笔记(03): ADC(SPL库函数版)(2)

A/D转换的常用技术有逐次逼近式、双积分式、并行式和跟踪比较式等。目前用的较多的是前3种。 A/D转换器的主要技术指标 转换时间 分辨率 例如&#xff0c;8位A/D转换器的数字输出量的变化范围为0&#xff5e;255&#xff0c;当输入电压的满刻度为5V时&#xff0c;数字量每变化…...

2024年7月2日 (周二) 叶子游戏新闻

老板键工具来唤去: 它可以为常用程序自定义快捷键&#xff0c;实现一键唤起、一键隐藏的 Windows 工具&#xff0c;并且支持窗口动态绑定快捷键&#xff08;无需设置自动实现&#xff09;。 卸载工具 HiBitUninstaller: Windows上的软件卸载工具 经典名作30周年新篇《恐怖惊魂夜…...

如何使用Spring Boot Profiles进行环境配置管理

如何使用Spring Boot Profiles进行环境配置管理 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天我们将深入探讨如何利用Spring Boot Profiles来管理不同环境…...

Java错题归纳(二)

1、若有如下接口A的定义&#xff0c;下列哪些类下确实现了该接口&#xff1a;C interface A { void method1(int i); void method2(int j); } A class B implements A{ void method1( ) { } void method2( ) { } } B class B implements A { void method1(int i ) { }…...

Grafana面试题精选和参考答案

目录 Grafana是什么以及它的主要应用场景 Grafana支持的数据源 Grafana的体系结构及主要组件 Grafana如何实现数据的可视化和监控 Grafana支持的图表类型 如何在Grafana中创建和编辑仪表盘 Grafana的查询编辑器功能 Grafana支持的认证方式 Grafana的性能调优建议 Gra…...

Node版本管理工具 fnm 安装使用

fnm 是一个基于 Rust 开发的 Node 版本管理工具&#xff0c;它的目标是提供一个快速、简单且可靠的方式来管理 Node.js 的不同版本。同时&#xff0c;它是跨平台的&#xff0c;支持 macOS、Linux、Windows。&#x1f680; Fast and simple Node.js version manager, built in R…...

vector模拟实现【C++】

文章目录 全部的实现代码放在了文章末尾准备工作包含头文件定义命名空间和类类的成员变量 迭代器迭代器获取函数 构造函数默认构造使用n个值构造迭代器区间构造解决迭代器区间构造和用n个值构造的冲突拷贝构造 析构函数swap【交换函数】赋值运算符重载emptysize和capacityopera…...

《每天5分钟用Flask搭建一个管理系统》第11章:测试与部署

第11章&#xff1a;测试与部署 11.1 测试的重要性 测试是确保应用质量和可靠性的关键步骤。它帮助开发者发现和修复错误&#xff0c;验证功能按预期工作。 11.2 Flask测试客户端的使用 Flask提供了一个测试客户端&#xff0c;可以在开发过程中模拟请求并测试应用的响应。 …...

Landsat数据从Collection1更改为Collection2

目录 问题解决 问题 需要注意!您使用的是废弃的陆地卫星数据集。为确保功能持续&#xff0c;请在2024年7月1日前更新。 在使用一些以前的代码时会遇到报错&#xff0c;因为代码里面用的是老的数据集 解决 对于地表反射率SR&#xff0c;需要在name中&#xff0c;将C01换为C02&…...

《每天5分钟用Flask搭建一个管理系统》第12章:安全性

第12章&#xff1a;安全性 12.1 Web应用的安全威胁 Web应用面临的安全威胁包括但不限于跨站脚本攻击&#xff08;XSS&#xff09;、SQL注入、跨站请求伪造&#xff08;CSRF&#xff09;、不安全的直接对象引用&#xff08;IDOR&#xff09;等。 12.2 Flask-Talisman扩展的使…...

Unity之创建与导出PDF

内容将会持续更新&#xff0c;有错误的地方欢迎指正&#xff0c;谢谢! Unity之创建与导出PDF TechX 坚持将创新的科技带给世界&#xff01; 拥有更好的学习体验 —— 不断努力&#xff0c;不断进步&#xff0c;不断探索 TechX —— 心探索、心进取&#xff01; 助力快速…...

【Android面试八股文】优化View层次过深问题,选择哪个布局比较好?

优化深层次View层次结构的问题&#xff0c;选择合适的布局方式是至关重要的。以下是几点建议&#xff1a; 使用ConstraintLayout&#xff1a;ConstraintLayout是Android开发中推荐的布局&#xff0c;能够有效减少嵌套&#xff0c;提高布局性能。相比RelativeLayout&#xff0c;…...

轻松设置:服务器域名配置全攻略

目录 前置条件 在阅读本篇内容之前&#xff0c;请先确保以下物料已准备好&#xff1a; 一台公网服务器&#xff0c;服务正常运行申请完成的域名&#xff0c;在对应域名服务商后台正常DNS解析域名备案完成可选条件&#xff1a;有https访问请求时&#xff0c;需要申请SSL证书 …...

ABB PPC902AE1013BHE010751R0101控制器 处理器 模块

ABB PPC902AE1013BHE010751R0101 该模块是用于自动化和控制系统的高性能可编程控制器。它旨在与其他自动化和控制设备一起使用&#xff0c;以提供完整的系统解决方案 是一种数字输入/输出模块&#xff0c;提供了高水平的性能和可靠性。它专为苛刻的工业应用而设计&#xff0c…...

智能生产管理系统设计

智能生产管理系统的设计旨在提升制造业的效率、灵活性和响应速度&#xff0c;通过集成先进的信息技术&#xff08;如物联网IoT、大数据分析、人工智能AI、云计算等&#xff09;实现生产过程的智能化。以下是一些关键设计要素和步骤&#xff0c;用于构建一个高效的智能生产管理系…...

hmmer数据库合并

下载的数据库&#xff0c;解压之后是一个很多hmm文件的文件夹&#xff0c;不便于注释 https://fileshare.csb.univie.ac.at/vog/vog224/vfam.hmm.tar.gz cat *.hmm > vog224.hmm hmmpress vog224.hmm比对 hmmscan --tblout test_VOG.tbl --domtblout test_VOG.dom --noal…...

python入门详细介绍

Python 是一种广泛使用的高级编程语言&#xff0c;以其清晰的语法和代码可读性而闻名。它支持多种编程范式&#xff0c;包括面向对象、命令式、函数式和过程式编程。Python 由 Guido van Rossum 于1989年底发明&#xff0c;第一个公开发行版发行于1991年。 Python 的特点&…...

理解 REST API 和 GraphQL 的区别

你可能听说过 GraphQL&#xff0c;但对它与 REST 的区别还不完全确定。今天我们将介绍 REST 和 GraphQL 的一些基本原理&#xff0c;以及它们的不同使用场景。 GraphQL 作为 REST API 的替代品越来越受欢迎&#xff0c;不过它不一定是完全的“替代品”。 根据你的使用情景&am…...

售24.69万元,智己L6新车型上市!产品力如何?

近日,智己旗下新车——2024款智己L6 Max 长续航欧版正式上市!定位为纯电中大型轿车。新车为新增车型,共推出了1款车型,售价为24.69万元,产品力如何呢?具体来了解一下!首先来看外观方面,新车采用了纯电车型惯用的封闭式格栅设计,搭配两侧个性犀利的大灯组,内部结构清晰…...

“霸总雷军”发财报,又赢了

朋友们,5月23日,小米发布2024年Q1业绩公告。小米第一季度营收人民币755亿元,同比增长27%。雷军想必对今年的业绩是满意的。从2020年提出高端战略后,这是小米首次在财报中将其形容为“取得显著成效”。与之相对的是,2024年第一季度小米经调整净利润达人民币65亿元,同比增长…...

SAP 生产订单报工函数BAPI_PRODORDCONF_CREATE_TT不返回报错信息

最近财务一直反馈MES报工的数据都没有成本,然后去查看原因发现是财务当月的KP26的价格没有进行维护,导致没有收集到工单的报工成本。 但是在前台操作CO11 报工的时候,系统会给出报错的信息 但是我们在调用函数BAPI_PRODORDCONF_CREATE_TT的时候,系统并没有返回报错的信息…...

解锁Android高效数据传输的秘钥 - Parcelable剖析

作为Android开发者&#xff0c;我们经常需要在不同的组件(Activity、Service等)之间传输数据。这里的"传输"往往不仅仅是简单的数据复制&#xff0c;还可能涉及跨进程的内存复制操作。当传输的数据量较大时&#xff0c;这种操作可能会带来严重的性能问题。而Android系…...

于AI对话 --如何更好的使用AI工具

文章目录 于AI对话 --如何更好的使用AI工具1、认识AI工具&#xff1a;2、对话原则&#xff1a;3、提问步骤&#xff1a;4、AI可以学习什么&#xff1f;5、提问技巧&#xff1a;1、提出假设性问题:2、&#xff08;鼓励引导式提问&#xff09;跨学科思考:举个例子&#xff1a; 3、…...

JAVA学习路线图

计算机网课资料分享群&#xff1a;710895979...