IO进程day06(进程间通信、信号、共享内存)

news2024/11/15 17:35:13

目录

【1】进程间通信 IPC

1》 进程间通信方式

2》 无名管道

1> 特点

2> 函数接口

3> 注意事项

练习:父子进程实现通信,父进程循环从终端输入数据,子进程循环打印数据,当输入quit结束。

3》有名管道

 1> 特点

2> 函数接口

3> 注意事项

 练习:通过两个进程实现 cp功能

 4> 有名管道和无名管道的区别

【2】信号

 1》概念

 2》信号的响应方式

3》信号种类

4》 函数接口

1>  信号发送和挂起

2> 定时器  alarm

3> 信号处理函数 signal()

【3】共享内存

1》特点

 2》步骤

3》 函数接口

4》命令 


【1】进程间通信 IPC

1》 进程间通信方式

(1)早期的进程间通信:

无名管道(pipe)、有名管道(fifo)、信号(signal)

(2)system V PIC:

共享内存(share memory)、信号灯集(semaphore)、消息队列(message queue)

(3)BSD:

套接字(socket)

2》 无名管道

1> 特点

(1)只能用于具有亲缘关系的进程之间的通信

(2)半双工的通信模式,具有固定的读端fd[0]和写端fd[1]。

(3)管道可以看成是一种特殊的文件,对于它的读写可以使用文件IO如read、write函数。

(4)管道是基于文件描述符的通信方式。当一个管道建立时,它会创建两个文件描述符 fd[0]和fd[1]。其中fd[0]固定用于读管道,而fd[1]固定用于写管道。

2> 函数接口

int pipe(int fd[2])

功能:创建无名管道

参数:文件描述符 fd[0]:读端 fd[1]:写端

返回值:成功 0

              失败 -1

#include <stdio.h>
#include <unistd.h>
 
int main(int argc, char const *argv[])
{
    char buf[65536] = "";
    int fd[2] = {0}; //fd[0]代表读端,fd[1]代表写端
    if (pipe(fd) < 0)
    {
        perror("pipe err");
        return -1;
    }
    printf("%d %d\n", fd[0], fd[1]);
 
    //结构类似队列,先进先出
    //1. 当管道中无数据时,读阻塞。
    // read(fd[0], buf, 32);
    // printf("%s\n", buf);
 
    //但是关闭写端就不一样了
    //当管道中有数据关闭写端可以读出数据,无数据时关闭写端读操作会立即返回。
    // write(fd[1], "hello", 5);
    // close(fd[1]);
    // read(fd[0], buf, 32);
    // printf("%s\n", buf);
 
    //2. 当管道中写满数据时,写阻塞,管道空间大小为64K
    // write(fd[1], buf, 65536);
    // printf("full!\n");
    //write(fd[1], "a", 1);  //当管道写满时不能再继续写了会阻塞
 
    //写满一次之后,当管道中至少有4K空间时(也就是读出4K),才可以继续写,否则阻塞。
    // read(fd[0], buf, 4096); //换成4095后面再写就阻塞了,因为不到4K空间
    // write(fd[1], "a", 1);
 
    //3. 当读端关闭,往管道中写入数据无意义,会造成管道破裂,进程收到内核发送的SIGPIPE信号。
    close(fd[0]);
    write(fd[1], "a", 1);
    printf("read close\n");
 
    return 0;
}

用gdb调试可以看见管道破裂信号:

gcc -g xx.c

gdb a.out

r

3> 注意事项

(1)当管道中无数据时,读操作会阻塞

管道中有数据,将写端关闭,可以将数据读出

管道中无数据,将写端关闭,读操作会立即返回

(2)管道中装满(管道大小64K)数据写阻塞,一旦由 4k 空间,写继续

(3)只有在管道的读端存在时,向管道中写入数据才有意义,否则,会导致管道破裂,向管道中写入数据的进程将收到内核传来的SIGPIPE信号(通常Broken pipe错误)

练习:父子进程实现通信,父进程循环从终端输入数据,子进程循环打印数据,当输入quit结束。

提示:不需要加同步机制, 因为pipe无数据时读会阻塞。

先创建管道再fork,这样父子进程可以使用同一个无名管道。

#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char const *argv[])
{
    char buf[32];//定义一个数组
    int fd[2];//定义两个文件描述符
    if (pipe(fd) < 0)//创建管道并判断
    {
        perror("pipe err\n");
        return -1;
    }
    printf("%d %d\n", fd[0], fd[1]);//打印一下文件描述符
 
    int pid;
    pid = fork();//创建父子进程
    if (pid < 0)//创建失败
    {
        perror("fork err\n");
        return -1;
    }
    else if (pid == 0)//子进程
    {
        while (1)
        {
            read(fd[0], buf, 32);//从读端读取管道中的数据到buf中
            printf("%s\n", buf);//打印buf中的数据
        }
    }
    else//父进程
    {
        while (1)
        {
            scanf("%s", buf);//从终端输入数据到buf中
            if (strcmp(buf, "quit") == 0)//判断输入的是否为 quit,若是,则退出循环
            {
                break;
            }
            write(fd[1], buf, 32);//将 buf 中的数据从写端写到管道中
        }
    }
    return 0;
}

3》有名管道

 1> 特点

(1)有名管道可以使互不相关的两个进程互相通信。

(2)有名管道可以通过路径名来指出,并且在文件系统中可见,但内容存放在内存中。但是读写数据不会存在文件中,而是在管道中。

(3)进程通过文件IO来操作有名管道。

(4)有名管道遵循先进先出规则

(5)不支持如lseek()操作

2> 函数接口

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

功能:创健有名管道

参数:filename:有名管道文件名

           mode:权限

返回值:成功:0

              失败:-1,并设置errno号

注意对错误的处理方式:

如果错误是file exist时,注意加判断,如:if(errno == EEXIST)

注意:函数只是在路径下创建管道文件,往管道中写的数据依然是写在内核空间中

 练习:

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>//引用错误号头文件

int main(int argc, char const *argv[])
{
    if (mkfifo("./fifo", 0777) < 0)
    {
        if (errno == EEXIST)   //如果错误号信息是已存在则打印提示语句
            printf("file exist!\n");
        else
        {
            perror("mkfifo err");
            return -1;
        }
    }
    printf("mkfifo success\n");

    return 0;
}

3> 注意事项

(1)只写方式打开会阻塞,一直到另一个进程把读端打开

(2)只写方式打开会阻塞,一直到另一个进程把写端打开

(3)可读可写,如果管道中没有数据,读会阻塞

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    char buf[32] = "";
    if (mkfifo("./fifo", 0777) < 0)
    {
        if (errno == EEXIST) // 如果错误号信息是已存在则打印提示语句
            printf("file exist!\n");
        else
        {
            perror("mkfifo err");
            return -1;
        }
    }
    printf("mkfifo success\n");

    // 打开文件
    int fd = open("./fifo", O_RDONLY);//只读方式会阻塞
    // int fd = open("./fifo", O_WRONLY);//只写方式会阻塞
    // int fd = open("./fifo", O_RDWR);//可读可写方式不会阻塞

    // 读写操作
    write(fd, "hello", 5);//向管道写 hello
    read(fd, buf, 32);//从管道中读取数据到buf中
    printf("%s\n", buf);//打印buf中内容
    return 0;
}

 练习:通过两个进程实现 cp功能

一个进程读取源文件中的内容放到管道中,另一个进程读取管道中的内容写到目的文件中

读进程代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    if (mkfifo("./fifo", 0777) < 0) // 创建管道文件
    {
        if (errno == EEXIST)
            printf("fifo exist\n");
        else
        {
            perror("mkfifo err\n");
            return -1;
        }
    }
    printf("fifo success\n");

    int fd, fd1;
    char buf[32];

    // 读
    fd = open("fifo", O_WRONLY);//以只写方式打开管道文件
    fd1 = open(argv[1], O_RDONLY);//以只读方式打开源文件
    int n;//保存读取的数据个数
    //循环读取源文件中的数据,先放到数组buf中,再从buf中读取放到管道中
    while (n = read(fd1, buf, 32))
        write(fd, buf, n);
    
    //关闭文件描述符
    close(fd);
    close(fd1);
    return 0;
}

写进程代码: 

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    // 重复执行创建管道操作不会出错,因为当官到文件第一次被创建后,后面在创建不会影响而是会打印 EEXIST 的错误,提示已经存在
    if (mkfifo("./fifo", 0777) < 0) // 创建管道文件
    {
        if (errno == EEXIST)
            printf("fifo exist\n");
        else
        {
            perror("mkfifo err\n");
            return -1;
        }
    }
    printf("fifo success\n");

    int fd, fd2;
    char buf[32];

    // 写
    fd = open("fifo", O_RDONLY);//以只读方式打开管道文件
    fd2 = open(argv[1], O_WRONLY | O_CREAT | O_TRUNC, 0777);//以只写方式打开目的文件
    int n;
    //循环读取管道中的数据,先放到数组buf中,再从buf中读取放到目的文件中
    while (n = read(fd, buf, 32))
        write(fd2, buf, n);

    close(fd);
    close(fd2);
    return 0;
}

 一起执行完毕

 4> 有名管道和无名管道的区别

无名管道有名管道
使用场景具有亲缘关系的进程间不相干的进程间也可以使用
特点

半双工通信

固定的读端fd[0]和写端fd[1],看做一种特殊的文件可以通过文件操作

在文件系统中会存在管道文件,数据放在内核空间中,通过文件IO进行操作遵循先进先出,不支持lseek操作
函数

pipe(),直接read/write

mkfifo(),先打开open,再读写read/write
读写特性

当管道中无数据会度阻塞

当管道中写满时会写阻塞

关闭读端,往管道中写会管道破裂

只写方式下打开管道会阻塞,直到另一个进程把读端打开

只读方式下打开管道会阻塞,直到另一个进程把写端打开

可读可写方式下打开管道,如果管道中无数据会读阻塞

【2】信号

 1》概念

(1)信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式

(2)信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件

(3)如果该进程当前并未处于执行态,则该信号就又内核保存起来,直到该进程恢复执行再传递给它;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。

信号的生命周期

 

 2》信号的响应方式

(1)忽略信号:对信号不做任何处理,但是有两个信号不能忽略:即SIGKILL及SIGSTOP.

(2)捕捉信号:定义信号处理函数,当信号发生时,执行相应的处理函数

(3)执行缺省操作:Linux对每种信号都规定了默认操作

3》信号种类

一些比较常用的信号:

SIGINT(2):中断信号,Ctrl-C 产生,用于中断进程

SIGQUIT(3):退出信号, Ctrl-\ 产生,用于退出进程并生成核心转储文件

SIGKILL(9):终止信号,用于强制终止进程。此信号不能被捕获或忽略。

SIGALRM(14):闹钟信号,当由 alarm() 函数设置的定时器超时时产生。

SIGTERM(15):终止信号,用于请求终止进程。此信号可以被捕获或忽略。termination

SIGCHLD(17):子进程状态改变信号,当子进程停止或终止时产生。

SIGCONT(18):继续执行信号,用于恢复先前停止的进程。

SIGSTOP(19):停止执行信号,用于强制停止进程。此信号不能被捕获或忽略。

SIGTSTP(20):键盘停止信号,通常由用户按下 Ctrl-Z 产生,用于请求停止进程。

4》 函数接口

1>  信号发送和挂起

#include <signal.h>

int kill(pid_t pid, int sig);

功能:信号发送

参数:pid:指定进程

           sig:要发送的信号

返回值:成功 0

              失败 -1

int raise(int sig);

功能:进程向自己发送信号

参数:sig:信号

返回值:成功 0

              失败 -1

相当于:kill(getpid(), sig);

int pause(void);

功能:用于将调用进程挂起,直到收到被捕获处理的信号为止。

练习:

#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    //kill(getpid(), SIGKILL);  //给进程发送信号,此例子是给当前进程发送SIGKILL信号
    // raise(SIGKILL);  //给当前进程发送SIGKILL信号,等同于kill(getpid(), SIGKILL);  
    // while (1);

    pause();  //将进程挂起,作用类似死循环但是不占用CPU
    return 0;
}

 

2> 定时器  alarm

man 2 alarm

unsigned int alarm(unsigned int seconds)

功能:在进程中设置一个定时器。当定时器指定的时间到了时,它就向进程发送SIGALARM信号。

参数:seconds:定时时间,单位为秒

返回值:如果调用此alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0

注意:一个进程只能有一个闹钟时间。如果在调用alarm时已设置过闹钟时间,则之前的闹钟时间被新值所代替。

常用操作:取消定时器alarm(0),返回旧闹钟余下秒数。

系统默认对SIGALRM(闹钟到点后内核发送的信号)信号的响应: 如果不对SIGALRM信号进行捕捉或采取措施,默认情况下,闹钟响铃时刻会退出进程。

#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    printf("%d\n", alarm(10));  //设定闹钟5秒后发送闹钟信号
    sleep(1);                                       //睡眠1秒,此时闹钟还剩9秒
    printf("%d\n", alarm(3));    //打印剩余的9秒,设新闹钟3秒后发送闹钟信号
    pause(); //为了不让进程结束,等待SIGALRM信号产生,产生之后结束当前进程
    return 0;
}

3> 信号处理函数 signal()

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

功能:信号处理函数

参数:signum:要处理的信号

           handler:信号处理方式

                       SIG_IGN:忽略信号 (忽略 ignore)

                       SIG_DFL:执行默认操作 (默认 default

                       handler:捕捉信号 (handler为函数名,可以自定义)

void handler(int sig){} //函数名可以自定义, 参数为要处理的信号

返回值:成功:设置之前的信号处理方式

              失败:-1

补充:typedef给数据类型重命名

#include <stdio.h>

//给普通数据类型int重命名
typedef int size4;        

//给指针类型int* 重命名
typedef int *int_p;       

//给数组类型int [10]重命名
typedef int intArr10[10]; 

//给函数指针void (*)()重命名
typedef void (*fun_p)(); 

void fun()
{
    printf("fun\n");
}

int main(int argc, char const *argv[])
{
    size4 a = 10;             //相当于int a=10;
    int_p p = &a;             //相当于int* p=&a;
    intArr10 arr = {1, 2, 3}; //相当于int arr[10]={1,2,3};
    fun_p fp = fun;           //相当于 void (*fp)()=fun;
    printf("%d\n", *p);
    printf("%d\n", arr[0]);
    fp();

    return 0;
}

总而言之,定义变量的变量名写在哪里,用typedef给数据类型重命名的新名字就写在哪里。然后使用新名字定义变量的格式直接就可以为:新名字 变量名;

#include <sys/types.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>

void handler(int sig) //参数sig代表要处理的信号
{
    if (sig == SIGINT)
        printf("ctrl C: %d\n", sig);
    else if (sig == SIGTSTP)
        printf("ctrl Z: %d\n", sig);
}

int main(int argc, char const *argv[])
{
    signal(SIGINT, SIG_IGN);  //对SIGINIT信号设置忽略方式处理
    //signal(SIGINT,SIG_DFL);  //对SIGINIT信号设置缺省方式处理,也就是默认操作
    signal(SIGINT, handler); //对SIGINIT信号设置捕捉方处理,也就是自定义处理方式
    signal(SIGTSTP, handler);

    //while (1); //为了让进程不要结束,因为等到信号真的来才能验证,不然进程就结束了
    pause();  //收到被捕获处理的信号时会结束挂起
    return 0;
}

【3】共享内存

1》特点

(1)共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝。

(2)为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程

将其映射到自己的私有地址空间。进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高的效率。

(3)由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等

 2》步骤

1> 创建key值

2> 创建或打开共享内存

3> 映射共享内存到用户空间

4> 撤销映射

5> 删除共享内存

3》 函数接口

key_t ftok(const char *pathname, int proj_id);

功能:创建出来的具有唯一映射关系的一个key值,帮助操作系统用来标识一块共享内存

参数:Pathname:已经存在的可访问文件的名字

           Proj_id:一个字符(因为只用低8位)

返回值:成功:key值

              失败:-1

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

功能:创建或打开共享内存

参数:key 键值

           size 共享内存的大小

           shmflg IPC_CREAT|IPC_EXCL|0777

返回值:成功 shmid

              出错 -1

注意对错误的处理方式:

如果错误是file exist光打开共享内存不用设IPC_CREAT|IPC_EXCL了,加判断,如:if(errno == EEXIST)

void *shmat(int shmid,const void *shmaddr,int shmflg); //attaches

功能:映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问

参数:shmid 共享内存的id号

           shmaddr 一般为NULL,表示由系统自动完成映射

           如果不为NULL,那么有用户指定

           shmflg:SHM_RDONLY就是对该共享内存只进行读操作

           0 可读可写

返回值:成功:完成映射后的地址,

              出错:-1(地址)

用法:if((p = (char *)shmat(shmid,NULL,0)) == (char *)-1)

int shmdt(const void *shmaddr); //detaches

功能:取消映射

参数:要取消的地址

返回值:成功0

              失败的-1

int shmctl(int shmid,int cmd,struct shmid_ds *buf); //control

功能:(删除共享内存),对共享内存进行各种操作

参数:shmid 共享内存的id号

           cmd

                    IPC_STAT 获得shmid属性信息,存放在第三参数

                    IPC_SET 设置shmid属性信息,要设置的属性放在第三参数

                    IPC_RMID:删除共享内存,此时第三个参数为NULL即可

           buf shmid所指向的共享内存的地址,空间被释放以后地址就赋值为null

返回值:成功0

              失败-1

用法:shmctl(shmid,IPC_RMID,NULL);

#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <sys/shm.h>
#include <errno.h>

int main(int argc, char const *argv[])
{
    int shmid;
    key_t key;
    char *p;
    key = ftok("shm.c", 'a');
    if (key < 0)
    {
        perror("key err");
        return -1;
    }
    printf("key: %#x\n", key);

    //打开或创建共享内存
    shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0777); //如果共享内存不存在则创建,存在则返回-1
    if (shmid <= 0)
    {
        if (errno == EEXIST)                //如果共享内存已存在则,直接打开
            shmid = shmget(key, 128, 0777); //直接打开已有的共享内存并且获得共享内存id
        else
        {
            perror("shmget err");
            return -1;
        }
    }
    printf("shmid: %d\n", shmid);

    //映射共享内存
    p = (char *)shmat(shmid, NULL,0);
    if(p == (char *)-1)
    {
        perror("shmat err");
        return -1;
    }

    //操作共享内存
    scanf("%s",p);
    printf("%s\n", p);

    //取消映射
    shmdt(p);

    //删除共享内存
    shmctl(shmid,IPC_RMID,NULL);

    return 0;
}

进程间通信: 

4》命令 

ipcs -m :查看系统中的共享内存

ipcrm -m shmid:删除共享内存

ps: 可能不能直接删除掉还存在进程使用的共享内存。

这时候可以用ps -ef对进程进行查看,kill掉多余的进程后,再使用ipcs查看。


今天的分享就到这里结束啦,如果有哪里写的不好的地方,请指正。
如果觉得不错并且对你有帮助的话点个关注支持一下吧!

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

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

相关文章

24数学建模国赛准备!!!!(10——马氏链模型)

详细获取资料方式在文章末尾&#xff01;&#xff01;&#xff01;&#xff01; 点击链接加入群聊获取资料以及国赛助力https://qm.qq.com/q/NGl6WD0Bky &#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&…

多场景建模: STAR(Star Topology Adaptive Recommender)

之前&#xff0c;分享了一篇关于多任务学习的文章&#xff1a;多任务学习MTL模型&#xff1a;MMoE、PLE&#xff0c;同样的还有关于多任务学习中的多目标loss优化策略。 这篇文章则开始一个与多任务学习有着紧密联系的系列&#xff1a;多场景建模学习。 前言 首先&#xff0…

[Raspberry Pi]如何利用docker執行motioneye,並利用Line Notify取得即時通知和照片?

[Motioneye]How to setup motion detection and send message/image for Line Notify 無意間&#xff0c;翻了一本關於樹莓派的書籍&#xff0c;除了樹莓派的簡介和應用外&#xff0c;也包含初階和高階的Linux運作邏輯&#xff0c;書籍結構相當完整&#xff0c;也因此需要花時間…

DAC: High-Fidelity Audio Compression with Improved RVQGAN

Rithesh KumarDescript, Inc.2023NIPS code 文章目录 abstratmethod abstrat 44.1k音频编码为8k bps&#xff0c;接近90x压缩比&#xff1b;可以处理speech, musiccodebook collapse: 部分码本没有利用到。----quantizer dropout &#xff1a;允许单个模型支持可变比特率&…

HarmonyOS NEXT实战:“相机分段式拍照”性能提升实践

概述 相机拍照性能依赖算法处理的速度&#xff0c;而处理效果依赖算法的复杂度&#xff0c;算法复杂度越高的情况下会导致处理时间就越长。目前系统相机开发有两种相机拍照方案&#xff0c;分别是相机分段式拍照和相机单段式拍照&#xff1a; 分段式拍照是系统相机开发的重要…

几款最新好用的图纸加密软件

在现代数字化办公环境中&#xff0c;图纸的保护变得尤为重要。无论是建筑设计图纸、工程图纸&#xff0c;还是机械制造图纸&#xff0c;如何有效地加密并保护这些机密文件&#xff0c;避免信息泄露&#xff0c;是每个企业都需要重视的问题。今天&#xff0c;我们就来推荐几款最…

从开题到答辩:ChatGPT超全提示词分享!(下)【建议收藏】

数据收集 1. "请帮我找出关于如何收集【研究领域】社交媒体数据进行消费者行为研究的五篇指导性文章&#xff0c;并概述它们的主要方法论摘要。" 2. "我需要对【特定领域】市场的消费者偏好进行调查。能否提供一份包含调查问卷设计原则和示例的草稿&#xff1f;…

react vant 在使用dialog.confirm取消报错 Uncaught (in promise) undefined

项目场景&#xff1a; 在使用react做移动端开发时&#xff0c;需要使用Dialog.confirm确认框来做弹框选项&#xff0c;这是在操作中非常常用的一种场景。 问题描述 在列表中&#xff0c;使用弹框时&#xff0c;点击取消时&#xff0c;语法报错&#xff1b;导致后面再触发弹框…

养老小程序源码家政服务小程序开发方案

预约上门养老小程序&#xff0c;是php开发预约&#xff0c;前端是uniapp&#xff0c;有开发好的小程序案例&#xff0c;可源码&#xff0c;也可以二开&#xff0c;也可以定制开发。 一 用户端&#xff1a;服务分类、服务内容详情介绍、在线下单支付&#xff0c;管理我的订单。…

认知杂谈42

今天分享 有人说的一段争议性的话 I I 《摆脱自负自卑&#xff0c;找准自我定位》 I 在咱的生活里啊&#xff0c;有时候咱会在自负和自卑这两个地方来回晃悠&#xff0c;根本就找不着真正属于自己的那个位置。你想想看&#xff0c;自负的时候呢&#xff0c;就好像给自己戴了…

Unity(2022.3.41LTS) - 地形

目录 一、地形的创建 二.页面详解 1.创建相邻的 Terrain 瓦片。 2.雕刻和绘制地形。 3.添加树。 4.添加细节&#xff0c;如草地、花朵和岩石。 5.更改所选 Terrain 的常规设置 三、地形编辑工具 四、地形的属性设置 五、地形的优化 六、地形的应用场景 一、地形的创…

校园牛奶订购配送小程序开发制作方案

校园牛奶订购配送小程序系统的开发方案&#xff0c;包括对用户需求的分析、目标用户的界定、使用场景的设定以及开发功能模块的规划。校园牛奶订购配送小程序系统主要是为校园内学生和教职工提供牛奶订购与配送服务。 目标用户 主要面向在校学生、教职工以及其他有牛奶订购需求…

Mac GIF录制神器LICEcap

GIF录制软件的优点先看下 mac gif制作win gif录制完全免费界面简洁软件大小不到1M 今天就来介绍一款录屏并能生成 GIF 的软件&#xff1a;LICEcap。 背景 希望小巧免费的GIF录制的话LICEcap非常适合&#xff0c;网上很多能轻松录制屏幕的工具&#xff0c;基本都是录制之后带…

操作系统:实验四进程调度实验

一、实验目的 1、了解操作系统CPU管理的主要内容。 2、加深理解操作系统管理控制进程的数据结构--PCB。 3、掌握几种常见的CPU调度算法&#xff08;FCFS、SJF、HRRF、RR&#xff09;的基本思想和实现过程。 4、用C语言模拟实现CPU调度算法。 5、掌握CPU调度算法性能评价指…

基于python文案转语音并输出-自媒体等职业副业均可使用,不受他人限制

开发背景: 目前自媒体比较火爆,有很多书单、视频等推广方式可以作为副业盈利,之前每次搞的时候都需要不停的网上找一些在线文字转语音的平台将文案复制上去然后生成下载,好多还是付费的,挺无奈的,然后就想着自己能不能搞,然后的然后就有了下面的东西, 如果大家有此类需要…

文心智能体-梦想目标实现助手-实现你的老板梦

前言&#xff1a; 其实我从小就很羡慕小说里面的男主&#xff0c;从家境贫寒到后面成为天之骄子&#xff0c;在一路上都有很多好的机遇和贵人。用今天的话来说&#xff0c;男主好像都有一个“系统”&#xff0c;毫不意外&#xff0c;我也有这样的武侠梦&#xff0c;金庸的小说更…

波导阵列天线学习笔记6 用于K和Ka频段卫星通信的超宽带双圆极化波导阵列天线

摘要: 在本文中&#xff0c;设计了一种用于K和Ka双频段的宽带双圆极化波导天线阵列。一种多级方波导结构被利用&#xff08;exploited&#xff09;在辐射层内来实现双极化响应的激励。一种脊波导极化器被集成在内来实现左旋圆极化和右旋圆极化。为了馈网的更好设计&#xff0c;…

qtlinux

filezilla传 白色 权限不够 chmod x ./运行 source路径 qmake make 55可执行文件 nfs拷贝到开发版 ./运行 make j 核数 &#xff08;加速编译过程&#xff09;

【精选】推荐4款写作效率翻倍的AI论文写作助手

在当前的学术研究和写作领域&#xff0c;AI论文写作助手已经成为提高写作效率和质量的重要工具。这些工具利用先进的自然语言处理和机器学习技术&#xff0c;帮助研究人员和学生快速生成论文草稿、优化内容、进行查重和排版等操作。以下是四款高效且广受好评的AI论文写作助手&a…

迎来“成人礼”的良品铺子,蜕变了吗?

成立18年的良品铺子&#xff0c;正在迎来一场“成人礼”。 在这一关键节点&#xff0c;“苦”可能是其最先品尝到的味道。据良品铺子近日发布的财报&#xff0c;2024年上半年&#xff0c;公司实现营业收入38.86亿元&#xff0c;同比下滑2.52%&#xff1b;归属于上市公司股东的…