【计算机网络】select/poll

多路转接 - select/poll

  • 一、I/O 多路转接之 select
    • 1. select 接口
    • 2. select 的使用
    • 3. select 的优缺点
  • 二、I/O 多路转接之 poll
    • 1. poll 接口
    • 2. poll 的使用
    • 3. poll 与 select 的对比

一、I/O 多路转接之 select

多路转接属于 IO 复用方式的一种。系统提供 select() 函数来实现多路复用输入/输出模型。select 系统调用是用来让我们的程序监视多个文件描述符的状态变化的。程序会停在 select 这里等待,直到被监视的文件描述符有一个或多个发生了状态改变。

1. select 接口

select 只负责等待,而且一次可以等待多个文件描述符。其中接口如下:

在这里插入图片描述

其中第一个参数 nfds 表示 select 等待的多个文件描述符的最大值+1,例如需要 select 等待的 fd 有 1、2、3、4、5,那么 nfds 这个参数就是 6.

返回值如果大于0,代表有 n 个 fd 就绪了;如果返回值等于 0,代表超时,表示没有错误,也没有 fd 就绪;如果小于 0,表示等待出错。

关于最后一个参数 struct timeval,我们需要另外介绍一下,在 Linux 中有对应的接口可以让我们获取时间,例如 gettimeofday() 可以获取特定时区下的特定时间,如下:

在这里插入图片描述

其中它的参数中也有 struct timeval 结构,该结构中的字段如下:

在这里插入图片描述

其中 tv_sec 表示时间戳,以秒为单位;tv_usec 以微秒为单位。

所以回到 select 接口中,最后一个参数 struct timeval 表示给 select 设置等待方式,例如设为 struct timeval timeout = {5, 0} 表示每隔 5 秒,timeout 一次,也就是在这 5 秒期间,没有任何一个文件描述符就绪,select 就会直接返回,然后再重新进入,设置 5 秒的时候,就重复刚才的工作。如果在等待 5 秒期间有文件描述符就绪了,那么就会立即返回。如果我们设为 {0, 0} 代表立马返回,非阻塞的一种。-1 表示阻塞等待。

另外,如果我们设置了,这个参数是一个输入输出型参数。例如我们设置每隔 5 秒 timeout 一次,可是刚过去 2 秒就有文件描述符就绪了,此时 timeout 输出时就变成了 3 秒。

最后,第二、三、四个参数都是同一个类型 fd_setfd_set 是内核提供的一种数据类型,它是位图。我们目前关心的 fd 上面的读写事件,要么特定的 fd 上读事件就绪,要么特定的 fd 上写事件就绪,要么特定的 fd 上有异常事件。所以对于任何一个文件描述符,如果只准它关心一种事件,那么就是这三种的其中一种。所以如果我们关心特定一个 fd 上读事件就绪,就让 select 来通知我们,我们就应该把文件描述符设置进第二个参数中。如果我们关心写事件就绪,就把文件描述符设置进第三个参数中。如果我们既关心读又关心写,我们可以同时设置进第二和第三个参数中。

下面我们单独拿第二个参数 readfds 来讲,这个集合也是输入输出型参数。当它是输入时,表示的是,用户告诉内核,我给你的一个或者多个 fd,你要帮我关心 fd 上面的读事件,如果事件就绪了,你就要告诉我!当它是输出时,也就是返回时,内核告诉用户,你让我关心的多个 fd 中,有哪些已经就绪了,你赶紧读取吧!其中这个位图传入的时候,比特位的位置,就表示文件描述符编号,比特位的内容,0 或者 1,就表示是否需要内核关心! 当有 fd 就绪时,操作系统就直接修改该位图中的内容,将已经就绪的 fd 在该位图的位置不变,也就是还是 1,将没有就绪的位置清0,也就是返回输出的时候,0 还是 1,表示哪些用户关心的 fd 上面的读事件已经就绪了!所以 fd_set 是一张位图,是为了让用户和内核传递 fd 是否就绪的信息的!

所以这就注定了使用 select 的时候,一定会有大量的位图操作,所以操作系统给我们提供了一系列的位图操作接口,如下:

在这里插入图片描述

  • FD_CLR:用来清除集合 set 中相关 fd 的位,比如 fd = 3,就是将 set 中的编号为 3 的位置由 1 改为 0 即可
  • FD_ISSET:用来测试集合 set 中相关 fd 的位是否为真,即判断是否在集合中
  • FD_SET:用来设置集合 set 中相关 fd 的位,也就是将 fd 添加到集合中
  • FD_ZERO:用来清除集合 set 的全部位,也就是全部清零

最后我们知道,fd_set 是一个位图,并且是一个具体的类型,所以 fd_set 就一定有具体的大小,只要有实际的大小,那么 fd_set 就一定有它位图中比特位的个数,也就是说 select 等待多个文件描述符一定是有上限的!下面我们验证一下 select 最多可以等待多少个文件描述符,如下代码:

				int main(){std::cout << "fd_set bits num: " << sizeof(fd_set) * 8 << std::endl;return 0;}

结果如下:

在这里插入图片描述

所以在我们当前机器的 select 能够等待的文件描述符个数是 1024 个。

2. select 的使用

下面我们写一段简单的代码使用 select 完成多个文件描述符的等待,详细解析参考代码注释,代码如下:

  • 封装的 socket 套接字 Socket.hpp:

      		#pragma once#include <iostream>#include <string>#include <cstring>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <sys/socket.h>#include <arpa/inet.h>#include <netinet/in.h>#include "log.hpp"enum{SocketErr = 2,BindErr, ListenErr,};const int backlog = 10;class Sock {public:Sock(){}~Sock(){}public:void Socket(){_sockfd = socket(AF_INET, SOCK_STREAM, 0);if(_sockfd < 0){lg(Fatal, "socket error, %s: %d", strerror(errno), errno);exit(SocketErr);}int opt = 1;setsockopt(_sockfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));}void Bind(uint16_t port){sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_addr.s_addr = INADDR_ANY;local.sin_family = AF_INET;local.sin_port = htons(port);if(bind(_sockfd, (const sockaddr*)&local, sizeof(local)) < 0){lg(Fatal, "bind error, %s: %d", strerror(errno), errno);exit(BindErr);}}void Listen(){if(listen(_sockfd, backlog) < 0){lg(Fatal, "listen error, %s: %d", strerror(errno), errno);exit(ListenErr);}}int Accept(std::string* client_ip, uint16_t* client_port){sockaddr_in peer;socklen_t len = sizeof(peer);int newfd = accept(_sockfd, (sockaddr*)&peer, &len);if(newfd < 0){lg(Warning, "accept error, %s: %d", strerror(errno), errno);return -1;}char buffer[64];inet_ntop(AF_INET, &peer.sin_addr, buffer, sizeof(buffer));*client_ip = buffer;*client_port = ntohs(peer.sin_port);return newfd;}void Close(){close(_sockfd);}bool Connect(std::string serverip, uint16_t serverport){sockaddr_in peer;memset(&peer, 0, sizeof(peer));inet_pton(AF_INET, serverip.c_str(), &(peer.sin_addr));peer.sin_family = AF_INET;peer.sin_port = htons(serverport);int n = connect(_sockfd, (const sockaddr*)&peer, sizeof(peer));if(n < 0){lg(Fatal, "connect error, %s: %d", strerror(errno), errno);return false;}return true;}int GetFd(){return _sockfd;}private:int _sockfd;};
    
  • 对 select 封装的 SelectServer.hpp:

      	#pragma once#include <iostream>#include <string>#include <sys/select.h>#include <sys/time.h>#include "Socket.hpp"#include "log.hpp"static const uint16_t defaultport = 8888;static const int fd_set_max = (sizeof(fd_set) * 8);int default_fd = -1;class SelectServer{public:SelectServer(uint16_t port = defaultport): _port(port){for (int i = 0; i < fd_set_max; ++i){fd_array[i] = default_fd;}}bool Init(){_listenSock.Socket();_listenSock.Bind(8888);_listenSock.Listen();return true;}void Start(){int listenSock = _listenSock.GetFd();fd_array[0] = listenSock;while (true){fd_set rfds;FD_ZERO(&rfds); // 清空集合int maxfd = fd_array[0];for (int i = 0; i < fd_set_max; ++i){if (fd_array[i] == default_fd)continue;FD_SET(fd_array[i], &rfds); // 向集合添加指定fd// 更新最大的 fdif (maxfd < fd_array[i]){maxfd = fd_array[i];lg(Info, "max fd update, max fd is: %d", maxfd);}}struct timeval timeout = {2, 0}; // 输入输出,可能要进行周期重复设置// select 告诉我们就绪了,接下来的一次读取,我们读取 fd 的时候,不会被阻塞// rfds 是输入输出型参数,所以在输入时可能是 1111,返回时可能只有一个fd就绪,那么就被覆盖成 0001// 所以 rfds 原来的位图中的值就不见了,也就是需要内核关心的fd不见了!// 所以就要求 select 每次返回处理完之后,回到循环开始,每一次都要把 rfds 的参数重新设置!int n = select(maxfd + 1, &rfds, nullptr, nullptr, &timeout);switch (n){case 0:// std::cout << "time out, timeout: " << timeout.tv_sec << "." << timeout.tv_usec << std::endl;break;case -1:std::cerr << "select error" << std::endl;break;default:// 有事件就绪了,交给事件派发器 Dispatcherstd::cout << "get a new link!" << std::endl;Dispatcher(rfds);  break;}}}~SelectServer(){_listenSock.Close();}private:Sock _listenSock;uint16_t _port;// 辅助数组,为了将合法的文件描述符添加到 rfds 中int fd_array[fd_set_max];};
    
  • 事件派发器 Dispatcher()

      	    void Dispatcher(fd_set &rfds){	for (int i = 0; i < fd_set_max; ++i){int fd = fd_array[i];if (fd == default_fd)continue;// 如果当前 fd 在 rfds 中已经就绪if (FD_ISSET(fd, &rfds)){// 处理 listen 套接字if (fd == _listenSock.GetFd()){// 连接管理器Accepter();}// 其他文件描述符就绪, 也就是读事件就绪else{Recver(fd, i);}}}}
    
  • 连接管理器 Accepter()

      	    void Accepter(){// 连接事件就绪std::string clientip;uint16_t clientport = 0;int sock = _listenSock.Accept(&clientip, &clientport); // 这里不会阻塞,因为事件已经就绪if (sock < 0) return;lg(Info, "accept success, %s: %d", clientip.c_str(), clientport);// 将已经就绪的 sock 添加到辅助数组中即可,当 select 下一次设置的时候就会将该 fd 设置到 rfds 中!int pos = 1;for (; pos < fd_set_max; ++pos){if (fd_array[pos] != default_fd)continue;elsebreak;}if (pos == fd_set_max){lg(Warning, "server is full, close %d now!", sock);close(sock);}else{fd_array[pos] = sock;}}
    
  • 读事件处理器 Recver()

      	    void Recver(int fd, int pos){char buffer[1024];ssize_t n = read(fd, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = 0;std::cout << "get a message: " << buffer << std::endl;}else if (n == 0){lg(Info, "client quit, me too, close fd is: %d", fd);close(fd);fd_array[pos] = default_fd; // 本质从 rfds 中移除}else{lg(Warning, "recv error, fd is: %d", fd);close(fd);fd_array[pos] = default_fd; // 本质从 rfds 中移除}}
    

3. select 的优缺点

  1. 优点
  • select 已经是一种多路转接的方案了,在单进程的同时也能多用户的请求。select 一次可以等待多个文件描述符,IO 等于等待+拷贝,所以 select 可以知道多个文件描述符上的 IO 事件是否就绪,也就是把所有的等待时间重叠起来。这样如果有任何一个事件就绪,我们就可以知道这个事件就绪,然后把事件派发上来,让上层进行处理,要么是获取新连接,要么是读写数据。
  1. 缺点
  • select 能够等待的 fd 是有上限的
  • 输入输出型参数比较多,数据拷贝的频率比较高
  • 输入输出型参数比较多,每次都要对关心的 fd 进行事件重置,也就是需要大量的循环
  • 用户层使用第三方数组管理用户的 fd,用户层需要很多次遍历;内核中检测 fd 事件就绪,也要遍历

二、I/O 多路转接之 poll

poll 也是多路转接方案的一种,它主要解决的就是 select 中的等待 fd 有上限的问题,以及每次都要对关心的 fd 进行事件重置的问题。

1. poll 接口

下面我们看看 poll 的接口:

在这里插入图片描述

首先 poll 的返回值和 select 的返回值一模一样。

第三个参数 timeout 其实是一个整型,表示的是时间,单位为毫秒,含义和 select 中的 timeout 一样。

而我们发现,poll 的第一个参数,专门设计了一个结构体 struct pollfd,其实我们可以理解成这个结构体是一个数组,而第一个参数就表示第一个元素的地址。

第二个参数 nfds 代表第一个参数的数组中有多少个元素。

在这里插入图片描述

我们知道,多路转接无非包括两点,第一,用户告诉内核;第二,内核告诉用户。pollselect 一样,只不过 select 用位图,而 poll 用结构体数组。所以 poll 在用户传给内核的时候,表示告诉内核需要关心 struct pollfd 结构体中的 fd 中的 events 事件;当返回时,代表 struct pollfd 结构体中的 fd 中的 revents 事件就绪了。所以,poll 最大的特点是将输入和输出事件进行了分离!

但是当我们告诉内核需要关心 events 事件的时候,内核怎么知道是关心读事件还是写事件还是其他事件呢?当内核返回用户也一样。那么我们可以看到 eventsrevents 都是 short 类型,都是 16 个比特位,也就是在 Linux 中,使用了比特位传参!所以它把事件设置成位图的形式,如下,其实它们都是宏:

在这里插入图片描述

所以,poll 的本质是将读写事件分离,然后传入用户定的数组元素的大小,通过 eventsrevents 以位图的方式来传递就绪和关心标记位的解决方案!

2. poll 的使用

下面我们直接对 selectSever.hpp 做修改,改成一个 pollSever.hpp,代码如下:

			#pragma once#include <iostream>#include <string>#include <sys/time.h>#include <poll.h>#include "Socket.hpp"#include "log.hpp"static const uint16_t defaultport = 8888;static const int fd_num_max = 64;static const int default_fd = -1;static const int non_event = 0;class PollServer{public:PollServer(uint16_t port = defaultport): _port(port){for (int i = 0; i < fd_num_max; ++i){_event_fds[i].fd = default_fd;_event_fds[i].events = non_event;_event_fds[i].revents = non_event;   }}bool Init(){_listenSock.Socket();_listenSock.Bind(8888);_listenSock.Listen();return true;}void Accepter(){// 连接事件就绪std::string clientip;uint16_t clientport = 0;int sock = _listenSock.Accept(&clientip, &clientport); // 这里不会阻塞,因为事件已经就绪if (sock < 0) return;lg(Info, "accept success, %s: %d", clientip.c_str(), clientport);// 将已经就绪的 sock 添加到 _event_fds 中// 并将它的 events 设置为读事件 POLLINint pos = 1;for (; pos < fd_num_max; ++pos){if (_event_fds[pos].fd != default_fd)continue;elsebreak;}if (pos == fd_num_max){lg(Warning, "server is full, close %d now!", sock);close(sock);// 可以选择扩容...}else{_event_fds[pos].fd = sock;_event_fds[pos].events = POLLIN;}}void Recver(int fd, int pos){char buffer[1024];ssize_t n = read(fd, buffer, sizeof(buffer) - 1);if (n > 0){buffer[n] = 0;std::cout << "get a message: " << buffer << std::endl;}else if (n == 0){lg(Info, "client quit, me too, close fd is: %d", fd);close(fd);_event_fds[pos].fd = default_fd; // 本质从 结构体数组 中移除}else{lg(Warning, "recv error, fd is: %d", fd);close(fd);_event_fds[pos].fd = default_fd; // 本质从 结构体数组 中移除}}void Dispatcher(){for (int i = 0; i < fd_num_max; ++i){int fd = _event_fds[i].fd;if (fd == default_fd)continue;// 如果当前 fd 在 _event_fds 中已经就绪if (_event_fds[i].revents & POLLIN){// 处理 listen 套接字if (fd == _listenSock.GetFd()){// 连接管理器Accepter();}// 其他文件描述符就绪, 也就是读事件就绪else{Recver(fd, i);}}}}void Start(){_event_fds[0].fd = _listenSock.GetFd();_event_fds[0].events = POLLIN;      // listen 套接字只关心获取连接,即读事件int timeout = 2000; // 2swhile (true){int n = poll(_event_fds, fd_num_max, timeout);switch (n){case 0:std::cout << "time out..." << std::endl;break;case -1:std::cerr << "poll error" << std::endl;break;default:// 有事件就绪了,交给事件派发器 Dispatcherstd::cout << "get a new link!" << std::endl;Dispatcher( );  break;}}}~PollServer(){_listenSock.Close();}private:Sock _listenSock;uint16_t _port;struct pollfd _event_fds[fd_num_max];};

3. poll 与 select 的对比

  • 那么我们通过 poll 的使用可以看到,poll 本质上也是通过一个结构体数组来等待 fd 的,我们在开始的时候说过,它解决了 select 等待 fd 有上限的问题,那么它怎么解决了 fd 有上限的问题呢?其实我们在写的时候也发现,_event_fds 这个数组的大小是由我们自己定的,所以我们可以定的非常大,大到内存扛不住,所以此时就是操作系统的问题了,不是 poll 接口本身的问题。而 select 等待 fd 有上限的问题,本质上是接口本身的问题,所以 poll 本质上是解决了 select 等待 fd 有上限的问题。
  • pollselect 都需要遍历检测有哪些文件描述符就绪,其中 poll 在内核中需要遍历检测有哪些文件描述符就绪;在用户层需要遍历检测有哪些事件已经就绪。

所以 pollselect 都避免不开遍历的问题,也就是在效率上没有本质的提升。于是又出现了另一个接口 epoll,我们下一篇再介绍。

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

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

相关文章

R语言,数据类型转换

原文链接&#xff1a;R语言技能 | 不同数据类型的转换 本期教程 写在前面 今天是4月份的第一天&#xff0c;再过2天后再一次迎来清明小假期。木鸡大家是否正常放假呢&#xff1f; 我们在使用R语言做数据分析时&#xff0c;会一直对数据进行不同类型的转换&#xff0c;有时候…

百度网站收录提交入口

百度网站收录提交入口 在网站刚建立或者更新内容后&#xff0c;及时将网站提交给搜索引擎是提高网站曝光和获取流量的重要步骤之一。百度作为中国最大的搜索引擎之一&#xff0c;网站在百度中的收录情况尤为重要。下面介绍一下如何通过百度的网站收录提交入口提交网站。 1. 百…

代码随想录算法训练营第39天|62.不同路径 |63. 不同路径 II

代码随想录算法训练营第39天|62.不同路径 |63. 不同路径 II 详细布置 62.不同路径 本题大家掌握动态规划的方法就可以。 数论方法 有点非主流&#xff0c;很难想到。 https://programmercarl.com/0062.%E4%B8%8D%E5%90%8C%E8%B7%AF%E5%BE%84.html 视频讲解&#xff1a;https…

封装表格组件,最后一列动态生成 vue3子组件通过slot传值向父组件

将表格二次封装&#xff0c;方便以后开发中的复用。每次只需调用表格组件后&#xff0c;在父组件中往子组件标签上写入dataSource&#xff08;表格数据&#xff09;和columns&#xff08;表格列标题&#xff09;即可。 此案例中最后一列是删除按钮&#xff0c;动态生成&#xf…

Spire.PDF for .NET【文档操作】演示:合并 PDF 文件并添加页码

搜索了这么多有关 PDF 合并的信息后&#xff0c;很容易发现&#xff0c;无论您在线合并 PDF 文件还是使用 C#/VB.NET 来实现此任务&#xff0c;您都无法逃避对 PDF 文件安全等一些重要问题的担忧&#xff0c;因此需要花费多少时间或者合并后的文件是否支持打印页码等等。不过&a…

【Frida】【Android】 07_爬虫之网络通信库HttpURLConnection

&#x1f6eb; 系列文章导航 【Frida】【Android】01_手把手教你环境搭建 https://blog.csdn.net/kinghzking/article/details/136986950【Frida】【Android】02_JAVA层HOOK https://blog.csdn.net/kinghzking/article/details/137008446【Frida】【Android】03_RPC https://bl…

Word、Excel、PPT文件转PDF文件(C#)

一、添加依赖 为wpf项目引用Microsoft.Office.Interop.Excel、Microsoft.Office.Interop.PowerPoint、Microsoft.Office.Interop.Word、Office&#xff0c;依赖文件已经打到源代码包里了。 二、先定义一些命名空间 using Word Microsoft.Office.Interop.Word;using Excel M…

零基础入门转录组数据分析——DESeq2差异分析

零基础入门转录组数据分析——DESeq2差异分析 目录 零基础入门转录组数据分析——DESeq2差异分析1. 转录组分析基础知识2. DESeq2差异分析&#xff08;Rstudio&#xff09;3. 结语 1. 转录组分析基础知识 1.1 什么是转录组&#xff1f; 转录组&#xff08;transcriptome&#…

n1.线性表及其实现

1.引入—多项式表示 对于多项式&#xff0c;如何使用程序进行编写呢&#xff1f; 方法一&#xff1a;一维数组。下标对应未知数的指数&#xff0c;元素个体对应系数。缺点就是都得全部表示&#xff0c;系数为0项的存在浪费空间。例如&#xff0c;x只有一次方和2000次方&#…

MySQL、Oracle查看字节和字符长度个数的函数

目录 0. 总结1. MySQL1.1. 造数据1.2. 查看字符/字节个数 2. Oracle2.1. 造数据2.2. 查看字符/字节个数 0. 总结 databasecharacterbyteMySQLchar_length()length()Oraclelength()lengthB() 1. MySQL 1.1. 造数据 drop table if exists demo; create table demo (id …

手机一键换ip地址,解锁网络自由

在数字化时代&#xff0c;手机已经成为我们生活中不可或缺的一部分。随着移动互联网的快速发展&#xff0c;手机用户对于网络安全和隐私保护的需求也日益增强。其中&#xff0c;IP地址作为手机在网络中的标识&#xff0c;扮演着重要的角色。有时&#xff0c;出于隐私保护或网络…

WebSocket用户验证

在WebSocket中&#xff0c;如何携带用户的验证信息 一、在OnMessage中进行验证 客户端在连接到服务器后&#xff0c;客户端通过发送消息&#xff0c;服务器端在OnMessage方法中&#xff0c;进行信息验证&#xff0c;这种方式需要将用户身份验证及接收用户消息进行混合处理&am…

Python人工智能:推动气象科学研究与技术进步的新动力

Python是功能强大、免费、开源&#xff0c;实现面向对象的编程语言&#xff0c;在数据处理、科学计算、数学建模、数据挖掘和数据可视化方面具备优异的性能&#xff0c;这些优势使得Python在气象、海洋、地理、气候、水文和生态等地学领域的科研和工程项目中得到广泛应用。可以…

Oracle 数据库中的全文搜索

Oracle 数据库中的全文搜索 0. 引言1. 整体流程2. 创建索引2-1. 创建一个简单的表2-2. 创建文本索引2-3. 查看创建的基础表 3. 运行查询3-1. 运行文本查询3-2. CONTAINS 运算符3-3. 混合查询3-4. OR 查询3-5. 通配符3-6. 短语搜索3-7. 模糊搜索&#xff08;Fuzzy searches&…

2021-08-06

yarn的简介&#xff1a; Yarn是facebook发布的一款取代npm的包管理工具。 yarn的特点&#xff1a; 速度超快。 Yarn 缓存了每个下载过的包&#xff0c;所以再次使用时无需重复下载。 同时利用并行下载以最大化资源利用率&#xff0c;因此安装速度更快。超级安全。 在执行代码…

Redis性能瓶颈与安全隐患排查验证纪实

在写《Redis怎样保证数据安全&#xff1f;》这篇文章&#xff0c;我是有对redis设置密码需要哪些步骤&#xff0c;设置密码的性能损耗有验证的。这就涉及到要对redis的配置做修改。 开始时我是打算采用直接使用redis配置文件的方式。所以我从redis官网下载了一个默认的配置文件…

ROS 2边学边练(9)-- 用launch命令管理启动项

概念 我们在前面的ROS 2体验过程中&#xff0c;一直拿小海龟这个例子来练手&#xff0c;过程比较轻松&#xff0c;因为只有两个节点&#xff08;/turtlesim和/teleop_turtle&#xff09;&#xff0c;只需打开两个终端&#xff0c;ros2 run 节点就ok&#xff0c;但&#xff0c;现…

Datacom HCIP笔记-ISIS协议

IS中间系统&#xff08;路由器/运行了ISIS协议的设备&#xff09; ES终端系统(PC,PAD,print) 网络功能模型 ISO定义 事实标准 OSI TCP/IP 网络层(CLNP) (IS-IS) 网络…

EfficientVMamba实战:使用EfficientVMamba实现图像分类任务(一)

文章目录 摘要安装包安装timm 数据增强Cutout和MixupEMA项目结构编译安装Vim环境环境安装过程安装库文件 计算mean和std生成数据集 摘要 论文&#xff1a;https://arxiv.org/pdf/2401.09417v1.pdf 作者研究了轻量级模型设计的新方法&#xff0c;通过引入视觉状态空间模型&…