C语言编写Linux的Shell外壳

news2024/11/20 9:46:17

目录

一、输出命令行

1.1 了解环境变量

1.2 获取用户名、主机名、当前路径

1.3 缓冲区改进MakeCommandLine

二、获取用户命令

2.1 读取函数的选择

2.2 细节优化

2.3 返回值

三、指令和选项分割

3.1 strtok 函数

3.2 分割实现 

四、执行命令

4.1 fork 方法

4.2 进程等待

4.3 进程替换

4.4 程序编写

五、程序优化

5.1 执行次数

5.2 检测命令是否为内建命令

5.3 子进程执行失败

5.4 命令行路径更改

六、完整代码


在学习之前我们要认识到,Shell外壳中的命令行以及我们输入的指令都是字符串!

首先我们要创建两个文件:MyShell.cmakefile ,一个存储我们的 Shell 外壳,一个方便操作

其次,我们看一下 MyShell.c 中需要包含的头文件:

  1 #include <stdio.h>
  2 #include <unistd.h>     //进程创建接口
  3 #include <sys/types.h>  //进程等待
  4 #include <sys/wait.h>   //进程等待     

一、输出命令行


首先我们先来认识一下命令行,其中, Flash 是当前用户名、@ 后紧跟的是当前主机名、主机名空格后紧跟的是当前路径。所以由此我们知道,如果想打印出我们的命令行,我们至少要知道三个信息:1.用户名 2.主机名 3.当前路径

1.1 了解环境变量

这三个信息如何拿去呢?我们的操作系统中有自带的环境变量,在环境变量中存放着我们需要的三个信息。我们如何查我们的环境变量呢?

有三种方法:

1.命令行参数 -> [env] ,当我们在命令行输入指令,系统就会显示出一大串信息(这里有省略),其中就有我们需要的 [USER] [PWD] [HOSTNAME]

2.ENVIRON 等第三方提供的接口,这里不做详细介绍

3.使用C语言提供的接口函数,如 getenv() ,我们这里使用该方式,下面会详细介绍。

1.2 获取用户名、主机名、当前路径

getenv 函数 —— stdlib.h C语言标准库

其中,传参 [const char* name] 表示的是我们需要从环境变量中拿取的变量名称,我们这里需要拿到的就是上述使用命令行参数 [env] 读取到的 [USER] [HOSTNAME] [PWD],它的返回值就是我们需要的一个字符串。

现在我们就可以来编写我们的函数了。

char* GetUserName()
{
  char* User = getenv("USER");
  if (!User) return "None";
  return User;
}
char* GetHostName()
{
  char* Host = getenv("HOSTNAME");
  if (!Host) return "None";
  return Host;
}
char* GetCwd()
{
  char* PWD = getenv("PWD");
  if (!PWD) return "None";
  return PWD;
}
void MakeCommandLine()
{
  char* UserName = GetUserName();
  char* HostName = GetHostName();
  char* Cwd = GetCwd();
  printf("[%s@%s %s]>\n", UserName, HostName, Cwd);
}
int main()
{
  MakeCommandLine();
  return 0;
}

我们还可以继续改进一下,使用缓冲区的概念

1.3 缓冲区改进MakeCommandLine

首先我们先宏定义一个缓冲区的大小,这里我设置为 256 。

#define SIZE 256
char Line[SIZE];//自定义缓冲区

其次我们再来学习一个函数 snprintf

按照参数的顺序,依次是 s : 需要写入的目的地 n : 写入字符的数量 format : 标准格式

使用 snprintf 就可以像我们自定义的缓冲区里写啦!

snprintf(Line, sizeof(Line), "[%s@%s %s]>", UserName, HostName, Cwd);
printf("%s\n", Line);

成品: 

#define SIZE 256
//只有MakeCommandLine函数变化,只展示该函数
void MakeCommandLine()
{
  char Line[SIZE];
  char* UserName = GetUserName();
  char* HostName = GetHostName();
  char* Cwd = GetCwd();
  snprintf(Line, sizeof(Line), "[%s@%s %s]>", UserName, HostName, Cwd);
  printf("%s\n", Line);
}

但是还有一个细节,当我们想让程序慢一点结束时,使用 sleep 函数,就会发生神奇的一幕:

为什么没有立刻打印呢?原因是 stdout 的缓冲问题。printf 是行缓冲的,通常情况下,当遇到换行符('\n')时,缓冲区的内容会被送往 stdout 进行输出。然而在某些情况下,如果在调用 sleep 之前没有刷新缓冲区,那么输出可能会延迟直到缓冲区被刷新。

我们的解决方法是使用 fflush 函数!这将刷新(即清空并发送)包含 printf 输出的缓冲区,从而确保立即看到输出:

snprintf(Line, sizeof(Line), "[%s@%s %s]>", UserName, HostName, Cwd);
printf("%s\n", Line);
fflush(stdout);
sleep(5);

二、获取用户命令

首先,我们在输出命令行时,为了方便阅读,用 printf 输出 Line 时加了 '\n' ,我们现在要保证命令行和命令在同一行,所以现在我们要删除掉 '\n' 。

2.1 读取函数的选择

我们可以继续使用 scanf 函数来读取命令吗?按照我们输入的命令,如 [ls -a -l] ,他们都是以空格为分隔符,显然与我们的 sacnf 发生了冲突,而且无法控制每次输入命令的空格数量,所以我们不能使用 scanf 读取输入。

我们的命令都是用行读取,C语言也提供了这样的函数 fgets 。

其中,返回指向读取到字符开头的指针,传参依次为读取字符后存放的位置,读取字符的长度,读取字符的位置。 

 int GetUserCommand(char Command[], size_t n)
 {                                               
    char* s = fgets(Command, n, stdin);
    if (!s) return -1;                                                    
 }     
 int main()                                                         
 {                                                                  
    //输出命令行                                                     
    MakeCommandLine();                                               
    //读取用户命令                                                   
    char UserCommand[SIZE];                                          
    GetUserCommand(UserCommand, sizeof(UserCommand));  
    printf("echo : %s\n", UserCommand); //打印验证一下是否被读取                                            
    return 0;
 }

2.2 细节优化

现在又来了一个细节问题:

fgets 按行读取,如果我们想执行就必须按 [回车] ,假设我们输入的是"Hello World",那么其读取到的就是"Hello World\n",再加上我们自己写的 printf 中的 '\n' 就变成了两行,下面我们进一步优化一下,只需要把命令的最后一个字符改成 '\0' 即可:

 int GetUserCommand(char Command[], size_t n)
 {                                               
    char* s = fgets(Command, n, stdin);
    if (!s) return -1;
    Command[strlen(Command) - 1] = '\0';                                                    
 } 

2.3 返回值

关于函数的返回值,为了与 [return -1] 区分开,也为了更好地执行命令,我们可以返回一下读取到的命令长度,这样当命令 [> 0] 时,我们再继续执行,否则直接退出:

 int GetUserCommand(char Command[], size_t n)
 {                                               
    char* s = fgets(Command, n, stdin);
    if (!s) return -1;
    Command[strlen(Command) - 1] = '\0';    
    return strlen(Command);                                                
 } 
 int main()                                                         
 {                                                                  
    //输出命令行                                                     
    MakeCommandLine();                                               
    //读取用户命令                                                   
    char UserCommand[SIZE];                                          
    int n = GetUserCommand(UserCommand, sizeof(UserCommand));  
    (void)n;//暂时不搞,先强转一下,防止警告                                         
    return 0;
 }

三、指令和选项分割

像我们上面使用的 []ls -a -l] ,它们是由指令和选项构成,所以我们如果要执行,肯定也要把读取用户传入的字符串分割成下面的形式, [ls] [-a] [-l] 。

3.1 strtok 函数

那么如何分割呢?这又要使用到C语言中的字符串函数 strtok 。

其中,返回值是分割后的小字符串,传入参数 str 是要分割的字符串, delimiters 是分隔符

如果首次分割是已经传入参数 str ,那么再次调用时把 str 设置为 NULL ,strtok就会默认继续分割上次的字符串。

一说到分隔符,我们这里的分隔符显而易见的就是空格啦!由于我们的分割是把一个字符串分割成若干小字符串,所以我们就可以直接定义一个全局的数组,用来挨个存放这些小字符串:

但是要注意, strtok 函数使用的分隔符都是字符串,我们的空格不能设置为 ' ' ,而应该为 " "

 #define SEP " "    

3.2 分割实现 

void SplitCommand(char Command[], size_t n)
{
  (void)n;
  argv[0] = strtok(Command, SEP);
  int index = 1;
  while ((argv[index++] = strtok(NULL, SEP)));//strtok如果无法分割,则返回NULL,此时argv最后一个元组直接被赋值为NULL且while循环结束
}

我们还可以通过打印来验证是否正确:

 int i = 0;                                                               
 for (; argv[i]; i++)
 {
     printf("%s ", argv[i]);
 }
 printf("\n");

四、执行命令

我们之前讲过 Bash 会创建子进程,为了保证安全,都是子进程在执行我们的命令,而我们今天要实现的也是使用子进程来帮助我们执行命令。

4.1 fork 方法

fork 函数并不是我们的C语言函数,而是操作系统提供的,所以我们可以用 man 2 fork 查询

这里不做过多说明,实在不知道可以移步到Linux进程概念(1)-CSDN博客

如果执行的是子进程,那么返回值为0,如果是父进程,会返回子进程的 pid

4.2 进程等待

我们之前说过,如果父进程先于子进程退出,子进程就会变成僵尸进程,为了避免这种影响,父进程可以通过进程等待的方式,回收子进程资源,获取子进程退出信息。

下面我们来介绍几个进程等待的方法:

wait 方法:

    返回值:等待成功返回被等待子进程的 pid ,失败返回 -1

    参数:输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

waitpid 方法:

    返回值:

       当正常返回的时候 waitpid 返回收集到的子进程的进程ID;如果设置了选项 WNOHANG,而调用中 waitpid 发现没有已退出的子进程可收集,则返回 0;如果调用中出错,则返回 -1,这时 errno 会被设置成相应的值以指示错误所在;

    参数:

    pid:
       pid=-1,等待任一个子进程。与wait等效。
       pid>0.等待其进程ID与pid相等的子进程。
    status:
       WIFEXITED: 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
       WEXITSTATUS: 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
    options:
       WNOHANG: 若 pid 指定的子进程没有结束,则 waitpid() 函数返回0,不予以等待。

       若正常结束,则返回该子进程的ID。

4.3 进程替换

我们用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

有六种以 exec 开头的函数,统称为 exec 函数:

#include <unistd.h>

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);

这些函数都可以用 man execl 查询:

4.4 程序编写

void ExecuteCommand(char Command[], size_t n)
{
  pid_t id = fork();
  if (id == 0)//子进程,执行命令
  {
   execvp(argv[0],argv);
  }
  else if (id > 0)//父进程
  {
    int status = 0;
    waitpid(id, &status, 0);
  }
  else//fork创建失败,直接退出
  {
    exit(1);
  }
}

下面我们来看一下完整代码以及效果图:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>     //进程创建接口
#include <sys/types.h>  //进程等待
#include <sys/wait.h>   //进程等待

#define SIZE 256
#define NUM 16
#define SEP " "

char* argv[NUM];

char* GetUserName()
{
  char* User = getenv("USER");
  if (!User) return "None";
  return User;
}
char* GetHostName()
{
  char* Host = getenv("HOSTNAME");
  if (!Host) return "None";
  return Host;
}
char* GetCwd()
{
  char* PWD = getenv("PWD");
  if (!PWD) return "None";
  return PWD;
}
void MakeCommandLine()
{
  char Line[SIZE];
  char* UserName = GetUserName();
  char* HostName = GetHostName();
  char* Cwd = GetCwd();
  snprintf(Line, sizeof(Line), "[%s@%s %s]>", UserName, HostName, Cwd);
  printf("%s", Line);
  fflush(stdout);
}
int GetUserCommand(char Command[], size_t n)
{
  char* s = fgets(Command, n, stdin);
  if (!s) return -1;
  Command[strlen(Command) - 1] = '\0';
  return strlen(Command);
}
void SplitCommand(char Command[], size_t n)
{
  (void)n;
  argv[0] = strtok(Command, SEP);
  int index = 1;
  while ((argv[index++] = strtok(NULL, SEP)));
      //strtok如果无法分割,则返回NULL,此时argv最后一个元组直接被赋值为NULL且while循环结束
}
void ExecuteCommand(char Command[], size_t n)
{
  pid_t id = fork();
  if (id == 0)//子进程,执行命令
  {
   execvp(argv[0],argv);
  }
  else if (id > 0)//父进程
  {
    int status = 0;
    waitpid(id, &status, 0);
  }
  else//fork创建失败,直接退出
  {
    exit(1);
  }

}
int main()
{
  //输出命令行
  MakeCommandLine();
  //读取用户命令
  char UserCommand[SIZE];
  int n = GetUserCommand(UserCommand, sizeof(UserCommand));
  (void)n;
  printf("echo : %s\n", UserCommand);
  //指令和选项分割
  SplitCommand(UserCommand, sizeof(UserCommand));
  //执行命令
  ExecuteCommand(UserCommand, sizeof(UserCommand));
  return 0;
}

五、程序优化

5.1 执行次数

虽然我们的 Shell 已经完成的有一点雏形了,但是怎么这个外壳只能使用一次呀?我们是不是要让他多执行几次呢?所以我们就要把这几个步骤都放到一个 while 循环中。

int main()
{
  while(1)
  {
    //输出命令行
    MakeCommandLine();
    //读取用户命令
    char UserCommand[SIZE];
    int n = GetUserCommand(UserCommand, sizeof(UserCommand));
    (void)n;
    //指令和选项分割
    SplitCommand(UserCommand, sizeof(UserCommand));
    //执行命令
    ExecuteCommand(UserCommand, sizeof(UserCommand));
   }
  return 0;
}

5.2 检测命令是否为内建命令

我们在我们的 Shell 中使用 cd 命令,但是我们的命令行无法进入某目录,这是为什么呢?
因为我们上面创建了子进程,我们的 cd 命令是让子进程执行的,和我们真正的 bash 没有关系,我们正确的做法是让父进程执行!

什么是内建命令呢?

下面我们来看看如何检测是否为内建命令。

虽然我们的 [cd] 已经可以使用,但是我们的命令行路径怎么不回退呢?
因为我们还要更改我们的环境变量!

更改环境变量要用到系统调用,需要获取当前路径,并写入环境变量中。

获取当前路径:

man 2 getcwd

写入环境变量:

man snprintf

man putenv

所以我们此时在对我们的 cd 函数做修改:

void ExecuteCd()
{
  const char* path = argv[1];
  if (path == NULL) path = GetHome();
  else chdir(path);

  char tmp[SIZE];
  getcwd(tmp, sizeof(tmp));
  snprintf(cwd, sizeof(cwd), "PWD=%s", tmp);
  putenv(cwd);
}

当然还有其他内建命令,方法都诸如此类。

5.3 子进程执行失败

我们直接在全局定义一个退出码 int lastcode ,然后在父进程中左对应的修改:

void ExecuteCommand(char Command[], size_t n)
{
  pid_t id = fork();
  if (id == 0)//子进程,执行命令
  {
   execvp(argv[0],argv);
  }
  else if (id > 0)//父进程
  {
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if (rid > 0)
    {
      lastcode = WEXITSTATUS(status);
      if (lastcode != 0) printf("%s:%s:%d\n", argv[0], strerror(lastcode), lastcode);
    }
  }
  else//fork创建失败,直接退出
  {
    exit(1);
  }
}

5.4 命令行路径更改

在我们的 XShell 中提供的 Shell 外壳,其命令行的路径都是相对路径,我们的 Shell 也可以改成这样,如下:

我们这里采用了宏函数,也可以使用正常函数,在使用宏函数是时,若程序是代码块,建议放在 [do while(0)] 中,如下:

#define SkipPath(p) do{ p += (strlen(p)-1); while(*p != '/') p--; }while(0)

同时在输出命令行中也调用该函数,如下:

void MakeCommandLine()
{
  char Line[SIZE];
  char* UserName = GetUserName();
  char* HostName = GetHostName();
  char* Cwd = GetCwd();
  
  SkipPath(Cwd);
  snprintf(Line, sizeof(Line), "[%s@%s %s]>", UserName, HostName, Cwd);
  printf("%s", Line);
  fflush(stdout);
}

六、完整代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>     //进程创建接口
#include <sys/types.h>  //进程等待
#include <sys/wait.h>   //进程等待

#define SIZE 256
#define NUM 16
#define SEP " "
#define SkipPath(p) do{ p += (strlen(p)-1); while(*p != '/') p--; }while(0)

char* argv[NUM];
char cwd[SIZE];
int lastcode = 0;

char* GetUserName()
{
  char* User = getenv("USER");
  if (!User) return "None";
  return User;
}
char* GetHostName()
{
  char* Host = getenv("HOSTNAME");
  if (!Host) return "None";
  return Host;
}
char* GetCwd()
{
  char* PWD = getenv("PWD");
  if (!PWD) return "None";
  return PWD;
}
void MakeCommandLine()
{
  char Line[SIZE];
  char* UserName = GetUserName();
  char* HostName = GetHostName();
  char* Cwd = GetCwd();
  
  SkipPath(Cwd);
  snprintf(Line, sizeof(Line), "[%s@%s %s]>", UserName, HostName, Cwd);
  printf("%s", Line);
  fflush(stdout);
}
int GetUserCommand(char Command[], size_t n)
{
  char* s = fgets(Command, n, stdin);
  if (!s) return -1;
  Command[strlen(Command) - 1] = '\0';
  return strlen(Command);
}
void SplitCommand(char Command[], size_t n)
{
  (void)n;
  argv[0] = strtok(Command, SEP);
  int index = 1;
  while ((argv[index++] = strtok(NULL, SEP)));
      //strtok如果无法分割,则返回NULL,此时argv最后一个元组直接被赋值为NULL且while循环结束
}
const char* GetHome()
{
  const char* home = getenv("HOME");
  if (home == NULL) return "/";
  return home;
}
void ExecuteCd()
{
  const char* path = argv[1];
  if (path == NULL) path = GetHome();
  else chdir(path);

  char tmp[SIZE];
  getcwd(tmp, sizeof(tmp));
  snprintf(cwd, sizeof(cwd), "PWD=%s", tmp);
  putenv(cwd);
}
int CheckBuiltin()
{
  int ch = 0;//默认非内建命令
  const char* command = argv[0];
  if (strcmp(command, "cd") == 0)
  {
    ch = 1;
    ExecuteCd();
  }
  return ch;
}
void ExecuteCommand(char Command[], size_t n)
{
  pid_t id = fork();
  if (id == 0)//子进程,执行命令
  {
   execvp(argv[0],argv);
  }
  else if (id > 0)//父进程
  {
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if (rid > 0)
    {
      lastcode = WEXITSTATUS(status);
      if (lastcode != 0) printf("%s:%s:%d\n", argv[0], strerror(lastcode), lastcode);
    }
  }
  else//fork创建失败,直接退出
  {
    exit(1);
  }

}

int main()
{
  while(1)
  {
    //输出命令行
    MakeCommandLine();
    //读取用户命令
    char UserCommand[SIZE];
    int n = GetUserCommand(UserCommand, sizeof(UserCommand));
    (void)n;
    //指令和选项分割
    SplitCommand(UserCommand, sizeof(UserCommand));
    //检查是否为内建命令
    int ch = CheckBuiltin();
    if (ch) continue;
    //执行命令
    ExecuteCommand(UserCommand, sizeof(UserCommand));
   }
  return 0;
}

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

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

相关文章

iPhone GPU性能评估:优化移动应用开发

摘要 了解你的显卡对于在电脑上玩现代图形要求高的游戏非常重要。本文介绍了如何轻松查看你的显卡型号以及为什么显卡在玩电脑游戏时如此关键。 引言 随着电脑游戏的发展&#xff0c;现代游戏对硬件性能的要求越来越高。十年前发布的显卡已经无法满足当前游戏的需求。因此&…

【前端】CSS(引入方式+选择器+常用元素属性+盒模型)

文章目录 CSS一、什么是CSS二、语法规范三、引入方式1.内部样式表2.行内样式表3.外部样式 四、选择器1.选择器的种类1.基础选择器&#xff1a;单个选择器构成的1.标签选择器2.类选择器3.id 选择器4.通配符选择器 2.复合选择器1.后代选择器2.子选择器3.并集选择器4.伪类选择器 五…

深挖苹果Find My技术,伦茨科技ST17H6x芯片赋予产品功能

苹果发布AirTag发布以来&#xff0c;大家都更加注重物品的防丢&#xff0c;苹果的 Find My 就可以查找 iPhone、Mac、AirPods、Apple Watch&#xff0c;如今的Find My已经不单单可以查找苹果的设备&#xff0c;随着第三方设备的加入&#xff0c;将丰富Find My Network的版图。产…

Redis主从复制、哨兵模式、Cluster集群

目录 一、Redis主从复制 1、主从复制介绍 2、主从复制原理 ​编辑 3、主从复制的作用 4.Redis主从复制实验搭建 1. 关闭防火墙和安装依赖环境 2. 解压安装包 3. 编译并安装到指定目录 4. 执行脚本文件 5. 做软连接 6. 启动redis并查看端口 7. 重启redis 8. 修改主…

机器学习每周挑战——信用卡申请用户数据分析

数据集的截图 # 字段 说明 # Ind_ID 客户ID # Gender 性别信息 # Car_owner 是否有车 # Propert_owner 是否有房产 # Children 子女数量 # Annual_income 年收入 # Type_Income 收入类型 # Education 教育程度 # Marital_status 婚姻状况 # Housing_type 居住…

Win11 绕过 TPM 或 CPU 检测

方法 1&#xff1a;修改注册表绕过 TPM 或 CPU 检测&#xff08;升级安装&#xff09; 如果你的硬件不完全符合安装 Windows 11 的基本硬件要求&#xff0c;可以通过修改注册表&#xff0c;在至少拥有 TPM 1.2 和不支持的 CPU 上升级安装 Windows 11 系统. 适用场景&#xff…

【Layui】------ layui实现table表格拖拽行、列位置的示例代码

一、完整的示例代码&#xff1a;&#xff08;请使用layui v2.8.3的版本&#xff09;看懂就能用、不要照搬、照搬会出错误、拷贝重要代码改改符合你自己的需求。 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><…

【图论】知识点集合

边的类型 neighbors(邻居)&#xff1a;两个顶点有一条共同边 loop&#xff1a;链接自身 link&#xff1a;两个顶点有一条边 parallel edges&#xff1a;两个顶点有两条及以上条边 无向图 必要条件&#xff1a;删掉顶点数一定大于等于剩下的顶点数 设无向图G<V,E>是…

scRNA+bulk+MR:动脉粥样硬化五个GEO数据集+GWAS,工作量十分到位

今天给大家分享一篇JCR一区&#xff0c;单细胞bulkMR的文章&#xff1a;An integrative analysis of single-cell and bulk transcriptome and bidirectional mendelian randomization analysis identified C1Q as a novel stimulated risk gene for Atherosclerosis 标题&…

基于ZooKeeper的Kafka分布式集群搭建与集群启动停止Shell脚本

下载Kafka压缩包 下方是Kafka官网下载地址&#xff0c;本文使用Kafka 3.0.0在虚拟机环境中搭建分布式集群。 Apache Kafka Downloads link 虽然在Kafka 2.8.0之后可以使用KRaft模式搭建高可用的集群以提高数据处理效率&#xff0c;但是目前还有许多企业依然使用ZooKeeper搭建K…

C++中的List容器用法详解

文章目录 C中的List容器用法详解List 的特点List 的重要接口用法介绍1.创建和初始化Listlist 2.插入元素push_backpush_forntinsert 删除元素pop_backpop_fontclearerase 遍历List迭代器遍历范围for遍历 排序Listsort 反转Listreverse 转移Listsplice 去重unique 合并merge 总结…

wordpress课程项目主题电脑版+手机版自适应

这款主题适合做资源、课程、素材等&#xff0c;演示站&#xff1a;点击查看

​网络socket编程(二)——面向流的TCP编程及测试(SocketTool)、Wireshark软件使用

目录 一、书接上回&#xff08;select()函数使用注意事项&#xff09; 二、面向流(TCP)的socket编程 2.1 TCP服务端编程和测试 2.1.1 TCP服务器原理流程图 2.1.2 TCP服务端编程实战 2.1.3 测试 2.2 TCP客户端编程和测试 三、Wireshark抓包软件的使用 3.1 Wireshark是什…

fastlio2 给 interactive-slam 保存每帧的点云和每帧的里程计为单独的文件做后端回环优化和手动回环优化

为了给 interactive-slam 提供数据做后端回环优化和手动回环优化,需要保存每帧的点云和每帧的里程计为单独的文件,并且需要保存的名字为ros时间戳。 效果很好,比我自己写的手动回环模块好用 // This is an advanced implementation of the algorithm described in the // fo…

Jmeter各组件超详细介绍

1、JMeter和Loadrunner的区别&#xff1f; 2、JMeter如何开发脚本的&#xff1f;强化脚本的技术&#xff1f; 代理服务器录制脚本&#xff0c;Fiddler录制脚本&#xff0c;Badboy录制脚本&#xff0c;根据API&#xff0c;手写脚本&#xff0c;根据抓包&#xff0c;手写脚本。 …

Twitter Api查询用户粉丝列表

如果大家为了获取实现方式代码的话可能要让大家失望了&#xff0c;这边文章主要是为了节省大家开发时间&#xff0c;少点坑。https://api.twitter.com/2/users/:id/followers &#xff0c;这个接口很熟悉吧&#xff0c;他是推特提供的获取用户关注者&#xff08;粉丝&#xff0…

目标跟踪——行人车辆数据集

一、重要性及意义 首先&#xff0c;目标跟踪对于个人和组织的目标实现至关重要。无论是个人职业发展、企业业务增长还是政府的社会发展&#xff0c;目标跟踪都能够帮助我们明确目标&#xff0c;并将其分解为可行的步骤和时间表。这有助于我们保持动力和专注&#xff0c;提高效…

CNAS软件测试公司有什么好处?如何选择靠谱的软件测试公司?

CNAS认可是中国合格评定国家认可委员会的英文缩写&#xff0c;由国家认证认可监督管理委员会批准设立并授权的国家认可机构&#xff0c;统一负责对认证机构、实验室和检验机构等相关机构的认可工作。 在软件测试行业&#xff0c;CNAS认可具有重要意义。它标志着一个软件测试公…

Java并发编程基础面试题详细总结

1. 什么是线程和进程? 1.1 何为进程? 进程是程序的一次执行过程&#xff0c;是系统运行程序的基本单位&#xff0c;因此进程是动态的。系统运行一个程序即是一个进程从创建&#xff0c;运行到消亡的过程。 在 Java 中&#xff0c;当我们启动 main 函数时其实就是启动了一个…

Failed to resolve import “Home/components/HomeNew.vue“. Does the file exist?

错误信息 [plugin:vite:import-analysis] Failed to resolve import "/apis/home.js" from "src/views/Home/components/HomeNew.vue". Does the file exist? 错误原因 路径错误 解决方法