第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.rhkb.cn/news/216497.html

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

相关文章

C语言指针基础题(二)

目录 例题一题目解析及答案 例题二题目解析及答案 例题三题目解析及答案 例题四题目解析及答案 例题五题目解析及答案 感谢各位大佬对我的支持,如果我的文章对你有用,欢迎点击以下链接 &#x1f412;&#x1f412;&#x1f412; 个人主页 &#x1f978;&#x1f978;&#x1f…

Tomcat头上有个叉叉

问题原因&#xff1a; 这是因为它就是个空的tomcat,并没有导入项目运行 解决方案&#xff1a; war模式&#xff1a;发布模式&#xff0c;正式发布时用&#xff0c;将WEB工程以war包的形式上传到服务器 war exploded模式&#xff1a;开发时用&#xff0c;将WEB工程的文件夹直接…

虚拟现实三维电子沙盘数字沙盘开发教程第5课

虚拟现实三维电子沙盘数字沙盘无人机倾斜摄影全景建模开发教程第5课 设置system.ini 如下内容 Server122.112.229.220 userGisTest Passwordchinamtouch.com 该数据库中只提供 成都市火车南站附近的数据请注意&#xff0c;104.0648,30.61658 在鼠标指定的位置增加自己的UI对象&…

Qt 5.15.2 三维显示功能

Qt 5.15.2 三维显示功能 三维显示效果&#xff1a; .pro项目文件 QT core gui opengl 3dcore 3drender 3dinput 3dextrasgreaterThan(QT_MAJOR_VERSION, 4): QT widgetsCONFIG c17# You can make your code fail to compile if it uses deprecated APIs. # In ord…

虚幻学习笔记10—C++函数与蓝图的通信

一、前言 除了上一章C变量与蓝图通信讲的变量能与蓝图通信外&#xff0c;还有函数和枚举也可以和蓝图通信。函数的关键字为”UFUNCTION“、枚举的关键字为”UENUM“。 二、实现 2.1、BlueprintCallable蓝图中调用 该函数时带执行的&#xff0c;带入如下。编译成功后在蓝图中输…

Android 11 适配——整理总结篇

背景 > 经过检测&#xff0c;我们识别到您的应用&#xff0c;目前未适配安卓11&#xff08;API30&#xff09;&#xff0c;请您关注适配截止时间&#xff0c;尽快开展适配工作&#xff0c;避免影响应用正常发布和经营。 > targetSdkVersion30 升级适配工作参考文档&am…

【算法】递归、搜索与回溯算法

文章目录 一. 名词解释1. 递归1.1 什么是递归&#xff1f;1.2 为什么会用到递归&#xff1f;1.3 如何理解递归&#xff1f;1.4 如何写好一个递归&#xff1f; 2. 遍历和搜索3. 回溯和剪枝 二. 递归系列专题1. 汉诺塔问题2. 合并两个有序链表3. 反转链表4. 两两交换链表中的节点…

git自动更新功能

确认权限 因为一般Linux系统网页用的www 或 www-data用户和用户组&#xff0c;所以要实现自动来去&#xff0c;首先要在www用户权限下生成ssh密钥&#xff0c;不然没有权限&#xff0c;其次就是&#xff0c;要把用root用户拉去的代码&#xff0c;批量改成www用户 1. 给www权…

Unity | 渡鸦避难所-2 | 搭建场景并添加碰撞器

1 规范项目结构 上期中在导入一系列的商店资源包后&#xff0c;Assets 目录已经变的混乱不堪 开发过程中&#xff0c;随着资源不断更新&#xff0c;遵循一定的项目结构和设计规范是非常必要的。这可以增加项目的可读性、维护性、扩展性以及提高团队协作效率 这里先做下简单的…

最新鸿蒙HarmonyOS4.0开发登陆的界面1

下载deveco-studio 说明一下&#xff0c;本人只是学习中&#xff0c;现在只是拿着vue及uniapp的经验在一点一点的折腾&#xff0c;不过现在看来&#xff0c;鸿蒙入门并不是很难。也许是自己没有深入下去。 https://developer.harmonyos.com/cn/develop/deveco-studio#download…

Ubuntu 20.04 安装 mysql8 LTS

Ubuntu 20.04 安装 mysql8 LTS sudo apt-get update sudo apt-get install mysql-server -y mysql --version mysql Ver 8.0.35-0ubuntu0.20.04.1 for Linux on x86_64 ((Ubuntu)) Ubuntu20.04 是自带了 MySQL8. 几版本的&#xff0c;低于 20.04 则默认安装是 MySQL5.7.33…

基于Java8构建Docke镜像

基于Java8构建Docke镜像 搜索java8安装包 docker search java8 --no-trunc &#xff0c; --no-trunc展开描述信息 选择拉取 docker pull docker.io/mykro/java8-jre&#xff0c;为了减少磁盘占用&#xff0c;选择jre版本基础镜像 在宿主机创建文件夹iot&#xff0c;并把所需…

IDEA中,光标移动快捷键(Shift + 滚轮前后滚动:当前文件的横向滚动轴滚动。)

除此之外&#xff0c;其他常用的光标移动快捷键包括&#xff1a; Shift 滚轮前后滚动&#xff1a;当前文件的横向滚动轴滚动。Shiftenter&#xff1a;快速将鼠标移动到下一行。Ctrl ]&#xff1a;移动光标到当前所在代码的花括号结束位置。Ctrl 左方向键&#xff1a;光标跳转…

VisualSVN Server的安装全过程

目录 背景: 安装过程&#xff1a; 步骤1&#xff1a; 步骤2&#xff1a; 步骤3&#xff1a; 步骤4&#xff1a; 步骤5&#xff1a; 安装出现的bug&#xff1a; 问题: 解决办法: 总结: 背景: VisualSVN Server 是一款免费的 SVN (Subversion) 服务器软件&#xff0c…

大数据技术8:StarRocks极速全场景MPP数据库

前言&#xff1a;StarRocks原名DorisDB&#xff0c;是新一代极速全场景MPP数据库。StarRocks 是 Apache Doris 的 Fork 版本。StarRocks 连接的多种源。一是通过这个 CDC 或者说通过这个 ETL 的方式去灌到这个 StarRocks 里面&#xff1b;二是还可以去直接的和这些老的 kafka 或…

【报错栏】(vue)Module not found: Error: Can‘t resolve ‘element-ui‘ in xxx

Module not found: Error: Cant resolve element-ui in xxx 报错原因是&#xff1a; 未安装 element-ui 依赖 解决&#xff1a; npm install element-ui 运行

h2-database 安装部署学习

1&#xff0c;下载jar 包 Archive Downloads 进入到下载的包的位置&#xff1a; cd E:\IDE\Java\jre\lib 2&#xff0c;参考以下说明进行数据库创建&#xff1a; Tutorial 执行如下 可以进行创建默认的数据库 设置用户密码 E:\IDE\Java\jre\lib> java -cp h2-2.2.224.…

体系化学习运筹学基础算法的实践和总结

文章目录 引言目标设计目标实践文章汇总经验总结一则预告 引言 眨眼间已经12月了&#xff0c;眼看着2023年马上要过完了。 女朋友最近总说&#xff0c;工作以后感觉时间过的好快。事实上&#xff0c;我也是这么认为的。年纪越大&#xff0c;越会担心35岁危机的降临。所以&…

波奇学Linux:环境变量,本地变量和内建命令

Windows下的环境变量 echo $PATH 查看指令搜索命令路径 在bash命令行输入的指令&#xff0c;系统根据PATH中的路径查询。 增加PATH指令 $PATH等于上面的路径 :表示不同路径分割符 /home/boki/lesson13代表新的路径 相当于一个赋值语句。 相当于指令&#xff0c;可以直接使用…

K8s中pod詳解

目录 Yaml语法解析 Pod pod是如何被创建的 1.创建一个pod 2.创建一个多容器pod 进入容器 3.配置节点标签 4.Pod容器的交互 4.1创建pod&#xff0c;并做本地解析 4.2pod共享进程 4.3pod共享宿主机namespace 5.钩子函数lifecycle 基础指令 # 查看对应资源: 状态 $ kubectl…