Linux进程间的通信

IPC 即 Inter-Process Communication,也就是进程间通信,它指的是在不同进程之间进行数据交换和协调同步的机制。在操作系统里,每个进程都有自己独立的内存空间,一般情况下不能直接访问其他进程的内存,所以需要借助 IPC 机制来实现进程间的信息交互与协作。
进程间通信常用的几种方式
1.管道通信:有名管道,无名管道
2.消息队列
3.共享内存
4.信号量
5.套接字

管道

管道的本质是内核中的一块缓冲区,它在内核空间中开辟了一段连续的内存区域,用于存储进程间传输的数据。在用户程序看来,管道表现为一对文件描述符,一个用于读操作,另一个用于写操作,进程可以像操作普通文件一样对管道进行读写操作,但实际上数据是在内存中的缓冲区里进行传输的。

匿名管道

数据传输 :当一个进程向管道的写端写入数据时,数据会被复制到内核的管道缓冲区中;另一个进程从管道的读端读取数据时,数据会从管道缓冲区复制到该进程的用户空间。
亲缘关系要求 :匿名管道只能用于具有亲缘关系的进程之间,如父子进程。这是因为创建管道后,只有创建管道的进程及其子进程才能访问这对文件描述符,其他进程无法获取这些文件描述符,也就无法访问该管道。

创建

匿名管道通过 pipe() 系统调用创建。pipe() 函数会在内核中创建一个管道,并返回两个文件描述符,一个是读端文件描述符(通常用 fd[0] 表示),另一个是写端文件描述符(通常用 fd[1] 表示)

复制代码
#include <unistd.h>

int pipe(int fd[2])

/*fd‐传出参数:
  fd[0]‐读端
  fd[1]‐写端
  返回值:
  0:成功
  ‐1:创建失败*/

管道的读写

读操作

有数据

read(fd[1]) 正常读,返回读出的字节数

无数据

写端被全部关闭,read返回0,相当于读文件到了尾部

没有全部关闭,read阻塞

写操作

读端全部关闭

管道破裂,进程被终止

内核给当前进程发送信号SIGPIPE-13,默认处理动作
读端没全部关闭

缓冲区写满了,write阻塞

缓冲区没满,write继续写,直到写满,阻塞

如何设置非阻塞?

在管道的读写操作中,默认情况下是阻塞模式,即当管道没有数据可读或者管道已满无法写入时,相应的读或写操作会使进程阻塞等待。不过,你可以将管道设置为非阻塞模式,在这种模式下,当无法进行读写操作时,操作会立即返回而不会阻塞进程。

fcntl-变参函数

复制文件描述符-dup

修改文件属性-open的时候对应flag属性

设置方法

复制代码
//获取原来的flags
int flags = fcntl(fd[0],F_GETFL);

//设置新的flags
flags |=O_NONBLOCK;

//将新的flags设置给文件描述符
fcntl(fd[0],F_SETFL,flags);

查看管道缓冲区的大小

命令

ulimit -a

函数

fpathconf

复制代码
 long size = fpathconf(fd[1],_PC_PIPE_BUF);
 printf("size is %ld\n",size);

举例:

父子进程使用管道通信

实现 ps aux| grep "bash"

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
        int ret;
        int fd[2];
        ret = pipe(fd);
        if(ret == -1)
        {
                printf("create pipe failed!\n");
                exit(1);
        }
        pid_t pid;
        pid = fork();
        if(pid == -1)
        {
                printf("fork failed!\n");
                exit(1);
        }
        //ps aux
        if(pid>0)
        {
                close(fd[0]);
                dup2(fd[1],STDOUT_FILENO);//数据重定向
                execlp("ps","ps","aux",NULL);
                perror("execlp");
                exit(1);
        }
        //grep "hash"
        else if(pid == 0)
        {
                close(fd[1]);
                dup2(fd[0],STDIN_FILENO);
                execlp("grep","grep","bash","--color=auto",NULL);
        }

//      long size = fpathconf(fd[1],_PC_PIPE_BUF);
//      printf("size is %ld\n",size);

        printf("pipe[0] is %d\n",fd[0]);
        printf("pipe[1] is %d\n",fd[1]);

        close(fd[0]);
        close(fd[1]);


        return 0;
}

有名管道

数据传输: 与匿名管道类似,进程通过打开这个 FIFO 文件来获取管道的文件描述符,然后进行读写操作。数据同样是在内核的管道缓冲区中进行传输。
**无亲缘关系限制:**命名管道可以在任意两个进程之间使用,因为它有一个文件系统中的路径名,不同的进程可以通过该路径名打开同一个 FIFO 文件来访问管道。

创建

有名管道通过 mkfifo() 函数创建,它会在文件系统中创建一个特殊类型的文件(FIFO 文件)。这个文件并不实际存储数据,只是作为一个访问管道的接口。

复制代码
#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char \*filename,mode_t mode);

/*功能:创建管道文件
  参数:管道文件文件名,权限,创建的文件权限仍然和umask有关系。
  返回值:创建成功返回0,创建失败返回-1。*/

fifo文件可以使用io函数进程操作:open/close、read/write

不能执行lseek操作

有名管道的读写

读:

复制代码
int main()
{
        int ret,fd,nread;
        char readBuff[50] = {0};
        ret = mkfifo("/home/cc/ipc/myfifo",0755);
        if(ret == -1 )
        {
                return -1;
        }
        printf("create fifo success!\n");

        fd = open("./myfifo",O_RDONLY);
        if(fd < 0)
        {
                printf("open fifo  failed!\n");
                return -1;
        }
        printf("open fifo success!\n");
        nread = read(fd,readBuff,50);
        printf("read %d byte from fifo %s:\n ",nread,readBuff);
        close(fd);
        return 0;
}

写:

复制代码
int main()
{
        int fd;
        char *str = "hello world!";
        fd = open("./myfifo",O_WRONLY);
        if(fd < 0)
        {
                printf("open fifo  failed!\n");
                return -1;
        }
        printf("open fifo success!\n");
        write(fd,str,strlen(str));
        close(fd);
        return 0;
}

消息队列

特点:

消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级

消息队列独立于发送和接收进程,进程终止时,消息队列及其内容仍存在

消息队列可以实现消息的随机查询,消息不一定要先进先出的次序读取,也可以按消息的类型读取。

相关函数

1.创建或打开消息队列

在以下两种情况下,msgget将创建一个新的消息队列:

如果没有与键值key相对应的消息队列,并且flag中包含了IPC_CREAT标志

key参数为IPC_PRIVATE

复制代码
 #include <sys/types.h>
 #include <sys/ipc.h>
 #include <sys/msg.h>

int msgget(key_t key, int msgflg);

/*参数:
key:和消息队列关联的key值
msgflg:是一个权限标志,表示消息队列的访问权限,它与文件的访问权限一样。msgflg可以与IPC_CREAT做或
操作,表示当key所命名的消息队列不存在时创建一个消息队列,如果key所命名的消息队列存在时,IPC_CREAT标志会被忽略,而只返回一个标识符。
返回值:成功返回队列ID,失败则返回‐1*/

2.发送消息

复制代码
 #include <sys/types.h>
 #include <sys/ipc.h>
 #include <sys/msg.h>

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

/*参数:
msgid:消息队列的ID
msgp:指向消息缓冲区的指针,消息缓冲区的第一个成员必须是 long 类型,表示消息的类型。

常用结构体msgbuf如下:
struct msgbuf
{
  long mtype; //消息类型
  char mtext[N]; //消息正文
}
size:发送的消息正文的字节数
flag:
  IPC_NOWAIT 消息没有发送完成函数也会立即返回
  0:知道发送完成函数才返回
返回值:
  成功:0
  失败:‐1*/

3.接收消息

复制代码
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);

/*参数:
msgid:消息队列的ID
msgp:要接收消息的缓冲区
size:要接收的消息的字节数
msgtype:
    0:接收消息队列中第一个消息
    大于0:接收消息队列中第一个类型为msgtyp的消息
    小于0:接收消息队列中类型值不大于msgtyp的绝对值且类型值又最小的消息。
flag:
    0:若无消息函数一直阻塞
    IPC_NOWAIT:若没有消息,进程会立即返回ENOMSG。
返回值:
    成功:接收到的消息i长度
    出错:‐1*/

4.控制消息队列

复制代码
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgctl(int msqid, int cmd, struct msqid_ds *buf);

/*参数:
msqid:消息队列的队列ID
cmd:
   IPC_STAT:把msgid_ds结构中的数据设置为消息队列的当前关联值,即用消息队列的当前关联值覆
盖msgid_ds的值。
   IPC_SET:如果进程有足够的权限,就把消息列队的当前关联值设置为msgid_ds结构中给出的值
   IPC_RMID:删除消息队列
buf:是指向 msgid_ds 结构的指针,它指向消息队列模式和访问权限的结构
返回值:
   成功:0
   失败:‐1*/

5.ftok函数

系统建立IPC通讯(如消息队列、共享内存时)必须指定一个ID值。通常情况下,该id值通过ftok函数得到。

复制代码
#include <sys/types.h>
#include <sys/ipc.h>

key_t ftok( char * fname, int id )

/*参数:
fname就时你指定的文件名(该文件必须是存在而且可以访问的)。
id是子序号, 虽然为int,但是只有8个比特被使用(0‐255)。
返回值:
当成功执行的时候,一个key_t值将会被返回,否则 ‐1 被返回。*/

举例:

通过父子进程让两个进程间互相收发消息

进程1:

复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

struct msgbuf//magbuf结构体
{
        long mtype;
        char mtest[128];
        char ID[4];

};

int main()
{
        struct msgbuf sendbuf,readbuf;//定义读和写的结构体
        int msgid;
        key_t key;
        int readret;
        pid_t pid;
        key = ftok("a.c",1);//获取key值
        msgid = msgget(key,0755 | IPC_CREAT);//创建消息队列
        if(msgid == -1 )
        {

                printf("create message failed!\n");//创建失败就打印
                return -1;

        }
        printf("create message success! msgid is %d\n\n",msgid);//创建成功就打印
        system("ipcs -q");//查看队列信息

        //init msgbuf
        sendbuf.mtype = 100;//发送消息的类型是100

        pid = fork();//创建子进程
        if(pid>0)//父进程发送类型为100的消息
        {


                while(1)
                {
                        memset(sendbuf.mtest,0,128);//初始化发送消息的数组
                        printf("please input to message queue:\n");
                        fgets(sendbuf.mtest,128,stdin);//向发送消息的数组输入消息
                        //发送消息到消息队列
                        msgsnd(msgid,(void *)&sendbuf,strlen(sendbuf.mtest),0);
                }
        }
        if(pid == 0)//子进程读取类型为200的消息
        {

                while(1)
                {

                        memset(readbuf.mtest,0,128);
                        msgrcv(msgid,(void *)&readbuf,128,200,0);//读取消息到数组
                        //打印读取到的消息
                        printf("receive byte from message queue is: %s\n",readbuf.mtest);

                }

        }
        return 0;
}

进程2(读取类型为100的消息,发送类型为200的消息):

复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

struct msgbuf
{
        long mtype;
        char mtest[128];
        char ID[4];
};

int main()
{
        struct msgbuf sendbuf,readbuf;
        int msgid;
        key_t key;
        int readret;
        pid_t pid;
        key = ftok("a.c",1);
        msgid = msgget(key,0755 | IPC_CREAT);
        if(msgid == -1 )
        {

                printf("create message failed!\n");
                return -1;

        }
        printf("create message success! msgid is %d\n\n",msgid);
        system("ipcs -q");

        //init msgbuf
        sendbuf.mtype = 200;

        pid = fork();
        if(pid == 0)//child process write 200
        {

                while(1)
                {
                        memset(sendbuf.mtest,0,128);
                        printf("please input to message queue:\n");
                        fgets(sendbuf.mtest,128,stdin);
                        //send message to message queue
                        msgsnd(msgid,(void *)&sendbuf,strlen(sendbuf.mtest),0);
                }
        }
        if(pid > 0)//parent process read 100
        {

                while(1)
                {
                        memset(readbuf.mtest,0,128);
                        msgrcv(msgid,(void *)&readbuf,128,100,0);
                        printf("receive byte from message queue is: %s\n",readbuf.mtest);
                }

        }
        return 0;
}

共享内存

概念:

共享内存(Shared Memory)就是允许多个进程访问同一个内存空间,是在多个进程之间共享和传递数据最高效的方式。操作系统将不同进程之间共享内存安排为同一段物理内存,进程可以将共享内存连接到它们自己的地址空间中,如果某个进程修改了共享内存中的数据,其它的进程读到的数据也将会改变。

特点:

1.共享内存创建之后,一直存在于内核中,直到被删除或系统关闭

2.共享内存和管道不一样,读取后,内容仍然在共享内存中

相关函数

1.获取或创建共享内存

复制代码
#include <sys/ipc.h> 
#include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg);

/*参数:
key:IPC_PRIVATE 或 ftok的返回值
size:共享内存区大小
shmflg:同open函数的权限位,也可以用8进制表示法
返回值:
  成功:共享内存段标识符‐‐‐ID‐‐‐文件描述符
  出错:‐1*/

2.把共享内存连接映射到当前进程的地址空间

复制代码
#include <sys/types.h>
#include <sys/shm.h>

void *shmat(int shm_id, const void *shm_addr, int shmflg);

/*参数:
shm_id:ID号
shm_addr:映射到的地址,NULL为系统自动完成的映射
shmflg:
  SHM_RDONLY共享内存只读
  默认是0,表示共享内存可读写
返回值:
  成功:映射后的地址
  失败:NULL*/

3.将进程里的地址映射删除

复制代码
#include <sys/types.h>
#include <sys/shm.h>

int shmdt(const void *shmaddr);

/*参数:
shmid:要操作的共享内存标识符
返回值:
  成功:0
  出错:‐1*/

4.删除共享内存对象

复制代码
#include <sys/ipc.h>
#include <sys/shm.h>

int shmctl(int shm_id, int command, struct shmid_ds *buf);

/*参数:
shmid:要操作的共享内存标识符
cmd :
  IPC_STAT (获取对象属性)‐‐‐ 实现了命令ipcs ‐m
  IPC_SET (设置对象属性)
  IPC_RMID (删除对象) ‐‐‐实现了命令ipcrm ‐m
buf :指定IPC_STAT/IPC_SET时用以保存/设置属性
返回值:
  成功:0
  出错:‐1*/

举例:

不同进程用共享内存进行通信

写消息进程:

复制代码
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
        int shmid;
        key_t key;
        char *p;
        key = ftok("a.c",1);
        if(key < 0)
        {

                printf("ftok failed!\n");
                return -1;
        }
        printf("ftok success! key is : %x\n",key);
        shmid = shmget(key,128,0777 | IPC_CREAT);
        if(shmid < 0)
        {
                printf("create share memory failed!\n");
                return -1;
        }
        printf("create share memory success shmid = %d\n",shmid);
        system("ipcs -m");

        p = (char *) shmat(shmid,NULL,0);
        if(p == NULL)
        {
                printf("shmat function failed!\n");
                return  -2;
        }

        //write to share memory
        fgets(p,128,stdin);
        sleep(10);
        shmdt(p);
        shmctl(shmid,IPC_RMID,0);
        return 0;
}

收消息进程:

复制代码
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>

int main()
{
        int shmid;
        key_t key;
        char *p;
        key = ftok("a.c",1);
        if(key < 0)
        {

                printf("ftok failed!\n");
                return -1;
        }
        printf("ftok success! key is : %x\n",key);
        shmid = shmget(key,128,0);
        if(shmid < 0)
        {
                printf("create share memory failed!\n");
                return -1;
        }
        printf("create share memory success shmid = %d\n",shmid);
        system("ipcs -m");

        p = (char *) shmat(shmid,NULL,0);
        if(p == NULL)
        {
                printf("shmat function failed!\n");
                return  -2;
        }

        printf("share memory data:%s",p);
        shmdt(p);

        return 0;
}

信号

信号通信,其实就是内核向用户空间进程发送信号,只有内核才能发信号,用户空间进程不能发送信号。

使用命令"kill -l"可以看到内核可以发送的信号

信号通信的框架

信号的发送(发送信号进程):kill、raise、alarm

信号的接收(接收信号进程) : pause()、 sleep、 while(1)

信号的处理(接收信号进程) :signal

1.信号的发送(发送信号进程)

kill

复制代码
#include<signal.h>
#include<sys/types.h>
int kill(pid_t pid, int sig);

/*参数:
函数传入值:pid
正数:要接收信号的进程的进程号
0:信号被发送到所有和pid进程在同一个进程组的进程
‐1:信号发给所有的进程表中的进程(除了进程号最大的进程外)
sig:信号
函数返回值:成功 0 出错 ‐1*/

2.发信号给自己

raise: 发信号给自己 == kill(getpid(), sig)

复制代码
#include<signal.h>
#include<sys/types.h>

int raise(int sig);

/*参数:
函数传入值:sig:信号
函数返回值:
成功 0 出错 ‐1*/

3.发送闹钟信号

alarm 与 raise 函数的比较:

相同点:让内核发送信号给当前进程

不同点:

alarm 只会发送SIGALARM信号

alarm 会让内核定时一段时间之后发送信号, raise会让内核立刻发信号

复制代码
#include <unistd.h>

unsigned int alarm(unsigned int seconds)

/*参数:
seconds:指定秒数
返回值:
 成功:如果调用此alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0。
 出错:‐1*/

4.信号的接收

pause 函数的作用是让调用进程暂停执行,直至收到一个能终止该进程或者使进程调用信号处理函数的信号

复制代码
#include <unistd.h>

int pause(void);

//函数返回值 成功:0,出错:‐1

5.信号的处理

收到信号的进程,处理的方式:

1.进程的默认处理方式(内核为用户进程设置的默认处理方式)

A:忽略 B:终止进程 C: 暂停

2.自己的处理方式:

自己处理信号的方法告诉内核,这样你的进程收到了这个信号就会采用你自己的的处理方式

复制代码
#include <signal.h>

void (*signal(int signum, void (*handler)(int)))(int);

/*参数
signum:指定信号
handler
  SIG_IGN:忽略该信号。
  SIG_DFL:采用系统默认方式处理信号
  自定义的信号处理函数指针
函数返回值:
  成功:设置之前的信号处理方式
  出错:‐1
signal函数有二个参数,第一个参数是一个整形变量(信号值),
第二个参数是一个函数指针,是我们自己写的处理函数;
这个函数的返回值是一个函数指针*/

信号量

信号量集合(可以包含多个信号量)IPC对象是一个信号的集合(多个信号量)

1.创建或者获取一个信号量集

复制代码
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semget(key_t key, int nsems, int semflg);

/*函数参数:
key:和信号量集关联的key值
nsems: 信号量集中包含的信号量数目
semflg:信号量集的访问权限
函数返回值:
   成功:信号量集ID
   出错:‐1*/

2.控制信号量集

复制代码
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semctl(int semid, int semnum, int cmd, ...);

/*函数参数:
semid:信号量集ID
semnum: 要修改的信号量集中信号量编号
cmd :
  GETVAL:获取信号量的值
  SETVAL:设置信号量的值
  IPC_RMID:从系统中删除信号量集合
函数返回值:
  成功:0
  出错:‐1*/

/*在使用可变参数时,通常需要一个 union semun 类型的参数,该联合体的定义如下:
union semun {
    int  val;                //Value for SETVAL 
    struct semid_ds *buf;    //Buffer for IPC_STAT, IPC_SET
    unsigned short  *array;  //Array for GETALL, SETALL 
    struct seminfo  *__buf;  //Buffer for IPC_INFO
                                (Linux-specific) 
};*/

参数说明:

semid

这是信号量集的标识符,由 semget 函数返回。它用于唯一标识要操作的信号量集。
semnum

表示信号量集中要操作的信号量的编号,编号从 0 开始。如果 cmd 命令不涉及特定的信号量(如 IPC_STAT、IPC_RMID 等),则该参数会被忽略。
cmd

这是一个整数,指定要执行的操作命令,常见的命令如下:

SETVAL:将信号量集中指定编号的信号量的值设置为 arg.val。

GETVAL:获取信号量集中指定编号的信号量的当前值。

SETALL:将信号量集中所有信号量的值设置为 arg.array 数组中的值。

GETALL:将信号量集中所有信号量的当前值存储到 arg.array 数组中。

IPC_STAT:获取信号量集的状态信息,并将其存储在 arg.buf 指向的 struct semid_ds 结构体中。

IPC_SET:将 arg.buf 指向的 struct semid_ds 结构体中的信息设置为信号量集的新状态。

IPC_RMID:删除指定的信号量集,此时 semnum 和 arg 参数会被忽略。
...(可变参数)

这是一个可变参数,根据 cmd 命令的不同,需要传递不同类型的 union semun 参数。如果 cmd 命令不需要额外的参数(如 IPC_RMID),则可以不传递该参数。

返回值:

若操作成功,不同的 cmd 命令会有不同的返回值:

对于 GETVAL 命令,返回指定信号量的当前值。

对于 GETALL 命令,返回 0。

对于 IPC_STAT、IPC_SET、SETVAL、SETALL、IPC_RMID 等命令,返回 0 表示操作成功。

若操作失败,返回 -1,并设置 errno 以指示错误类型。

PV操作

P 操作(申请资源)

功能:P 操作用于申请资源,当一个进程执行 P 操作时,它会尝试获取一个资源。

执行过程:

首先将信号量的值减 1。

检查信号量的值:

如果信号量的值大于等于 0,说明有可用资源,进程可以继续执行。

如果信号量的值小于 0,说明没有可用资源,进程会被阻塞,进入等待队列,直到有其他进程释放资源。
V 操作(释放资源)

功能:V 操作用于释放资源,当一个进程执行 V 操作时,它会释放一个资源。

执行过程:

首先将信号量的值加 1。

检查信号量的值:

如果信号量的值小于等于 0,说明有进程在等待该资源,会唤醒等待队列中的一个进程。

如果信号量的值大于 0,说明没有进程在等待该资源,进程继续执行。

复制代码
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semop(int semid, struct sembuf *sops, size_t nsops);

/*参数:
semid : 信号量的标识码。也就是semget()的返回值
sops是一个指向结构体数组的指针。
struct sembuf
{
  unsigned short sem_num;//信号量编号;
  short sem_op;   //对该信号量的操作。‐1 ,P操作,1 ,V操作
  short sem_flg;  //0阻塞,1非阻塞
};
nsops: 操作信号量的个数
返回值:
  如果操作成功,semop 函数返回 0。
  如果操作失败,返回 -1 */
相关推荐
政安晨1 小时前
Ubuntu 服务器无法 ping 通网站域名的问题解决备忘 ——通常与网络配置有关(DNS解析)
linux·运维·服务器·ubuntu·ping·esp32编译服务器·dns域名解析
路溪非溪2 小时前
嵌入式Linux驱动开发杂项总结
linux·运维·驱动开发
Neolock3 小时前
Linux应急响应一般思路(三)
linux·web安全·应急响应
被遗忘的旋律.4 小时前
Linux驱动开发笔记(七)——并发与竞争(上)——原子操作
linux·驱动开发·笔记
轻松Ai享生活4 小时前
minidump vs core dump
linux
轻松Ai享生活5 小时前
详细的 Linux 常用文件系统介绍
linux
张童瑶5 小时前
Linux 离线安装lrzsz(rz、sz上传下载小插件)
linux·运维·centos
十五年专注C++开发6 小时前
通信中间件 Fast DDS(二) :详细介绍
linux·c++·windows·中间件·fastdds
YC运维7 小时前
Linux服务测试题(DNS,NFS,DHCP,HTTP)
linux·网络
zhanghongyi_cpp8 小时前
linux的conda配置与应用阶段的简单指令备注
linux·python·conda