Linux 文件系列:深入理解文件描述符fd,重定向,自定义shell当中重定向的模拟实现

Linux 文件系列:深入理解文件fd,重定向,自定义shell当中重定向的模拟实现

  • 一.预备知识
  • 二.回顾C语言中常见的文件接口跟重定向建立联系
    • 1.fopen函数的介绍
    • 2.fclose函数的介绍
    • 3.代码演示
      • 1.以"w"(写)的方式打开
      • 2.跟输出重定向的联系
      • 3.以 "a"(追加)的方式打开
      • 4.跟追加重定向的联系
  • 三.认识并使用系统接口
    • 1.open
      • 1.open和fopen的联系(引出 FILE和struct file的联系)
    • 2.open的进一步介绍
    • 3.open函数的使用
      • 1.close函数
      • 2.开始使用并且看看这个fd到底是什么?
  • 四.理解文件描述符fd
    • 1.文件描述符fd的本质
    • 2.标准输入,标准输出,标准错误
    • 3.理解Linux下一切皆文件的设计理念
  • 五.理解struct file内核数据结构
  • 六.fd的分配规则
    • 1.先抛出结论
    • 2.代码演示
    • 3.替换标准输出时的现象
  • 七.理解重定向
    • 1.重定向的本质
    • 2.演示一下重定向
      • 1.输出重定向
      • 2.追加重定向
      • 3.输入重定向
        • 1.fread函数
        • 2.演示
  • 八.dup2函数:实现两个fd之间的重定向
    • 1.dup2实现输出重定向
    • 2.dup2实现追加重定向
    • 3.dup2实现输入重定向
  • 九.自定义shell当中重定向的模拟实现
    • 1.原myshell.c代码
    • 2.如何实现重定向
    • 3.定义全局变量
    • 4.检测是否要进行重定向的函数
    • 5.创建子进程进行程序替换的函数修改
    • 6.main函数的修改
    • 7.修改之后myshell.c代码
  • 十.stderr的作用
    • 1.介绍2>&1
    • 2.stderr的作用
    • 3.演示
  • 十一.重定向和程序替换之间是互不影响的

一.预备知识

在这里插入图片描述
经过刚才的分析,我们可以一个很重要的结论:

一个文件要被打开,一定要先在OS中形成被打开的文件对象

下面我们来回顾一下C语言中常见的文件接口
我们会发现重定向跟它们有所联系

二.回顾C语言中常见的文件接口跟重定向建立联系

关于C语言文件操作的详细内容,大家可以看我的这篇博客:
C语言文件操作详解

1.fopen函数的介绍

在这里插入图片描述
在这里插入图片描述

2.fclose函数的介绍

在这里插入图片描述

3.代码演示

1.以"w"(写)的方式打开

在这里插入图片描述

以"w"(写)的方式打开,如果文件不存在,就会在当前进程所在的路径当中创建它

在这里插入图片描述
创建成功
我们用vim写一些内容,再用w打开,看看w是否会清空之前的内容
在这里插入图片描述
在这里插入图片描述
清空成功

2.跟输出重定向的联系

在这里插入图片描述
在这里插入图片描述
我们会发现,fopen的"w"选项跟输出重定向很像啊
下面我们再来看看"a"选项的方式打开跟追加重定向的关系

3.以 “a”(追加)的方式打开

"a"也是写入,不过是从文件结尾处开始写入,是追加式写入,并不会清空文件

在这里插入图片描述
在这里插入图片描述
并没有清空原有内容

4.跟追加重定向的联系

在这里插入图片描述
在这里插入图片描述
我们会发现,fopen的"a"选项跟追加重定向很像啊

三.认识并使用系统接口

下面我们来认识并使用一下系统调用接口
首先我们达成1个共识:

C语言的文件操作接口,它的底层一定封装了系统调用接口

1.open

1.open和fopen的联系(引出 FILE和struct file的联系)

这是C语言提供的库函数:fopen:
在这里插入图片描述
这是系统调用接口:open:
在这里插入图片描述
可见,这个fd跟我们之前常用的FILE*指针很像啊,
其实它们的功能是一样的,C语言的FILE是一个结构体,这个结构体里面封装了fd
而这个fd是被打开的文件的结构体(struct file内核数据结构)中的一个属性,是用来区分不同文件的

2.open的进一步介绍

刚才我们还没有介绍第2个参数呢,下面我们来看一下
在这里插入图片描述
至此我们也理解了fopen是如何对open进行封装的
下面我们来使用一下open函数并且看看这个fd到底是啥啊?

3.open函数的使用

1.close函数

在这里插入图片描述

2.开始使用并且看看这个fd到底是什么?

在这里插入图片描述
在这里插入图片描述
现在我们有了两个问题:

  1. 0 1 2去哪了?
  2. 为什么会是 3 4 5 6?

下面就让我们借助这两个问题来深入理解一下文件描述符fd

四.理解文件描述符fd

1.文件描述符fd的本质

在这里插入图片描述

2.标准输入,标准输出,标准错误

在C语言的学习中我们都听说过

C语言程序(也就是进程),只要运行起来,默认就打开3个流

在这里插入图片描述
今天我们要说明的是:
在这里插入图片描述

3.理解Linux下一切皆文件的设计理念

在这里插入图片描述

五.理解struct file内核数据结构

在这里插入图片描述

六.fd的分配规则

1.先抛出结论

在这里插入图片描述

2.代码演示

分配规则1就不言而喻了,我们来验证分配规则2
在这里插入图片描述
我们先关闭stdin,然后在打开log.txt
如果该进程中log.txt被分配的fd是0,那么验证成功
在这里插入图片描述
验证成功

3.替换标准输出时的现象

下面我们先关闭stdout,然后再打开log.txt
在这里插入图片描述
在这里插入图片描述
为什么最后的

printf("log.txt的fd是: %d\n",fd);

没有成功打印呢?

因为stdout是标准输出流,是显示器对应的流,
我们平常printf是将字符串打印到stdout当中,但是我们在printf之前已经把stdout关掉了
所以不会打印到显示器

可是当我加了一行代码
在这里插入图片描述
cat log.txt之后
在这里插入图片描述
发现刚才printf中本来要往显示器上打印的数据现在写到了log.txt里面
这说明:

1.printf只认识stdout,也就是fd为1的文件2.上层的fd并没有改变,但是底层fd指向的内容发生改变了
本来fd值为1的这个fd应该要指向显示器这个设备文件的
但是在这个进程当中  现在指向log.txt了3.也就是说这个过程其实就是进行了一种类似于狸猫换太子式的指向的改变

七.理解重定向

1.重定向的本质

经由刚才的print的例子之后,我们可以发现:
由此可以得出重定向的本质:
在这里插入图片描述

重定向的本质,其实就是修改特定文件fd的指向

2.演示一下重定向

1.输出重定向

这是log.txt之前的数据
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
输出重定向成功

2.追加重定向

在这里插入图片描述
在这里插入图片描述
追加重定向成功

3.输入重定向

要进行输入重定向,我们要使用fread函数

1.fread函数

在这里插入图片描述

2.演示

在这里插入图片描述
在这里插入图片描述
输入重定向成功

八.dup2函数:实现两个fd之间的重定向

其实库里面给我们提供了一个函数dup2
可以实现两个fd之间的重定向
下面我们使用dup2函数再来演示一下重定向
在这里插入图片描述

1.dup2实现输出重定向

在这里插入图片描述
在这里插入图片描述
此时log.txt的fd是3

2.dup2实现追加重定向

在这里插入图片描述
在这里插入图片描述
实现成功

3.dup2实现输入重定向

在这里插入图片描述
在这里插入图片描述
实现成功

九.自定义shell当中重定向的模拟实现

经过上面的练习之后,下面我们修改一下我们的myshell.c代码,模拟实现一下重定向
关于myshell.c代码的实现,大家可以看我的博客当中的
Linux自定义shell的编写,里面实现了自定义shell

1.原myshell.c代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
//#define DEBUG 1
#define SEP " "char cwd[1024]={'\0'};
int lastcode=0;//上一次进程退出时的退出码char env[1024][1024]={'\0'};
int my_index=0;const char* getUsername()
{const char* username=getenv("USER");if(username==NULL) return "none";return username;
}const char* getHostname()
{const char* hostname=getenv("HOSTNAME");if(hostname==NULL) return "none";return hostname;
}const char* getPwd()
{const char* pwd=getenv("PWD");if(pwd==NULL) return "none";return pwd;
}//分割字符串填入usercommand数组当中
//例如: "ls -a -l" 分割为"ls" "-a" "-l"
void CommandSplit(char* usercommand[],char* command)
{int i=0;usercommand[i++]=strtok(command,SEP);while(usercommand[i++]=strtok(NULL,SEP));
}//解析命令行
void GetCommand(char* command,char* usercommand[])
{command[strlen(command)-1]='\0';//清理掉最后的'\0'CommandSplit(usercommand,command);
#ifdef DEBUGint i=0;while(usercommand[i]!=NULL){printf("%d : %s\n",i,usercommand[i]);i++;}
#endif
}//创建子进程,完成任务
void Execute(char* usercommand[])
{pid_t id=fork();if(id==0){//子进程执行部分execvp(usercommand[0],usercommand);//如果子进程程序替换失败,已退出码为1的状态返回exit(1);}else{//父进程执行部分int status=0;//阻塞等待pid_t rid=waitpid(id,&status,0);if(rid>0){lastcode=WEXITSTATUS(status);}}
}void cd(char* usercommand[])
{chdir(usercommand[1]);char tmp[1024]={'\0'};getcwd(tmp,sizeof(tmp));sprintf(cwd,"PWD=%s",tmp);putenv(cwd);lastcode=0;
}   int echo(char* usercommand[])
{//1.echo后面什么都没有,相当于'\n'if(usercommand[1]==NULL){printf("\n");lastcode=0;return 1;}//2.echo $?  echo $PWD echo $char* cmd=usercommand[1];int len=strlen(cmd);if(cmd[0]=='$' && len>1){//echo $?if(cmd[1]=='?'){printf("%d\n",lastcode);lastcode=0;}//echo $PWDelse{char* tmp=cmd+1;const char* env=getenv(tmp);//找不到该环境变量,打印'\n',退出码依旧为0if(env==NULL){printf("\n");}else{printf("%s\n",env);}lastcode=0;}}else{printf("%s\n",cmd);}return 1;
}void export(char* usercommand[])
{//exportif(usercommand[1]==NULL){lastcode=0;return;}strcpy(env[my_index],usercommand[1]);putenv(env[my_index]);my_index++;
}int doBuildIn(char* usercommand[])
{//cdif(strcmp(usercommand[0],"cd")==0){if(usercommand[1]==NULL) return -1;cd(usercommand);return 1;}//echoelse if(strcmp(usercommand[0],"echo")==0){return echo(usercommand);}//exportelse if(strcmp(usercommand[0],"export")==0){export(usercommand);}return 0;
}int main()
{while(1){//1.打印提示符信息并获取用户的指令printf("[%s@%s %s]$ ",getUsername(),getHostname(),getPwd());char command[1024]={'\0'};fgets(command,sizeof(command),stdin);char* usercommand[1024]={NULL};//2.解析command字符串,放入usercommand指针数组当中GetCommand(command,usercommand);//3.检测并执行内建命令,如果是内建命令并成功执行,返回1,未成功执行返回-1,不是内建返回0int flag=doBuildIn(usercommand);//返回值!=0说明是内建命令,无需执行第4步if(flag!=0) continue;//4.创建子进程,交由子进程完成任务Execute(usercommand);}return 0;
}

2.如何实现重定向

以输出重定向为例:

指令 > log.txt

输出重定向的作用其实就是把本来应该往显示器上打印的内容打印到了log.txt上
也就是说进行输出重定向的话,我们的log.txt就替代了显示器的位置
也就是说执行指令之前我们只需要执行一个
dup2(fd,1)即可
fd是log.txt的文件描述符,1是显示器的文件描述符

也就是说对于用户输入的一个完整的指令
例如:

ls -a -l > log.txt

在这里插入图片描述
我们要做的是:
1.检测是否需要进行重定向(检测指令当中是否有> 或者 >> 或者<)
2.如果需要,把这个指令拆分为两部分
“ls -a -l"和"log.txt”
后半部分是重定向到哪个文件当中
前半部分是真正的指令
如何拆分呢?把>改为’\0’,>>改为’\0’>,<改为’\0’即可

注意:

ls -a -l >                        log.txt

这样写也是可以的,因此我们要取出log.txt的时候要跳过空格

3.定义全局变量

第一步:
我们定义全局变量redir和四个宏常量,文件名和跳过空格的宏
在这里插入图片描述
在这里插入图片描述
注意:
在解析命令行之前就要检测是否要进行重定向
因为如果要进行重定向,就会对命令行进行拆分,拆分之后的指令才是真正要执行的指令

在后续执行指令时只需要根据全局变量redir是否是NoneRedir来判断是否要进行重定向
如果要进行重定向,根据redir具体的值来判断要进行输出/追加/输入重定向
进而判断filename的打开方式和dup2要覆盖显示器还是键盘

然后分类打开和覆盖即可

4.检测是否要进行重定向的函数

//跳过空格的宏
#define SKIP_SPACE(pos) do{ while(isspace(*pos)) pos++; }while(0)//检测是否要进行重定向
void CheckRedir(char* command)
{int len=strlen(command);char* start=command,*end=command+len-1;while(end>=start){//输入重定向//cat < log.txtif(*end=='<'){*end='\0';filename=end+1;SKIP_SPACE(filename);redir=InputRedir;break;}else if(*end=='>'){//追加重定向//ls -a -l >> log.txtif(end>start && *(end-1)=='>'){*(end-1)='\0';filename=end+1;SKIP_SPACE(filename);redir=AppendRedir;break;}//输出重定向else{*end='\0';filename=end+1;SKIP_SPACE(filename);redir=OutPutRedir;break;}}else{end--;}}
}

在这里插入图片描述
在这里我们就只演示非内建命令的重定向操作了
因为只演示非内建命令就能够做到让大家很好地去理解重定向了

5.创建子进程进行程序替换的函数修改

在这里插入图片描述

//创建子进程,完成任务
void Execute(char* usercommand[])
{pid_t id=fork();if(id==0){//检测是否要进行重定向int fd=0;//输出重定向if(redir==OutPutRedir){fd=open(filename,O_WRONLY | O_CREAT | O_TRUNC,0666);dup2(fd,1);}//追加重定向if(redir==AppendRedir){fd=open(filename,O_WRONLY | O_CREAT | O_APPEND,0666);dup2(fd,1);}//输入重定向if(redir==InputRedir){fd=open(filename,O_RDONLY);dup2(fd,0);}//子进程执行部分execvp(usercommand[0],usercommand);//如果子进程程序替换失败,已退出码为1的状态返回exit(1);}else{//父进程执行部分int status=0;//阻塞等待pid_t rid=waitpid(id,&status,0);if(rid>0){lastcode=WEXITSTATUS(status);}}
}

6.main函数的修改

在这里插入图片描述

int main()
{while(1){redir=NoneRedir;filename=NULL;//1.打印提示符信息并获取用户的指令printf("[%s@%s %s]$ ",getUsername(),getHostname(),getPwd());char command[1024]={'\0'};fgets(command,sizeof(command),stdin);command[strlen(command)-1]='\0';//清理掉最后的'\n'//2.检测重定向CheckRedir(command);char* usercommand[1024]={NULL};//3.解析command字符串,放入usercommand指针数组当中GetCommand(command,usercommand);//4.检测并执行内建命令,如果是内建命令并成功执行,返回1,未成功执行返回-1,不是内建返回0int flag=doBuildIn(usercommand);//返回值!=0说明是内建命令,无需执行第4步if(flag!=0) continue;//5.创建子进程,交由子进程完成任务Execute(usercommand);}return 0;
}

7.修改之后myshell.c代码

模拟实现重定向的目的是为了让我们更好地去理解重定向
因此本次实现重定向只是简单的模拟实现,跟系统的重定向并不完全相同

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <ctype.h>
#include <sys/stat.h>
#include <fcntl.h>
//#define DEBUG 1#define SEP " "#define NoneRedir 0
#define OutPutRedir 1
#define AppendRedir 2
#define InputRedir 3
int redir=NoneRedir;char* filename=NULL;char cwd[1024]={'\0'};
int lastcode=0;//上一次进程退出时的退出码char env[1024][1024]={'\0'};
int my_index=0;const char* getUsername()
{const char* username=getenv("USER");if(username==NULL) return "none";return username;
}const char* getHostname()
{const char* hostname=getenv("HOSTNAME");if(hostname==NULL) return "none";return hostname;
}const char* getPwd()
{const char* pwd=getenv("PWD");if(pwd==NULL) return "none";return pwd;
}//分割字符串填入usercommand数组当中
//例如: "ls -a -l" 分割为"ls" "-a" "-l"
void CommandSplit(char* usercommand[],char* command)
{int i=0;usercommand[i++]=strtok(command,SEP);while(usercommand[i++]=strtok(NULL,SEP));
}//解析命令行
void GetCommand(char* command,char* usercommand[])
{if(strlen(command)==0) return;CommandSplit(usercommand,command);
#ifdef DEBUGint i=0;while(usercommand[i]!=NULL){printf("%d : %s\n",i,usercommand[i]);i++;}
#endif
}//创建子进程,完成任务
void Execute(char* usercommand[])
{pid_t id=fork();if(id==0){//检测是否要进行重定向int fd=0;//输出重定向if(redir==OutPutRedir){fd=open(filename,O_WRONLY | O_CREAT | O_TRUNC,0666);dup2(fd,1);}//追加重定向if(redir==AppendRedir){fd=open(filename,O_WRONLY | O_CREAT | O_APPEND,0666);dup2(fd,1);}//输入重定向if(redir==InputRedir){fd=open(filename,O_RDONLY);dup2(fd,0);}//子进程执行部分execvp(usercommand[0],usercommand);//如果子进程程序替换失败,已退出码为1的状态返回exit(1);}else{//父进程执行部分int status=0;//阻塞等待pid_t rid=waitpid(id,&status,0);if(rid>0){lastcode=WEXITSTATUS(status);}}
}void cd(char* usercommand[])
{chdir(usercommand[1]);char tmp[1024]={'\0'};getcwd(tmp,sizeof(tmp));sprintf(cwd,"PWD=%s",tmp);putenv(cwd);lastcode=0;
}   int echo(char* usercommand[])
{//1.echo后面什么都没有,相当于'\n'if(usercommand[1]==NULL){printf("\n");lastcode=0;return 1;}//2.echo $?  echo $PWD echo $char* cmd=usercommand[1];int len=strlen(cmd);if(cmd[0]=='$' && len>1){//echo $?if(cmd[1]=='?'){printf("%d\n",lastcode);lastcode=0;}//echo $PWDelse{char* tmp=cmd+1;const char* env=getenv(tmp);//找不到该环境变量,打印'\n',退出码依旧为0if(env==NULL){printf("\n");}else{printf("%s\n",env);}lastcode=0;}}else{printf("%s\n",cmd);}return 1;
}int doBuildIn(char* usercommand[])
{if(usercommand[0]==NULL) return 0;//cdif(strcmp(usercommand[0],"cd")==0){if(usercommand[1]==NULL) return -1;cd(usercommand);return 1;}//echoelse if(strcmp(usercommand[0],"echo")==0){return echo(usercommand);}//exportelse if(strcmp(usercommand[0],"export")==0){//exportif(usercommand[1]==NULL){lastcode=0;return 1;}strcpy(env[my_index],usercommand[1]);putenv(env[my_index]);my_index++;}return 0;
}//跳过空格的宏
#define SKIP_SPACE(pos) do{ while(isspace(*pos)) pos++; }while(0)//检测是否发生了重定向
void CheckRedir(char* command)
{int len=strlen(command);char* start=command,*end=command+len-1;while(end>=start){//输入重定向//cat < log.txtif(*end=='<'){*end='\0';filename=end+1;SKIP_SPACE(filename);redir=InputRedir;break;}else if(*end=='>'){//追加重定向//ls -a -l >> log.txtif(end>start && *(end-1)=='>'){*(end-1)='\0';filename=end+1;SKIP_SPACE(filename);redir=AppendRedir;break;}//输出重定向else{*end='\0';filename=end+1;SKIP_SPACE(filename);redir=OutPutRedir;break;}}else{end--;}}
}int main()
{while(1){redir=NoneRedir;filename=NULL;//1.打印提示符信息并获取用户的指令printf("[%s@%s %s]$ ",getUsername(),getHostname(),getPwd());char command[1024]={'\0'};fgets(command,sizeof(command),stdin);command[strlen(command)-1]='\0';//清理掉最后的'\n'//2.检测重定向CheckRedir(command);char* usercommand[1024]={NULL};//3.解析command字符串,放入usercommand指针数组当中GetCommand(command,usercommand);//4.检测并执行内建命令,如果是内建命令并成功执行,返回1,未成功执行返回-1,不是内建返回0int flag=doBuildIn(usercommand);//返回值!=0说明是内建命令,无需执行第4步if(flag!=0) continue;//5.创建子进程,交由子进程完成任务Execute(usercommand);}return 0;
}

十.stderr的作用

首先先介绍一下2>&1这一语法

1.介绍2>&1

下面我们用fprintf来演示一下
在这里插入图片描述
在这里插入图片描述
如果我们现在就是想要把标准错误和标准输出都往显示器上打印呢?

./mycmd > log.txt 2>&1

在这里插入图片描述
又因为我们先把1重定向到log.txt中,再把2重定向到1中
因此就做到把2和1中的内容全都往log.txt中打印了

2.stderr的作用

我们平常学习编程的时候,程序写的并不大
程序运行时的错误信息和正常信息我们都统一往显示器上打印了
可是一旦程序特别大,要打印的信息特别多,此时区分显示器上的正常信息和错误信息就很麻烦了

而区分正常信息和错误信息之后就能够方便我们对错误信息进行统一排查,提高效率

因此标准输出的作用是:接收打印的正常信息
标准错误的作用是接收打印的错误信息

3.演示

还是刚才那份代码
现在我们想把正常信息重定向到log.txt中
错误信息重定向到log.txt.error中

./mycmd 1>log.txt 2>log.txt.error
把1重定向给log.txt
把2重定向给log.txt.error

在这里插入图片描述
注意:这样重定向时不能带空格
也就是说不能这样写:

./mycmd 1 > log.txt 2 > log.txt.error

在这里插入图片描述

十一.重定向和程序替换之间是互不影响的

为什么它们之间是互不影响的呢?
因为程序替换时改变的是进程结构体当中的页表中虚拟地址空间和物理地址空间的映射和进程地址空间中的相关属性

而重定向改变的是文件描述符表中fd的指向
两者互不影响

以上就是Linux 文件系列:深入理解文件fd,重定向,自定义shell当中重定向的模拟实现的全部内容,希望能对大家有所帮助!

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

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

相关文章

【JVM】JVM相关机制

1. JVM内存区域划分 1.1 内存区域划分简介 内存区域划分&#xff1a;实际上JVM也是一个进程&#xff0c;进程运行时需要向操作系统申请一些系统资源&#xff08;内存就是典型的资源&#xff09;&#xff0c;这些内存空间就支撑着后续Java程序的运行&#xff0c;而这些内存又会…

WiFi|硬体:茶凳浅谈-高通802.11be WLAN AP Chipsets 参考设计与boardData之间的映射

前言: WiFi|硬体:茶凳浅谈-高通Wi-Fi 7立项前的选型 博文中提到一些选型的组合&#xff0c;比如: 主芯片的搭配IPQ9554 QCN9274 (2x2 2.4G, 2x2 5G) QCN9272 (2x2 6G) 主芯片的搭配IPQ9574 QCN9274 (2x2 2.4G, 2x2 5GL) QCN9274 (2x2 5GH, 2x2 6G) 红色标示出差异的部分…

map和set基本使用

map和set基本使用 二叉搜索树setmultisetmapmultimap 二叉搜索树 在学习map和和set的使用之前&#xff0c;我们需要对二叉搜索树有一定的了解。 二叉搜索树也称二叉排序树或者二叉查找树&#xff0c;如果该树不是空树&#xff0c;就应该满足以下条件&#xff1a;非空左子树的所…

2024.3.4

思维导图 作业1&#xff1a;广播 发送端&#xff1a; #include<myhead.h> int main(int argc, const char *argv[]) {//创建套接字int sfd socket(AF_INET,SOCK_DGRAM,0);if(sfd -1){perror("sockeet error");return -1;}//设置当前套接字允许广播属性int …

nvm安装和使用保姆级教程(详细)

一、 nvm是什么 &#xff1a; nvm全英文也叫node.js version management&#xff0c;是一个nodejs的版本管理工具。nvm和npm都是node.js版本管理工具&#xff0c;为了解决node.js各种版本存在不兼容现象可以通过它可以安装和切换不同版本的node.js。 二、卸载之前安装的node: …

pytorch(五)逻辑斯蒂回归

文章目录 模型损失函数代码和结果 逻辑斯蒂回归解决的事分类问题&#xff0c;分类输出的是类别的概率 模型 在线性模型中&#xff0c;通过 y w x b ywxb ywxb输出的是一个实数值&#xff0c;但是在分类问题中&#xff0c;输出的是类别的概率&#xff0c;所以需要一个函数&am…

社交媒体的未来图景:探索Facebook的数字化之旅

社交媒体已经成为现代社会不可或缺的一部分&#xff0c;其影响力已经深入到人们生活的方方面面。而在众多社交媒体平台中&#xff0c;Facebook无疑是其中的巨头&#xff0c;其数字化之旅更是引领着整个社交媒体行业的发展方向。本文将深入探讨社交媒体的未来图景&#xff0c;以…

稳定性建设

开篇 SLA&#xff08;service-level agreement&#xff0c;即 服务级别协议&#xff09;也称服务等级协议&#xff0c;经常被用来衡量服务稳定性指标。业界高可用的标准是按照系统宕机时间来衡量的&#xff0c;通常被称作“几个 9”&#xff0c;9 越多代表服务全年可用时间越长…

Linux运维工具-ywtool默认功能介绍

提示:工具下载链接在文章最后 目录 一.资源检查二.日志刷新三.工具升级四.linux运维工具ywtool介绍五.ywtool工具下载链接 一.资源检查 只要系统安装了ywtool工具,默认就会配置上"资源检查"的脚本资源检查脚本的执行时间:每天凌晨3点进行检查资源检查脚本的检查内容…

如何使用公网地址远程访问内网Nacos UI界面查看注册服务

文章目录 1. Docker 运行Nacos2. 本地访问Nacos3. Linux安装Cpolar4. 配置Nacos UI界面公网地址5. 远程访问 Nacos UI界面6. 固定Nacos UI界面公网地址7. 固定地址访问Plik Nacos是阿里开放的一款中间件,也是一款服务注册中心&#xff0c;它主要提供三种功能&#xff1a;持久化…

Oracle 如何将txt文件中的数据导入数据库

使用文本导入器&#xff0c;可以将ASCII文件导入数据库。支持大多数面向行的格式&#xff0c;如逗号和制表符分隔的字段。导入程序将尝试自动确定文件格式&#xff0c;因此大多数时候您不会抰 需要定义任何内容&#xff0c;只需选择文件&#xff0c;选择一个表&#xff0c;然后…

三级分销数据库设计

一&#xff0c;数据结构 二&#xff0c;查询方法 1.mysql递归查询 获取id9的所有上级 r : 9 设置自己所要搜索子节点的id SELECTT2.* FROM(SELECTr AS _id,( SELECT r : pid FROM sj_user WHERE id _id ) AS 2v2,l : l 1 AS lvl FROM( SELECT r : 9 ) vars, -- 查询id为…