第16章 网络io与io多路复用select/pool/epool

第16.1节 写一个服务端代码

  1. 服务端代码

    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>#include <fcntl.h>int main() {//openint sockfd = socket(AF_INET, SOCK_STREAM, 0); // iostruct sockaddr_in servaddr;memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 192.168.2.123servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0servaddr.sin_port = htons(9999);if (-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))) {printf("bind failed: %s", strerror(errno));return -1;}listen(sockfd, 10); getchar();
    }
    
  2. 运行该段代码,可以使用netstat -anop | grep 9999查看某一个端口(如:9999)进程的命令。结果如下图。可以发现代码执行到此处的时候,程序已经开始监听了。

    1702393298534.png

  3. 可以使用第三方的网络助手工具尝试连接该端程序。可以发现可以正常发送成功,但是却没有没有反馈。

    1702394609486.png

  4. 出现上述问题主要原因见下图。通过listen这是监听了,并没有真正的建立连接。建立连接是通过accept来实现的,并且每个客户端都有一个服务对应处理。

  5. 故而添加accept代码如下

    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>#include <fcntl.h>int main() {//openint sockfd = socket(AF_INET, SOCK_STREAM, 0); // iostruct sockaddr_in servaddr;memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 192.168.2.123servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0servaddr.sin_port = htons(9999);if (-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))) {printf("bind failed: %s", strerror(errno));return -1;}listen(sockfd, 10); //sleep(10);#if 0printf("sleep\n");int flags = fcntl(sockfd, F_GETFL, 0);flags |= O_NONBLOCK;fcntl(sockfd, F_SETFL, flags);
    #endifstruct sockaddr_in clientaddr;socklen_t len = sizeof(clientaddr); int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);// getchar();}
    
  6. 上述代码运行效果如下
    1702394975250.png
    可以发现代码阻塞在accept函数处,此时通过工具建立连接,就会继续运行。而通过代码中的#if…#endif处的代码可以将阻塞io转换为非阻塞io。

  7. 思考:若在listen之后还未到accept的时候建立连接会成功吗?修改代码验证该思考。

    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>#include <fcntl.h>int main() {//openint sockfd = socket(AF_INET, SOCK_STREAM, 0); // iostruct sockaddr_in servaddr;memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 192.168.2.123servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0servaddr.sin_port = htons(9999);if (-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))){printf("bind failed: %s", strerror(errno));return -1;}listen(sockfd, 10); sleep(10);printf("sleep\n");#if 0printf("sleep\n");int flags = fcntl(sockfd, F_GETFL, 0);flags |= O_NONBLOCK;fcntl(sockfd, F_SETFL, flags);
    #endifstruct sockaddr_in clientaddr;socklen_t len = sizeof(clientaddr); int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);printf("sockfd:%d, clientfd:%d\n", sockfd, clientfd);// getchar();
    }
    

    使用工具在未打印sleep之前点击连接,过一会儿运行结果如下图,可以发现依然可以建立连接。
    1702395456754.png
    所以该思考的结果是:accept和是否能建立连接没有关系。如在listen之后sleep(10),还没有到accept的时候建立连接也是可以的。

  8. 思考:将代码改成非阻塞的,若在accept到来之前还未点击连接会如何?修改案例代码如下:

    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>#include <fcntl.h>int main() {//openint sockfd = socket(AF_INET, SOCK_STREAM, 0); // iostruct sockaddr_in servaddr;memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 192.168.2.123servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0servaddr.sin_port = htons(9999);if (-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))) {printf("bind failed: %s", strerror(errno));return -1;}listen(sockfd, 10); sleep(10);#if 1printf("sleep\n");int flags = fcntl(sockfd, F_GETFL, 0);flags |= O_NONBLOCK;fcntl(sockfd, F_SETFL, flags);
    #endifstruct sockaddr_in clientaddr;socklen_t len = sizeof(clientaddr); int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);printf("sockfd:%d, clientfd:%d\n", sockfd, clientfd);// getchar();
    }
    

    代码运行结果:
    1702395803487.png
    可以发现失败了,linux下正数表示成功,-1表示失败。而如果在运行到accept之前点击了连接,就会连接成功。此处从3开始,是因为0是标准输入,1是标准输出,2是错误。

  9. 上面只实现了网络io的连接,那么接下来考虑服务端如何接收数据。使用recv,recv返回0表示断开连接

    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>#include <fcntl.h>#define BUFFER_LENGTH		1024int main() {//openint sockfd = socket(AF_INET, SOCK_STREAM, 0); // iostruct sockaddr_in servaddr;memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 192.168.2.123servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0servaddr.sin_port = htons(9999);if (-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))) {printf("bind failed: %s", strerror(errno));return -1;}listen(sockfd, 10); struct sockaddr_in clientaddr;socklen_t len = sizeof(clientaddr); int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);printf("sockfd:%d, clientfd:%d\n", sockfd, clientfd);char buffer[BUFFER_LENGTH] = {0};int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0);printf("ret: %d, buffer: %s\n", ret, buffer);send(clientfd, buffer, ret, 0);// getchar();
    }
    

    使用第三方的网络助手工具尝试连接该端程序,代码运行结果如下:
    1702475309152.png
    通过上图可以发现recv也是阻塞的,只会阻塞一次,send 也是一次**。**

  10. 思考:那么放入到while中是否可以发送多次数据呢?代码如下:

    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>#include <fcntl.h>#define BUFFER_LENGTH		1024int main() {//openint sockfd = socket(AF_INET, SOCK_STREAM, 0); // iostruct sockaddr_in servaddr;memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 192.168.2.123servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0servaddr.sin_port = htons(9999);if (-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))) {printf("bind failed: %s", strerror(errno));return -1;}listen(sockfd, 10); struct sockaddr_in clientaddr;socklen_t len = sizeof(clientaddr); int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);printf("sockfd:%d, clientfd:%d\n", sockfd, clientfd);while (1) { //slavechar buffer[BUFFER_LENGTH] = {0};int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0);if (ret == 0) {close(clientfd);break; }printf("ret: %d, buffer: %s\n", ret, buffer);send(clientfd, buffer, ret, 0);}// getchar();
    }
    

    代码运行结果如下:
    image.png
    可以发现支持数据的多次接收。那么这段代码是否可以接收多个客户端的请求呢。实例如下:
    image.png
    可以发现无法处理第二个客户端的请求,思考后,这是因为accept只有一次,那么将accept放入到while中是否可以呢?

    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>#include <fcntl.h>#define BUFFER_LENGTH		1024int main() 
    {//openint sockfd = socket(AF_INET, SOCK_STREAM, 0); // iostruct sockaddr_in servaddr;memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 192.168.2.123servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0servaddr.sin_port = htons(9999);if (-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))){printf("bind failed: %s", strerror(errno));return -1;}listen(sockfd, 10); struct sockaddr_in clientaddr;socklen_t len = sizeof(clientaddr); while (1) { //slaveint clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);printf("sockfd:%d, clientfd:%d\n", sockfd, clientfd);char buffer[BUFFER_LENGTH] = {0};int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0);if (ret == 0){close(clientfd);break; }printf("ret: %d, buffer: %s\n", ret, buffer);send(clientfd, buffer, ret, 0);}// getchar();
    }
    

    代码运行结果:
    1702476120129.png
    可以发现只能发送一次,这是因为第二次发送的时候调用了accept,此时没有连接,会被阻塞住。那么为了在while中即调用accept,又可以调用recv,所以考虑使用多线程实现。代码如下所示。该段代码编译需要使用gcc -o xxx xxx.c -lpthread.

    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>#include <fcntl.h>
    #include <pthread.h>#define BUFFER_LENGTH		1024void *client_thread(void *arg) 
    {int clientfd = *(int*)arg;while (1) { //slavechar buffer[BUFFER_LENGTH] = {0};int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0);if (ret == 0) {close(clientfd);break; }printf("ret: %d, buffer: %s\n", ret, buffer);send(clientfd, buffer, ret, 0);}}int main() 
    {//openint sockfd = socket(AF_INET, SOCK_STREAM, 0); // iostruct sockaddr_in servaddr;memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 192.168.2.123servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0servaddr.sin_port = htons(9999);if (-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))){printf("bind failed: %s", strerror(errno));return -1;}listen(sockfd, 10); struct sockaddr_in clientaddr;socklen_t len = sizeof(clientaddr); while (1) { //slaveint clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);printf("sockfd:%d, clientfd:%d\n", sockfd, clientfd);pthread_t threadid;pthread_create(&threadid, NULL, client_thread, &clientfd);}// getchar();
    }
    

    代码运行结果:
    1702476550233.png
    从上图结果可以发现可以达到我们的预期目标。但是在客户端多的时候,比如有10000个客户端,无法创建10000个线程。那么如何处理这个问题。此时就需要用到本章的重点知识,网络io多路复用技术,对多个服务进行管理。如select, pool, epool,kqueue(mac)等。

第16.2节 select

16.2.1 介绍

网络IO复用是指在单线程或少数线程的情况下,通过一种机制同时监控多个IO流的状态,当某个IO流有数据到达时,就通知相应的线程进行处理。其中,select是一种比较常用的IO多路复用技术,它可以同时监控多个文件描述符,当某个文件描述符就绪(一般是读就绪或写就绪)时,就会通知应用程序进行相应的操作。

16.2.2 代码案例

  1. 代码

    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>#include <fcntl.h>
    #include <sys/select.h>#define BUFFER_LENGTH		1024int main() 
    {//openint sockfd = socket(AF_INET, SOCK_STREAM, 0); // iostruct sockaddr_in servaddr;memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 192.168.2.123servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0servaddr.sin_port = htons(9999);if (-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))){printf("bind failed: %s", strerror(errno));return -1;}listen(sockfd, 10); struct sockaddr_in clientaddr;socklen_t len = sizeof(clientaddr); fd_set rfds, rset;FD_ZERO(&rfds);FD_SET(sockfd, &rfds);int maxfd = sockfd;int clientfd = 0;while (1) { rset = rfds;int nready = select(maxfd+1, &rset, NULL, NULL, NULL);if (FD_ISSET(sockfd, &rset)) { clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);printf("accept: %d\n", clientfd);FD_SET(clientfd, &rfds);if (clientfd > maxfd) maxfd = clientfd; // 这是因为有回收机制,所以始终找最大值。if (-- nready == 0) continue;}int i = 0;for (i = sockfd + 1; i <= maxfd; i++) {if (FD_ISSET(i, &rset)){char buffer[BUFFER_LENGTH] = {0};int ret = recv(i, buffer, BUFFER_LENGTH, 0);if (ret == 0) {close(i);break; }printf("ret: %d, buffer: %s\n", ret, buffer);send(i, buffer, ret, 0);}}}// getchar();
    }
    

    运行结果
    1702477855762.png
    发现可以达到同样的目的。

  2. 代码说明

    1. select(maxfd, &rfds, &wfds, efds, timeout)
      maxfd - 表示的是所有的accept连接中返回值最大的id,
      rfds - 可读的集合,记录了可以读的io集合
      wfds - 可写的集合,记录了可以写的io集合
      efds - 出错的集合,记录了上次出错的io集合
      timeout - 表示多久轮询上面三个集合一次
      返回值 - 表示当前有多少个io连接

    2. fd_set rfds: fd_set内部是按照bit位来的

    3. FD_ZERO(&rfds): 是设置fd_set中的每一个bit位为0

    4. FD_SET(x, &rfds):将rfds中的dix位置为1

    5. FD_ISSET(socked, &rfds):表示查询rfds的第socked位是否为1

  3. 关于send是否可以写的问题,是应用将需要发送的数据放入到内核的sendbuffer中。通常而言都是可以send成功的,只有在循环send或sendbuffer()非常小的时候才会失败,这个配置可以在sysconfig文件中修改,所以send是否可以写是需要判断的。

16.2.3 缺陷

  1. 由于select的fd_set需要通过select先传入到内核,再从内核传出来,所以会很消耗性能。
  2. 在select中,一个fd_set是128个bit位,如果io的数量超过128个后,就会出现资源不够。
  3. 在内核中会循环遍历,这也会比较耗时。

第16.3节 poll

16.3.1 介绍

poll是一种常见的IO多路复用技术,它可以同时监视多个文件描述符,当其中任意一个文件描述符就绪时,就会通知应用程序进行相应的操作。

16.3.2 代码案例

  1. 代码

    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>#include <fcntl.h>
    #include <sys/poll.h>#define BUFFER_LENGTH		1024
    #define POLL_SIZE   1024
    int main() 
    {//openint sockfd = socket(AF_INET, SOCK_STREAM, 0); // iostruct sockaddr_in servaddr;memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 192.168.2.123servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0servaddr.sin_port = htons(9999);if (-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))){printf("bind failed: %s", strerror(errno));return -1;}listen(sockfd, 10); struct pollfd fds[POLL_SIZE] = {0};fds[sockfd].fd = sockfd;fds[sockfd].events = POLLIN; // 表示可读int maxfd = sockfd;int clientfd = 0;while (1) {int nready = poll(fds, maxfd + 1, -1);if (fds[sockfd].revents & POLLIN) {clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);printf("accept: %d\n", clientfd);fds[clientfd].fd = clientfd;fds[clientfd].events = POLLIN;if (clientfd > maxfd) maxfd = clientfd;if (-- nready == 0) continue;}int i = 0;for (i = 0;i <= maxfd; i ++) {if (fds[i].revents & POLLIN) {char buffer[BUFFER_LENGTH] = {0};int ret = recv(i, buffer, BUFFER_LENGTH, 0);if (ret == 0) {fds[i].fd = -1; // 需要将fd置为无效才行。fds[i].events = 0;close(i);break; }printf("ret: %d, buffer: %s\n", ret, buffer);send(i, buffer, ret, 0);}}}// getchar();
    }
    
  2. 代码说明

    结构中的events是我们传入到内核中的可读的项,而revents是从内核中反馈出来的。

    struct poolfd
    {int fd;short events;short revent;
    }
    

16.3.3 相对select的优缺点

  1. 与select相比,poll没有最大文件描述符数量的限制,因此可以处理更多的并发连接。poll的使用方法与select类似,但是poll的效率比select更高,因为它不需要遍历整个文件描述符集合,而是只需要遍历就绪的文件描述符集合。
  2. pool只有一个数组
  3. 接口简单只有一个

第16.4节 epoll

16.4.1 介绍

epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。epoll可以同时处理大量的文件描述符,是基于事件驱动的IO操作方式,可以取代select和poll函数。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。另外,epoll使用红黑树存储管理事件,每次插入和删除事件的效率都是O(logn)的,其中n是红黑树中节点的个数。

16.4.2 代码案例

  1. 代码
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>#include <fcntl.h>
    #include <sys/epoll.h>#define BUFFER_LENGTH		1024int main() 
    {//openint sockfd = socket(AF_INET, SOCK_STREAM, 0); // iostruct sockaddr_in servaddr;memset(&servaddr, 0, sizeof(struct sockaddr_in)); // 192.168.2.123servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0servaddr.sin_port = htons(9999);if (-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))){printf("bind failed: %s", strerror(errno));return -1;}listen(sockfd, 10); struct sockaddr_in clientaddr;socklen_t len = sizeof(clientaddr);int epfd = epoll_create(1);//1000  //liststruct epoll_event ev;ev.events = EPOLLIN;ev.data.fd = sockfd;epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev); //struct epoll_event events[1024] = {0};while (1){  // mainloopint nready = epoll_wait(epfd, events, 1024, -1); //-1, 0, if (nready < 0) continue;int i = 0;for (i = 0;i < nready;i ++) {int connfd = events[i].data.fd;if (sockfd == connfd) { // acceptint clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);if (clientfd <= 0) {continue;}printf(" clientfd: %d\n", clientfd);ev.events = EPOLLIN | EPOLLET;ev.data.fd = clientfd;epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);}else if (events[i].events & EPOLLIN) {char buffer[10] = {0};short len = 0;recv(connfd, &len, 2, 0);len = ntohs(len);int n = recv(connfd, buffer, 10, 0);if (n > 0) {printf("recv : %s\n", buffer);send(connfd, buffer, n, 0);} else if (n == 0) {printf("close\n");epoll_ctl(epfd, EPOLL_CTL_DEL, connfd, NULL);close(connfd);}}}}// getchar();
    }
    
    运行结果
    1702480477212.png

16.4.3 相对select,pool的优点

  1. 可以处理大量请求
  2. 底层使用红黑树实现,效率更高

补充:

  1. io的数量意味着什么?意味着并发

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

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

相关文章

考试的最大困扰度

说在前面 &#x1f388;不知道大家对于算法的学习是一个怎样的心态呢&#xff1f;为了面试还是因为兴趣&#xff1f;不管是出于什么原因&#xff0c;算法学习需要持续保持。 1、题目描述 一位老师正在出一场由 n 道判断题构成的考试&#xff0c;每道题的答案为 true &#xff…

Hexo部署到云服务器后CSS样式无效的问题

Hexo部署到云服务器后CSS样式无效的问题 01 前言 趁活动入手了一个云服务器&#xff08;Linux&#xff09;&#xff0c;打算简单挂个博客上去&#xff0c;因为之前部署到github有了一些经验&#xff0c;所以还是选择使用Hexo。中间步骤略&#xff0c;部署完使用浏览器访问的时…

HT4125 低压CMOS 缓冲门器件 单电源电压转换

​​亿胜盈科HT4125 是一款低压CMOS 缓冲门器件&#xff0c;可运行在针对便携式和电池设备的更宽电压范围内。 其采用了较低阀值电路来设计此输入&#xff0c;以便匹配Vcc 3.3V 时的1.8V 输入逻辑&#xff0c;并且可被用 在1.8V 至3.3V 电平上行转换器功能中。此外&#xff0c;…

RabbitMQ的详细使用

消息队列RabbitMQ的详细使用 文章目录 消息队列RabbitMQ的详细使用MQ 的相关概念什么是MQ为什么要用MQMQ 的分类MQ 的选择 RabbitMQRabbitMQ 的概念四大核心概念各个名词介绍安装RabbitMQWeb管理界面及授权操作Docker 安装 Hello world简单示例 Work Queues轮训分发消息消息应答…

用友时空 KSOA 多处SQL注入漏洞复现

0x01 产品简介 用友时空 KSOA 是建立在 SOA 理念指导下研发的新一代产品,是根据流通企业前沿的 IT 需求推出的统一的IT基础架构,它可以让流通企业各个时期建立的 IT 系统之间彼此轻松对话。 0x02 漏洞概述 用友时空 KSOA 系统 PayBill、QueryService、linkadd.jsp等接口处…

如何在Android中旋转屏幕时避免重新绘制Activity

如何在Android中旋转屏幕时避免重新绘制Activity 在Android开发中&#xff0c;设备旋转通常导致当前活动&#xff08;Activity&#xff09;被销毁并重新创建&#xff0c;这可能导致用户界面重置和不必要的资源重新加载。然而&#xff0c;有时我们希望避免这种行为&#xff0c;…

GBDT介绍

GBDT介绍 GBDT&#xff08;Gradient Boosting Decision Tree&#xff09;&#xff0c;即梯度提升决策树&#xff0c;是一种常用的机器学习算法&#xff0c;属于集成学习方法中的Boosting类算法。GBDT主要用于回归和分类问题&#xff0c;通过结合多个决策树来构建一个更为强大的…

Leetcode69 x的平方根

x的平方根 题解1 袖珍计算器算法题解2 二分查找题解3 牛顿迭代 给你一个非负整数 x &#xff0c;计算并返回 x 的 算术平方根 。 由于返回类型是整数&#xff0c;结果只保留 整数部分 &#xff0c;小数部分将被 舍去 。 注意&#xff1a;不允许使用任何内置指数函数和算符&…

配置文件中的$和@

配置文件中的$和 0、前言 借鉴文章&#xff1a; https://blog.csdn.net/Saintmm/article/details/124603343 https://blog.csdn.net/ster_ben/article/details/119295815在yml配置文件中&#xff0c;可以使用${}和{}来引用其他配置项的值作为配置项的值。 spring:applicati…

复旦微用AXIDMA接收原始图像

参考SD卡移植博客&#xff0c;&#xff0c;移植SD卡相应代码 AXIDMA部分Demo下的bsp包整个pl搬到相应位置&#xff0c;添加相应文件 #include <stdio.h> #include <stdlib.h> #include "platform.h" #include "fmsh_common.h" #include "…

CentOS 7 部署frp穿透内网

本文将介绍如何在CentOS 7.9上部署frp&#xff0c;并通过示例展示如何配置和测试内网穿透。 文章目录 &#xff08;1&#xff09;引言&#xff08;2&#xff09;准备工作&#xff08;4&#xff09;frps服务器端配置&#xff08;5&#xff09;frpc客户端配置&#xff08;6&#…

【Linux】进程和环境变量

我们启动一个软件&#xff0c;本质就是启动一个进程 在Linux下&#xff0c;运行一条命令&#xff0c;运行的时候&#xff0c;其实就是在系统层面创建了一个进程 而Linux系统管理大量进程则是先描述&#xff0c;再组织 进程 对应的代码和数据 进程等对应的PCB结构体 PCB包含了…

深度优先搜索LeetCode979. 在二叉树中分配硬币

给你一个有 n 个结点的二叉树的根结点 root &#xff0c;其中树中每个结点 node 都对应有 node.val 枚硬币。整棵树上一共有 n 枚硬币。 在一次移动中&#xff0c;我们可以选择两个相邻的结点&#xff0c;然后将一枚硬币从其中一个结点移动到另一个结点。移动可以是从父结点到…

postman接口测试系列: 时间戳和加密

在使用postman进行接口测试的时候&#xff0c;对于有些接口字段需要时间戳加密&#xff0c;这个时候我们就遇到2个问题&#xff0c;其一是接口中的时间戳如何得到&#xff1f;其二就是对于现在常用的md5加密操作如何在postman中使用代码实现呢&#xff1f; 下面我们以一个具体的…

【ZeroMQ(ZMQ)】高速并发通信框架学习笔记(C风格、C++风格都有哦)

目录 官方文档&#xff1a; ZeroMQ An open-source universal messaging library 前言 ☘️ ZeroMQ——基于消息队列模式的Socket库 框架提供的套接字可以满足在多种协议之间传输原子信息&#xff0c;如线程间、进程间、TCP、广播等。 ZMQ将消息通信分成 4 种模型&#xff0c…

JavaEE之多线程编程:2.创建线程及Thread类常见方法(超全!!!)

一、创建线程 Java中创建线程的写法有很多种&#xff01;&#xff01;&#xff01;这里介绍其中5种。 方法1&#xff1a;继承Thread类&#xff0c;重写run 创建一个类&#xff0c;让这个类继承自Thread父类&#xff0c;再重写我们的run方法就可以了。 使用Thread类&#xff…

MYsql第三次作业

目录 问题&#xff1a; 解答 1.查询student表的所有记录 2.查询student表的第2条到4条记录 3.从student表查询所有学生的学号&#xff08;id&#xff09;、姓名&#xff08;name&#xff09;和院系&#xff08;department&#xff09;的信息 4.从student表中查询计算机系和…

智能优化算法应用:基于鸽群算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于鸽群算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于鸽群算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.鸽群算法4.实验参数设定5.算法结果6.参考文献7.MA…

EdgeYOLO: anchor-free,边缘部署友好

简体中文 1 Intro 2 Updates 3 Coming Soon 4 Models 5 Quick Start \quad 5.1 setup

物奇平台MIC配置与音频通路关系

物奇平台MIC配置与音频通路关系 是否需要申请加入数字音频系统研究开发交流答疑群(课题组)&#xff1f;可加我微信hezkz17, 本群提供音频技术答疑服务&#xff0c;群赠送语音信号处理降噪算法&#xff0c;蓝牙耳机音频&#xff0c;DSP音频项目核心开发资料, 1 255代表无效&am…