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

Linux笔记---自定义shell

目录

前言

1. 程序框架

2. 打印命令行提示符

2.1 获取用户名(GetUserName)

2.2 获取主机名(GetHostName)

2.3 获取工作目录(GetPwd)

3. 获取命令行输入

4. 判断是否有重定向

5. 解析命令行

 6. 内建命令

6.1 内建命令的特点

6.2 常见内建命令

6.3 内建命令 vs 外部命令

6.4 为什么需要内建命令

6.5 处理内建命令

 6.5.1 cd 命令

6.5.2 echo 命令

7. 执行命令

8. 完整代码

9. 缺陷


前言

该自定义shell并没有任何实用价值,一是因为其功能并不完善,二是因为其是建立在bash的基础之上进行的编写的成果(初始环境变量取自bash),仅具有学习价值。

为了贴近真实的 shell 程序开发,我们的代码完全用C语言完成。

本文通过完成一个自定义shell小项目的方式,对前面学习的知识做一个简单的总结,帮助读者深入理解有关概念(子进程,进程调度,环境变量……)。

前置参考文章:https://blog.csdn.net/2302_80372340/category_12833896.html

1. 程序框架

在开始之前,我们要先明确我们要做什么,我们的目标是:(1)不断显示当前用户名、主机和工作目录,并要求用户输入指令;(2)解析用户输入的指令,执行对应的操作或启动对应的进程。

当然,在正式开始之前,我们需要初始化我们自己的 shell 程序(后文统称myshell)的环境变量。

// myshell的环境变量
#define MAX_ENV_SIZE 100
char *my_env[MAX_ENV_SIZE];
int env_count = 0;

据此,我们可以大致拟出 myshell 的框架: 

int main()
{// 初始化环境变量InitEnv();// PrintEnv();while(true){// 打印命令行提示符PrintCommandPrompt();// 获取命令行输入char commandline[MAX_COMMAND_LEN];if(!GetCommandLine(commandline, sizeof(commandline)))continue;// 判断是否有重定向RedirCheck(commandline);// 命令行解析if(!ParseCommandLine(commandline))continue;// PrintArgv();// 判断是否有为内建命令,如果是就执行// 后文中再解释何为内建命令if(CheckAndExecBuiltin())continue;// 执行命令Execute();}return 0;
}

bash 会在启动时,通过解析配置文件,将环境变量加载到内存中并维护起来。

我们并不了解其配置文件的解析方式,为了简单起见,我们直接抄袭 bash 的环境变量以初始化的环境变量。

在拷贝完成之后,需要让environ指向我们自己的环境变量表,这样一来,各种与环境变量有关的系统调用就会对我们自己的环境变量表进行操作。

实际上,就算直接使用 environ 指向的环境变量表也是完全一样的效果,因为其本来就是从 bash 继承下来的一个拷贝。但就像我们之前说的,我们只是用拷贝 bash 环境变量的方式代替了从配置文件中读取的过程,真正的 shell 程序肯定是要自己维护一张环境变量表的。虽然将环境变量拷贝到自己的环境变量表中略显多余,但是为了帮助我们更好地理解 shell 程序的运作方式,还是自己维护一张环境变量表的方式更好。

// 初始化环境变量
void InitEnv()
{memset(my_env, 0, sizeof(my_env));extern char** environ;// 本来应该从配置文件中读取,但为了方便就直接拷贝bash的环境变量for(env_count = 0; environ[env_count]; env_count++){my_env[env_count] = (char*)malloc(strlen(environ[env_count]) + 1);strcpy(my_env[env_count], environ[env_count]);}my_env[env_count++] = NULL;// 让environ指向我们自己的环境变量表environ = my_env;
}

2. 打印命令行提示符

所谓命令行提示符,就是:用户名 + 主机 + 工作目录。

上图是Ubuntu环境下的命令行提示符,它将工作目录完整显示出来了,当工作目录层次较深时看起来很冗余。

相比之下,我更喜欢CentOS的格式,下图是myshell运行起来的提示符:

只显示了最后一级工作目录,看起来要简洁一些。

void PrintCommandPrompt()
{// CentOs的格式,感觉更简洁清晰一些printf("[%s@%s %s]# ", GetUserName(), GetHostName(), GetBaseName());
}

2.1 获取用户名(GetUserName)

直接到环境变量中找到 "USER" 环境变量即可。

// 默认字符串
const char* default_str = "None";// 获取用户名
const char* GetUserName()
{const char* username = getenv("USER");return username == NULL ? default_str : username;
}

2.2 获取主机名(GetHostName)

在Unbuntu中没有 "HOSTNAME" 环境变量,我们可以通过系统调用 gethostname 来获取。

// 主机名hostname
#define MAX_HOST_SIZE 256
char hostname[MAX_HOST_SIZE];// 获取主机名
const char* GetHostName()
{// Ubuntu中没有HOSTNAME环境变量// const char* hostname = getenv("HOSTNAME");return gethostname(hostname, sizeof(hostname)) != 0 ? default_str : hostname;
}

2.3 获取工作目录(GetPwd)

同样,在找到环境变量 "PWD" 即可,但是我们只需要最后一级目录,所以对GetPwd进行了一层封装。

这里要解释一下 cwd 是什么。cwd 是进程的一个属性而不是环境变量,用于表示进程的当前工作目录。使用 getcwd 系统调用可以获得 cwd。至于为什么要用 cwd 来更新 PWD ,我们在后文中讲到 cd 命令的时候在详细解释。


// cwd---当前用户工作目录
#define MAX_CWD_SIZE 1024
char cwd[MAX_CWD_SIZE];
char pwdOfcwd[MAX_CWD_SIZE + 6];// 获取当前工作目录
const char* GetPwd()
{const char* pwd = getcwd(cwd, sizeof(cwd));// 更新pwdif(pwd != NULL){snprintf(pwdOfcwd, sizeof(pwdOfcwd), "PWD=%s", cwd);putenv(pwdOfcwd);}return pwd == NULL ? default_str : pwd;
}// 最后一级路径,也叫"基本名称"
const char* GetBaseName()
{const char* pwd = GetPwd();int pos = strlen(pwd) - 1;while(pos > 0 && pwd[pos] != '/')pos--;if(pwd[pos] == '/') pos++;return (pwd + pos);
}

3. 获取命令行输入

第一个参数是存放命令行输入的字符数组,第二个参数是字符数组的大小。

这个函数只负责将用户的输入照搬下来,放到一个一维数组,包括空格、重定向符号等。

为了读取一整行的字符,这里用 fgets 函数来进行读取。

// 命令行输入的最大值
#define MAX_COMMAND_LEN 1024
// 在主循环中定义的数组
char commandline[MAX_COMMAND_LEN];bool GetCommandLine(char* commandline, int n)
{char* str = fgets(commandline, n, stdin);if(str == NULL) return false;// 清理'\n',顺便在结尾加上'\0'commandline[strlen(commandline) - 1] = '\0';if(strlen(commandline) == 0) return false;return true;
}

4. 判断是否有重定向

在解析命令行之前,需要先判断是否存在重定向,并将重定向类型与相关文件记录下来。接着,将命令行输入中的重定向符号与文件名删除。

这样一来,解析命令行部分要做的就只有将命令行参数按空格分开,而不需要再检查某个参数中是否含有重定向符号了。

// 重定向状态
enum redir_status
{NONE_REDIR,INPUT_REDIR,OUTPUT_REDIR,APPEND_REDIR
};
enum redir_status redir = NONE_REDIR;
#define MAX_FILENAME 1024
char filename[MAX_FILENAME];void RedirCheck(char* commandline)
{redir = NONE_REDIR;int len = strlen(commandline);int pos = 0;for(pos = 0; pos < len; pos++){// 输入重定向if(commandline[pos] == '<'){commandline[pos] = '\0';redir = INPUT_REDIR;break;}else if(commandline[pos] == '>'){commandline[pos] = '\0';// 追加重定向if(pos < len - 1 && commandline[pos + 1] == '>'){redir = APPEND_REDIR;pos++;}// 输出重定向else{redir = OUTPUT_REDIR;}break;}}pos++;// 发生了重定向,提取文件名if(pos != len){while(commandline[pos] == ' '){pos++;}int i = 0;for(i = 0; pos + i < len; i++){filename[i] = commandline[pos + i];}filename[i] = '\0';}
}

5. 解析命令行

利用 strtok 函数将命令行的输入按空格拆分成一个个的命令行参数,并存放到命令行参数向量 my_argv 中。

// 命令行参数
#define MAX_ARGC 128
char* my_argv[MAX_ARGC];
int my_argc = 0;bool ParseCommandLine(char* commandline)
{my_argc = 0;static const char sep[] = {" "};for(my_argv[my_argc] = strtok(commandline, sep); my_argv[my_argc++]; my_argv[my_argc] = strtok(NULL, sep)){;}my_argc--;return my_argc > 0 ? true : false;
}

 6. 内建命令

在最后一个部分中,我们可以通过启动子程序的方式来执行大多数命令(就像 bash 做的那样),但是某些命令却不能用这样的方式来实现,因为它们是 shell 本身的一种功能,而非外部命令。

在 Linux 和类 Unix 系统的 Shell 中,内建命令(Built-in Command) 是直接集成在 Shell 解释器内部的命令,不需要调用外部的可执行文件。它们由 Shell 自身直接执行,因此具有更高的执行效率和特殊的功能特性。

6.1 内建命令的特点

  1. 直接由 Shell 解释器处理:内建命令的代码是 Shell 程序的一部分,执行时不会创建新的进程(例如 cd、echo、export)。

  2. 与 Shell 环境紧密相关:内建命令通常用于修改 Shell 自身的状态(例如修改当前目录 cd、设置环境变量 export)。

  3. 执行速度快:因为不需要启动外部进程或搜索磁盘上的可执行文件。

  4. 无法通过 PATH 环境变量查找:内建命令的名称是固定的,不能通过路径调用(例如 /bin/cd 并不存在,但 cd 是 Shell 的内建命令)。

6.2 常见内建命令

  • cd:切换当前工作目录。
  • echo:输出文本。
  • export:设置环境变量。
  • source(或 .):加载并执行脚本(不创建子 Shell)。
  • exit:退出 Shell 或脚本。
  • alias/unalias:设置或取消命令别名。
  • type:查看命令类型(内建、外部或别名)。

6.3 内建命令 vs 外部命令

特性内建命令外部命令
存储位置集成在 Shell 解释器中存储在磁盘上的可执行文件(如 /bin/ls)
执行方式由 Shell 直接执行需要启动新进程(通过 fork + exec)
依赖环境直接影响当前 Shell 环境在子进程中运行,不影响父 Shell 环境
执行速度快(无进程创建开销)慢(需启动进程和加载可执行文件)

6.4 为什么需要内建命令

  1. 修改 Shell 自身状态:例如 cd 必须内建,因为外部命令无法修改父 Shell 的当前目录。
  2. 提高效率:高频操作(如 echo)内建可减少进程创建开销。
  3. 提供特殊功能:例如 source 命令用于在当前 Shell 中执行脚本,而非新建子进程。

某些命令可能同时有内建和外部版本(例如 echo、printf),默认优先使用内建版本。可以通过路径强制调用外部版本(例如 /bin/echo)。

不同 Shell(如 Bash、Zsh)的内建命令可能略有差异。

6.5 处理内建命令

下面的代码仅实现了内建命令 cd 和 echo,对于其他的内建命令,各位可以自行尝试实现。

bool CheckAndExecBuiltin()
{if(strcmp(my_argv[0], "cd") == 0){Cd();return true;}else if(strcmp(my_argv[0], "echo") == 0){Echo();return true;}else if(strcmp(my_argv[0], "export") == 0){// Todoreturn true;}else if(strcmp(my_argv[0], "alias") == 0){// Todoreturn true;}else{return false;}
}
 6.5.1 cd 命令

cd 命令的作用是修改 shell 的工作目录,即修改 shell 进程的 cwd 属性,在代码层面,我们可以使用 chdir 系统调用来实现这一点。

// 内建命令cd
void Cd()
{if(my_argc == 1 || strcmp(my_argv[1], "~") == 0 || strcmp(my_argv[1], "-") == 0){const char* home = GetHome();if(strcmp(home, default_str) != 0)chdir(home);if(strcmp(my_argv[1], "-") == 0)printf("%s\n", home);}else{chdir(my_argv[1]);}
}

cwd(Current Working Directory)是进程的一个属性,表示进程当前的工作目录。

PWD(Print Working Directory)是进程环境变量表中的一个环境变量,用于打印当前工作目录。

在 bash 中,我们使用 cd 修改的是 cwd,程序关注的也是 cwd (cwd + 相对路径 = 绝对路径),但是 bash 会自动更新 PWD 。

当我们自己管理环境变量表时,一定要记得更新 PWD ,这一点我们放到 GetPwd 中完成。

6.5.2 echo 命令
// 最近的一次指令执行的退出码
int last_exit_code = 0;// 内建命令echo
void Echo()
{FILE* file = stdout;if(redir == OUTPUT_REDIR){file = fopen(filename, "w");}else if(redir == APPEND_REDIR){file = fopen(filename, "a");}if(strcmp(my_argv[1], "$?") == 0){fprintf(file, "%d\n", last_exit_code);}else if(my_argv[1][0] == '$'){const char* env = getenv(my_argv[1] + 1);if(env)fprintf(file, "%s\n", env);}else{fprintf(file, "%s\n", my_argv[1]);}if(file != stdout)fclose(file);
}

7. 执行命令

就像我们在Linux笔记---进程:进程替换-CSDN博客和Linux笔记---系统文件I/O-CSDN博客中讲到的,启动子程序,重定向,程序替换,三步即可完成。

看上去是核心,实际上却是全篇最简单的部分(因为巨人主要站在这部分)。

// 执行
void Execute()
{pid_t id = fork();if(id == 0){int fd = 0;if(redir == INPUT_REDIR){fd = open(filename, O_RDONLY);if(fd < 0) exit(1);dup2(fd, 0);}else if(redir == OUTPUT_REDIR){fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);if(fd < 0) exit(1);dup2(fd, 1);}else if(redir == APPEND_REDIR){fd = open(filename, O_CREAT | O_WRONLY | O_APPEND, 0666);if(fd < 0) exit(1);dup2(fd, 1);}execvp(my_argv[0], my_argv);exit(1);}redir = NONE_REDIR;int status;pid_t rid = waitpid(id, &status, 0);if(rid > 0)last_exit_code = WEXITSTATUS(status);
}

8. 完整代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/stat.h>// myshell的环境变量
#define MAX_ENV_SIZE 100
char *my_env[MAX_ENV_SIZE];
int env_count = 0;// cwd---当前用户工作目录
#define MAX_CWD_SIZE 1024
char cwd[MAX_CWD_SIZE];
char pwdOfcwd[MAX_CWD_SIZE + 6];// 主机名hostname
#define MAX_HOST_SIZE 256
char hostname[MAX_HOST_SIZE];// 命令行参数
#define MAX_ARGC 128
char* my_argv[MAX_ARGC];
int my_argc = 0;// 命令行输入的最大值
#define MAX_COMMAND_LEN 1024// 最近的一次指令执行的退出码
int last_exit_code = 0;// 默认字符串
const char* default_str = "None";// 重定向状态
enum redir_status
{NONE_REDIR,INPUT_REDIR,OUTPUT_REDIR,APPEND_REDIR
};
enum redir_status redir = NONE_REDIR;
#define MAX_FILENAME 1024
char filename[MAX_FILENAME];// 初始化环境变量
void InitEnv()
{memset(my_env, 0, sizeof(my_env));extern char** environ;// 本来应该从配置文件中读取,但为了方便就直接拷贝bash的环境变量for(env_count = 0; environ[env_count]; env_count++){my_env[env_count] = (char*)malloc(strlen(environ[env_count]) + 1);strcpy(my_env[env_count], environ[env_count]);}my_env[env_count++] = NULL;// 让environ指向环境变量表environ = my_env;
}// 打印环境变量,用于调试
void PrintEnv()
{extern char** environ;for(int i = 0; environ[i]; i++){printf("%s\n", environ[i]);}
}// 获取用户名
const char* GetUserName()
{const char* username = getenv("USER");return username == NULL ? default_str : username;
}// 获取主机名
const char* GetHostName()
{// Ubuntu中没有HOSTNAME环境变量// const char* hostname = getenv("HOSTNAME");return gethostname(hostname, sizeof(hostname)) != 0 ? default_str : hostname;
}// 获取当前工作目录
const char* GetPwd()
{const char* pwd = getcwd(cwd, sizeof(cwd));// 更新pwdif(pwd != NULL){snprintf(pwdOfcwd, sizeof(pwdOfcwd), "PWD=%s", cwd);putenv(pwdOfcwd);}return pwd == NULL ? default_str : pwd;
}// 最后一级路径,也叫"基本名称"
const char* GetBaseName()
{const char* pwd = GetPwd();int pos = strlen(pwd) - 1;while(pos > 0 && pwd[pos] != '/')pos--;if(pwd[pos] == '/') pos++;return (pwd + pos);
}// 获取家目录
const char* GetHome()
{const char* home = getenv("HOME");return home == NULL ? default_str : home;
}void PrintCommandPrompt()
{// CentOs的格式,感觉更简洁清晰一些printf("[%s@%s %s]# ", GetUserName(), GetHostName(), GetBaseName());
}bool GetCommandLine(char* commandline, int n)
{char* str = fgets(commandline, n, stdin);if(str == NULL) return false;// 清理'\n',顺便在结尾加上'\0'commandline[strlen(commandline) - 1] = '\0';if(strlen(commandline) == 0) return false;return true;
}bool ParseCommandLine(char* commandline)
{my_argc = 0;static const char sep[] = {" "};for(my_argv[my_argc] = strtok(commandline, sep); my_argv[my_argc++]; my_argv[my_argc] = strtok(NULL, sep)){;}my_argc--;return my_argc > 0 ? true : false;
}// 打印命令行参数,用于调试
void PrintArgv()
{for(int i = 0; i < my_argc; i++){printf("%s ", my_argv[i]);}printf("\n");
}// 内建命令cd
void Cd()
{if(my_argc == 1 || strcmp(my_argv[1], "~") == 0 || strcmp(my_argv[1], "-") == 0){const char* home = GetHome();if(strcmp(home, default_str) != 0)chdir(home);if(strcmp(my_argv[1], "-") == 0)printf("%s\n", home);}else{chdir(my_argv[1]);}
}// 内建命令echo
void Echo()
{FILE* file = stdout;if(redir == OUTPUT_REDIR){file = fopen(filename, "w");}else if(redir == APPEND_REDIR){file = fopen(filename, "a");}if(strcmp(my_argv[1], "$?") == 0){fprintf(file, "%d\n", last_exit_code);}else if(my_argv[1][0] == '$'){const char* env = getenv(my_argv[1] + 1);if(env)fprintf(file, "%s\n", env);}else{fprintf(file, "%s\n", my_argv[1]);}if(file != stdout)fclose(file);
}bool CheckAndExecBuiltin()
{if(strcmp(my_argv[0], "cd") == 0){Cd();return true;}else if(strcmp(my_argv[0], "echo") == 0){Echo();return true;}else if(strcmp(my_argv[0], "export") == 0){// Todoreturn true;}else if(strcmp(my_argv[0], "alias") == 0){// Todoreturn true;}else{return false;}
}// 执行
void Execute()
{pid_t id = fork();if(id == 0){int fd = 0;if(redir == INPUT_REDIR){fd = open(filename, O_RDONLY);if(fd < 0) exit(1);dup2(fd, 0);}else if(redir == OUTPUT_REDIR){fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);if(fd < 0) exit(1);dup2(fd, 1);}else if(redir == APPEND_REDIR){fd = open(filename, O_CREAT | O_WRONLY | O_APPEND, 0666);if(fd < 0) exit(1);dup2(fd, 1);}execvp(my_argv[0], my_argv);exit(1);}redir = NONE_REDIR;int status;pid_t rid = waitpid(id, &status, 0);if(rid > 0)last_exit_code = WEXITSTATUS(status);
}void RedirCheck(char* commandline)
{redir = NONE_REDIR;int len = strlen(commandline);int pos = 0;for(pos = 0; pos < len; pos++){// 输入重定向if(commandline[pos] == '<'){commandline[pos] = '\0';redir = INPUT_REDIR;break;}else if(commandline[pos] == '>'){commandline[pos] = '\0';// 追加重定向if(pos < len - 1 && commandline[pos + 1] == '>'){redir = APPEND_REDIR;pos++;}// 输出重定向else{redir = OUTPUT_REDIR;}break;}}pos++;// 发生了重定向,提取文件名if(pos != len){while(commandline[pos] == ' '){pos++;}int i = 0;for(i = 0; pos + i < len; i++){filename[i] = commandline[pos + i];}filename[i] = '\0';}
}int main()
{// 初始化环境变量InitEnv();// PrintEnv();while(true){// 打印命令行提示符PrintCommandPrompt();// 获取命令行输入char commandline[MAX_COMMAND_LEN];if(!GetCommandLine(commandline, sizeof(commandline)))continue;// 判断是否有重定向RedirCheck(commandline);// 命令行解析if(!ParseCommandLine(commandline))continue;// PrintArgv();// 判断是否有为内建命令,如果是就执行if(CheckAndExecBuiltin())continue;// 执行命令Execute();}return 0;
}

9. 缺陷

除了之前提到的内建命令不完整的问题以外,myshell 与真正的 bash 还有一定的区别:

  1. 输入时会将方向键,删除键等识别为字符,而不会发挥其原本的文本编辑功能。
  2. ls 不会根据文件类型显示对应的颜色。

尝试过解决,但是放弃了。先这样吧,说不定以后学的知识多了就知道怎么解决了。 

相关文章:

Linux笔记---自定义shell

目录 前言 1. 程序框架 2. 打印命令行提示符 2.1 获取用户名(GetUserName) 2.2 获取主机名(GetHostName) 2.3 获取工作目录(GetPwd) 3. 获取命令行输入 4. 判断是否有重定向 5. 解析命令行 6. 内建命令 6.1 内建命令的特点 6.2 常见内建命令 6.3 内建命令 vs 外部命…...

步进电机软件细分算法解析与实践指南

1. 步进电机细分技术概述 步进电机是一种将电脉冲信号转换为角位移的执行机构&#xff0c;其基本运动单位为步距角。传统步进电机的步距角通常为 1.8&#xff08;对应 200 步 / 转&#xff09;&#xff0c;但在高精度定位场景下&#xff0c;这种分辨率已无法满足需求。细分技术…...

mapbox开发小技巧

自定义图标 // 1、单个图标 const url ./static/assets/symbols/code24x24/VIDEO.png // 图标路径 map.loadImage(url ,(error, image) > {if (error) throw errormap.addImage(video-icon, image) })// 2、雪碧图利用canvas // json和png图片 function getStyleImage(fil…...

ApoorvCTF Rust语言逆向实战

上周参加了国外的比赛&#xff0c;名称叫&#xff1a;ApoorvCTF 看一下老外的比赛跟我们有什么不同&#xff0c;然后我根据国内比赛对比发现&#xff0c;他们考点还是很有意思的&#xff0c;反正都是逆向&#xff0c;哈哈哈 Rusty Vault 题目描述&#xff1a; In the heart…...

IntersectionObserver接口介绍

IntersectionObserver API 是浏览器提供的一个用于异步观察目标元素与其祖先元素或视口&#xff08;Viewport&#xff09;交叉状态&#xff08;即是否进入或离开视口&#xff09;的接口。在 IntersectionObserver 出现之前&#xff0c;开发者通常需要通过监听 scroll 事件或使用…...

AI壁纸进阶宝典:让创作效率与质量飞速提升的法门

在数字化创意浪潮席卷的当下&#xff0c;AI壁纸以其独特魅力和无限可能&#xff0c;吸引了众多设计爱好者和专业设计师的目光。然而&#xff0c;如何在众多创作者中脱颖而出&#xff0c;打造令人惊艳的AI壁纸呢&#xff1f;本文将从基础到进阶&#xff0c;全方位剖析AI壁纸创作…...

Releases(发布) 和 版本管理 是两个紧密相关的概念

在软件开发和维护中,Releases(发布) 和 版本管理 是两个紧密相关的概念,特别是在开源项目或企业软件开发中。 1. Releases(发布) Release 是指软件的一个正式发布版本,通常经过开发、测试、修复 Bug,并被认为是足够稳定和可用于生产环境的版本。 主要特点 里程碑:通…...

从零开始用react + tailwindcss + express + mongodb实现一个聊天程序(十二) socketio 消息处理

1.后端 在message.controller.js中 在sendMessage方法中 每当我们发送消息 需要socketio把这个消息转发给 接收人 加入转发逻辑 // 把消息发给指定的用户的socket const receiverSocketId getReceiverSocketId(receiverId); if(receiverSocketId) { io.to(receiverSocket…...

【运维篇】KubeSphere-02(经验汇总)

一、使用建议 1.对于数据库、对像存储比较重的要不能丢失&#xff0c;有异地存储备份需求的有状态服务&#xff0c;不建议采用k8s进行部署&#xff0c;会导致运维难度更大。 2.对于中间件如redis、MQ、harbor、seata、nacos、zookeeper可采用k8s部署。 3.对于无状态服务tomc…...

8.大模型微调学习案例:基于 Hugging Face、8位量化与 LoRA 适配器的方案

文章目录 一、引言二、数据预处理与构建数据集2.1 加载 JSON 数据2.2 构造训练样本2.3 构建 Hugging Face 数据集与分词2.4 添加标签字段2.5 划分训练集与验证集 三、模型加载、量化与适配器配置3.1 配置设备与显存使用3.2 配置 8 位量化3.3 加载预训练模型并映射设备3.4 为 8 …...

用分页干掉显存浪费!聊聊VLLM的PagedAttention

不知道你在部署模型的时候&#xff0c;有没有经历过这样的抓狂时刻——跑大模型时显卡显存明明没有占满&#xff0c;程序却报错OOM&#xff08;内存不足&#xff09;&#xff1f;表面看是显存不足&#xff0c;背后其实是KVCache碎片化和重复存储这个"隐形刺客"在作祟…...

Python——地图可视化

分级统计地图&#xff1a;一般使用同一种颜色表示一个区域的属性&#xff0c;常用与选举和人口普查数据的可视化。对于颜色的选择可以使用单色渐变系、双向渐变系、完整色谱变化。 使用这类颜色渐变进行填充存在的问题&#xff1a; 使用这类方法适用于数据密集分布的区域&#…...

3.8【Q】cv

这个draw_line函数的逻辑和功能是什么&#xff1f;代码思路是什么&#xff1f;怎么写的&#xff1f; 这个t是什么&#xff1f;t.v[0]和t.v[1],[2]又是什么&#xff1f; void rst::rasterizer::draw(rst::pos_buf_id pos_buffer, rst::ind_buf_id ind_buffer, rst::Primitive ty…...

LeetCode 热题 100_字符串解码(71_394_中等_C++)(栈)

LeetCode 热题 100_字符串解码&#xff08;71_394&#xff09; 题目描述&#xff1a;输入输出样例&#xff1a;题解&#xff1a;解题思路&#xff1a;思路一&#xff08;栈&#xff09;&#xff1a; 代码实现代码实现&#xff08;栈&#xff09;&#xff1a;以思路一为例进行调…...

基于大模型预测的新型隐球菌脑膜炎综合诊疗研究报告

目录 一、引言 1.1 研究背景与目的 1.2 研究现状综述 二、大模型预测原理及数据基础 2.1 大模型技术简介 2.2 数据收集与预处理 2.3 模型训练与验证 三、术前风险预测与准备 3.1 病情评估指标确定 3.2 手术方案制定 3.3 麻醉方案规划 四、术中监测与应对 4.1 实时…...

SQL Server核心知识总结

SQL Server核心知识总结 &#x1f3af; 本文总结了SQL Server核心知识点,每个主题都提供实际可运行的示例代码。 一、SQL Server基础精要 1. 数据库核心操作 -- 1. 创建数据库&#xff08;核心配置&#xff09; CREATE DATABASE 学生管理系统 ON PRIMARY (NAME 学生管理系统…...

浏览器WEB播放RTSP

注意&#xff1a;浏览器不能直接播放RTSP&#xff0c;必须转换后才能播放。这一点所有的播放都是如此。 参考 https://github.com/kyriesent/node-rtsp-stream GitHub - phoboslab/jsmpeg: MPEG1 Video Decoder in JavaScript 相关文件方便下载 https://download.csdn.net…...

登录固定账号和密码:

接口文档 【apifox】面试宝典 个人中心-保存用户数据信息 - 教学练测项目-面试宝典-鸿蒙 登录固定账号和密码&#xff1a; 账号&#xff1a;hmheima 密码&#xff1a;Hmheima%123 UI设计稿 【腾讯 CoDesign】面试宝典 CoDesign - 腾讯自研设计协作平台 访问密码&#xff1…...

深度学习/强化学习调参技巧

深度调优策略 1. 学习率调整 技巧&#xff1a;学习率是最重要的超参数之一。过大可能导致训练不稳定&#xff0c;过小则收敛速度慢。可以使用学习率衰减&#xff08;Learning Rate Decay&#xff09;或自适应学习率方法&#xff08;如Adam、RMSprop&#xff09;来动态调整学习…...

Java常用正则表达式(身份证号、邮箱、手机号)格式校验

目录 身份证号的正则表达式 代码解释 正则表达式 方法 isValidIDCard 注意事项 校验邮箱的正则表达式 代码解释 正则表达式 方法 isValidEmail 注意事项 手机号的正则表达式 中国大陆手机号校验&#xff08;支持空字符串&#xff09; 代码解释 通用手机号校验&am…...

1.12.信息系统的分类【ES】

专家系统&#xff08;ES&#xff09;技术架构深度解析 一、ES核心定义 &#x1f9e0; 智能决策中枢 由三大核心能力构建的领域专家模拟系统&#xff1a; 存储专家级领域知识&#xff08;10^4规则量级&#xff09;支持不确定性推理&#xff08;置信度>85%&#xff09;动态…...

c语言程序设计--(结构体、共用体)冲刺考研复试中的面试问答,来看看我是怎么回答的吧!

结构体 1、谈谈你对结构体的理解。 答&#xff0c;首先在结构的基础知识上&#xff0c;结构是一些值的集合&#xff0c;这些值称为成员变量结构的每个成员可以是不同类型的变量。结构体其实就是把&#xff08;一些单一类型的数据&#xff09;不同类型的数据组合在一起的做法便…...

闭包:JavaScript 中的隐形大杀器

你可能已经在很多地方听说过闭包这个词&#xff0c;尤其是涉及到 JavaScript 的作用域和异步操作时。闭包是 JavaScript 中非常核心的概念&#xff0c;然而它又非常容易让开发者感到困惑。今天我们就来深入剖析闭包&#xff0c;帮助你真正理解它的工作原理&#xff0c;以及如何…...

分布式锁—7.Curator的分布式锁一

大纲 1.Curator的可重入锁的源码 2.Curator的非可重入锁的源码 3.Curator的可重入读写锁的源码 4.Curator的MultiLock源码 5.Curator的Semaphore源码 1.Curator的可重入锁的源码 (1)InterProcessMutex获取分布式锁 (2)InterProcessMutex的初始化 (3)InterProcessMutex.…...

linux---天气爬虫

代码概述 这段代码实现了一个天气查询系统&#xff0c;支持实时天气、未来天气和历史天气查询。用户可以通过终端菜单选择查询类型&#xff0c;并输入城市名称来获取相应的天气信息。程序通过 TCP 连接发送 HTTP 请求&#xff0c;并解析返回的 JSON 数据来展示天气信息。 #in…...

C++蓝桥杯基础篇(九)

片头 嗨&#xff01;小伙伴们&#xff0c;大家好~ 今天我们将学习蓝桥杯基础篇&#xff08;十&#xff09;&#xff0c;学习函数相关知识&#xff0c;准备好了吗&#xff1f;咱们开始咯&#xff01; 一、函数基础 一个典型的函数定义包括以下部分&#xff1a;返回类型、函数名…...

Java Spring MVC (2)

常见的Request Controller 和 Response Controller 的区别 用餐厅点餐来理解 想象你去一家餐厅吃饭&#xff1a; Request Controller&#xff08;接单员&#xff09;&#xff1a;负责处理你的点餐请求&#xff0c;记录你的口味、桌号等信息。Response Controller&#xff08…...

Elasticsearch:使用 BigQuery 提取数据

作者&#xff1a;来自 Elastic Jeffrey Rengifo 了解如何使用 Python 在 Elasticsearch 中索引和搜索 Google BigQuery 数据。 BigQuery 是 Google 的一个平台&#xff0c;允许你将来自不同来源和服务的数据集中到一个存储库中。它还支持数据分析&#xff0c;并可使用生成式 AI…...

接口-菜品分页查询

业务内容 页面上菜品根据菜品名称、菜品分类、售卖状态三个字段进行分页查询。 在请求参数中携带了菜品名称、菜品分类、售卖状态三个字段参数。 返回PageResult类型的实体。 注意&#xff1a;在返回数据中在records下有个categoryName&#xff0c;这个字段的内容在category…...

springboot3 RestClient、HTTP 客户端区别

1 RestClient使用 RestClient 是 Spring 6.1 M2 中引入的同步 HTTP 客户端&#xff0c;它取代了 RestTemplate。同步 HTTP 客户端以阻塞方式发送和接收 HTTP 请求和响应&#xff0c;这意味着它会等待每个请求完成后才继续下一个请求。本文将带你了解 RestClient 的功能以及它与…...

自我训练模型:通往未来的必经之路?

摘要 在探讨是否唯有通过自我训练模型才能掌握未来的问题时&#xff0c;文章强调了底层技术的重要性。当前&#xff0c;许多人倾向于关注应用层的便捷性&#xff0c;却忽视了支撑这一切的根本——底层技术。将模型简单视为产品是一种短视行为&#xff0c;长远来看&#xff0c;理…...

RuoYi框架添加自己的模块(学生管理系统CRUD)

RuoYi框架添加自己的模块&#xff08;学生管理系统&#xff09; 框架顺利运行 首先肯定要顺利运行框架了&#xff0c;这个我不多说了 设计数据库表 在ry数据库中添加表tb_student 表字段如图所示 如图所示 注意id字段是自增的 注释部分是后面成功后前端要展示的部分 导入…...

linux查看python版本

1.查看Linux是否安装python yum list all | grep python 2.Linux安装python yum install python 3.Linux查看python版本 python -V...

算法题(89):单项链表

审题&#xff1a; 本题需要我们实现一个可以执行三个指令的数据结构来解决这里的问题 思路&#xff1a; 方法一&#xff1a;利用数组模拟链表 由于这里涉及插入删除操作&#xff0c;所以我们不能使用数组结构存储数据&#xff0c;这样子会超时&#xff0c;所以我们就利用数组来…...

开源之夏经验分享|Koupleless 社区黄兴抗:在开源中培养工程思维

开源之夏经验分享&#xff5c;Koupleless 社区黄兴抗&#xff1a;在开源中培养工程思维 文|黄兴抗 电子信息工程专业 Koupleless 社区贡献者 就读于南昌师范学院&#xff0c;电子信息工程专业的大三学生。 本文 2634 字&#xff0c;预计阅读 7​ 分钟​ 今天 SOFAStack 邀…...

体验开源openeuler openharmony stratovirt模拟器

文档 openeuler社区面向数字基础设施的开源操作系统 openharmony社区 OpenHarmony是由开放原子开源基金会&#xff08;OpenAtom Foundation&#xff09;孵化及运营的开源项目, 目标是面向全场景、全连接、全智能时代、基于开源的方式&#xff0c;搭建一个智能终端设备操作系统…...

【AI实践】基于TensorFlow/Keras的CNN(卷积神经网络)简单实现:手写数字识别的工程实践

深度神经网络系列文章 【AI深度学习网络】卷积神经网络&#xff08;CNN&#xff09;入门指南&#xff1a;从生物启发的原理到现代架构演进【AI实践】基于TensorFlow/Keras的CNN&#xff08;卷积神经网络&#xff09;简单实现&#xff1a;手写数字识别的工程实践 引言 在深度…...

深入探讨AI-Ops架构 第一讲 - 运维的进化历程以及未来发展趋势

首先&#xff0c;让我们一起回顾运维的进化之路&#xff0c;然后再深入探讨AI-Ops架构的细节。 运维的进化历程 1. AI 大范围普及前的运维状态 (传统运维) 在AI技术尚未广泛渗透到运维领域之前&#xff0c;我们称之为传统运维&#xff0c;其主要特点是&#xff1a; 人工驱动…...

2025年全球生成式AI消费应用发展趋势报告

原文链接&#xff1a;The Top 100 Gen AI Consumer Apps - 4th Edition | Andreessen Horowitz 核心要点&#xff1a;本报告由a16z发布&#xff0c;深度解析了2025年全球生成式AI消费应用的发展格局&#xff0c;揭示了技术迭代与商业化加速的双重趋势。 报告显示&#xff0c;A…...

VBA 列方向合并单元格,左侧范围大于右侧范围

实现功能如下&#xff1a; excel指定行列范围内的所有单元格 规则1&#xff1a;每一列的连续相同的值合并单元格 规则2&#xff1a;每一列的第一个非空单元格与其下方的所有空白单元格合并单元 规则3&#xff1a;优先左侧列合并单元格&#xff0c;合并后&#xff0c;右侧的单元…...

设计AI芯片架构的入门 研究生入行数字芯片设计、验证的项目 opentitan

前言 这几年芯片设计行业在国内像坐过山车。时而高亢&#xff0c;时而低潮。最近又因为AI的热潮开始high起来。到底芯片行业的规律是如何&#xff1f; 我谈谈自己观点&#xff1a;芯片设计是“劳动密集型”行业。 “EDA和工具高度标准化和代工厂的工艺标准化之后&#xff0c;芯…...

【弹性计算】异构计算云服务和 AI 加速器(二):适用场景

异构计算云服务和 AI 加速器&#xff08;二&#xff09;&#xff1a;适用场景 1.图形处理2.视频处理3.计算4.人工智能 异构计算 目前已经被广泛地应用于生产和生活当中&#xff0c;主要应用场景如下图所示。 1.图形处理 GPU 云服务器在传统的图形处理领域具有强大的优势&…...

JVM常用概念之移动GC和局部性

问题 非移动GC一定比移动GC好吗&#xff1f; 基础知识 移动GC和非移动GC 移动GC 在进行垃圾回收时&#xff0c;为了减少碎片而移动对象来顺利完成垃圾回收的GC。 Serial GC 在单线程环境下&#xff0c;它在标记-清除&#xff08;Mark-Sweep&#xff09;算法的基础上进行…...

微服务保护:Sentinel

home | Sentinelhttps://sentinelguard.io/zh-cn/ 微服务保护的方案有很多&#xff0c;比如&#xff1a; 请求限流 线程隔离 服务熔断 服务故障最重要原因&#xff0c;就是并发太高&#xff01;解决了这个问题&#xff0c;就能避免大部分故障。当然&#xff0c;接口的并发…...

使用Wireshark截取并解密摄像头画面

在物联网&#xff08;IoT&#xff09;设备普及的今天&#xff0c;安全摄像头等智能设备在追求便捷的同时&#xff0c;往往忽视了数据传输过程中的加密保护。很多摄像头默认通过 HTTP 协议传输数据&#xff0c;而非加密的 HTTPS&#xff0c;从而给潜在攻击者留下了可乘之机。本文…...

IDEA 基础配置: maven配置 | 服务窗口配置

文章目录 IDEA版本与MAVEN版本对应关系maven配置镜像源插件idea打开服务工具窗口IDEA中的一些常见问题及其解决方案IDEA版本与MAVEN版本对应关系 查找发布时间在IDEA版本之前的dea2021可以使用maven3.8以及以前的版本 比如我是idea2021.2.2 ,需要将 maven 退到 apache-maven-3.…...

20250-3-8 树的存储结构

一、树的逻辑结构回顾 树&#xff1a;一个分支结点可以有多课子树 如果按照二叉树的存储来实现树的存储&#xff0c;则只依靠数组下标&#xff0c;无法反映结点之间的逻辑关系。 二、双亲表示法&#xff08;顺序存储&#xff09; 1.因此&#xff1a;我们可以用链式存储的方法&…...

Visual-RFT视觉强化微调:用「试错学习」教会AI看图说话

&#x1f4dc; 文献卡 英文题目: Visual-RFT: Visual Reinforcement Fine-Tuning;作者: Ziyu Liu; Zeyi Sun; Yuhang Zang; Xiaoyi Dong; Yuhang Cao; Haodong Duan; Dahua Lin; Jiaqi WangDOI: 10.48550/arXiv.2503.01785摘要翻译: 像OpenAI o1这样的大型推理模型中的强化微调…...

PDF处理控件Aspose.PDF,如何实现企业级PDF处理

PDF处理为何成为开发者的“隐形雷区”&#xff1f; “手动调整200页PDF目录耗时3天&#xff0c;扫描件文字识别错误导致数据混乱&#xff0c;跨平台渲染格式崩坏引发客户投诉……” 作为开发者&#xff0c;你是否也在为PDF处理的复杂细节消耗大量精力&#xff1f;Aspose.PDF凭…...

DeepSeek-R1本地化部署(Mac)

一、下载 Ollama 本地化部署需要用到 Ollama&#xff0c;它能支持很多大模型。官方网站&#xff1a;https://ollama.com/ 点击 Download 即可&#xff0c;支持macOS,Linux 和 Windows&#xff1b;我下载的是 mac 版本&#xff0c;要求macOS 11 Big Sur or later&#xff0c;Ol…...