【Linux】进程间通信IPC机制

目录

一、无名管道

二、有名管道

三、共享内存

四、信号量

五、消息队列

六、套接字


一、无名管道

1.只能用于具有亲缘关系的进程之间的通信(也就是父子进程或者兄弟进程)。

2.是一个单工的通信模式,具有固定的读端和写端。

3.管道也可以看成是一种特殊的文件,对于它的读写也可以使用普通的read()、write()等函数。但是它不属于任何文件系统,并且只存在于内存中。

#include <unistd.h>
int pipe(int filedes[2]);
在管道中,文件描述符数组的第一个元素(索引为0)用于读取,第二个元素(索引为1)用于写入。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>#define BUFFER_SIZE 25
#define READ_END 0
#define WRITE_END 1int main() 
{char write_msg[BUFFER_SIZE] = "Hello, child!";char read_msg[BUFFER_SIZE];int fd[2];pid_t pid;// 创建管道if (pipe(fd) == -1) {fprintf(stderr, "Pipe failed");return 1;}// 创建子进程pid = fork();if (pid < 0) {fprintf(stderr, "Fork failed");return 1;}// 父进程if (pid > 0)   {// 关闭写入端,因为父进程不会写入close(fd[WRITE_END]);// 从管道中读取消息read(fd[READ_END], read_msg, BUFFER_SIZE);printf("Parent received message from child: %s\n", read_msg);// 关闭读取端close(fd[READ_END]);} // 子进程else {  // 关闭读取端,因为子进程不会读取close(fd[READ_END]);// 写入消息到管道write(fd[WRITE_END], write_msg, BUFFER_SIZE);// 关闭写入端close(fd[WRITE_END]);}return 0;
}

./a.out
执行结果:Parent received message from child: Hello, child! 

二、有名管道

  1. mkfifo(const char *pathname, mode_t mode):

    • 这个函数用于创建一个有名管道。
    • 参数pathname是要创建的管道的路径名,mode是指定管道权限的位掩码。
    • 返回值:成功时返回0,失败时返回-1,并设置errno变量表示错误类型。
  2. open(const char *pathname, int flags, mode_t mode):

    • 这个函数用于打开文件或管道。
    • 参数pathname是要打开的文件或管道的路径名,flags指定打开文件的方式(如只读、只写、读写等),mode是当创建新文件时指定的权限。
    • 返回值:成功时返回文件描述符,失败时返回-1,并设置errno变量表示错误类型。
  3. write(int fd, const void *buf, size_t count):

    • 这个函数用于向文件描述符指定的文件或管道中写入数据。
    • 参数fd是打开文件或管道时返回的文件描述符,buf是要写入的数据缓冲区,count是要写入的字节数。
    • 返回值:成功时返回实际写入的字节数,失败时返回-1,并设置errno变量表示错误类型。
  4. read(int fd, void *buf, size_t count):

    • 这个函数用于从文件描述符指定的文件或管道中读取数据。
    • 参数fd是打开文件或管道时返回的文件描述符,buf是用于接收数据的缓冲区,count是要读取的最大字节数。
    • 返回值:成功时返回实际读取的字节数,失败时返回-1,并设置errno变量表示错误类型。
  5. close(int fd):

    • 这个函数用于关闭打开的文件描述符。
    • 参数fd是要关闭的文件描述符。
    • 返回值:成功时返回0,失败时返回-1,并设置errno变量表示错误类型。
  6. unlink(const char *pathname):

    • 这个函数用于删除文件或链接。
    • 参数pathname是要删除的文件或链接的路径名。
    • 返回值:成功时返回0,失败时返回-1,并设置errno变量表示错误类型。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>#define BUFFER_SIZE 25int main() 
{char *fifo_name = "./name";char write_msg[BUFFER_SIZE] = "Hello, world!";char read_msg[BUFFER_SIZE];// 创建有名管道mkfifo(fifo_name, 0666);pid_t pid = fork();if (pid == 0)// 子进程负责写入{// 打开管道进行写入int fd_write = open(fifo_name, O_WRONLY);write(fd_write, write_msg, BUFFER_SIZE);close(fd_write);} else if (pid > 0) // 父进程负责读取{ // 打开管道进行读取int fd_read = open(fifo_name, O_RDONLY);read(fd_read, read_msg, BUFFER_SIZE);printf("Received message: %s\n", read_msg);close(fd_read);} else  // fork失败{fprintf(stderr, "Fork failed");return 1;}// 删除管道unlink(fifo_name);return 0;
}

三、共享内存

共享内存是最高效的,因为避免了数据在用户空间和内核空间的来回拷贝

  1. ftok(const char *pathname, int proj_id):

    • ftok()函数用于生成一个唯一的key,用于创建或访问共享内存。
    • 参数pathname是一个路径名,proj_id是一个整数,用于生成key。
    • 返回值:如果成功,返回一个唯一的key,如果失败,返回-1。
  2. shmget(key_t key, size_t size, int shmflg):

    • shmget()函数用于创建共享内存段或获取共享内存段的标识符。
    • 参数key是由ftok()生成的唯一key,size是共享内存的大小,shmflg是标志位,用于指定权限和行为。
    • 返回值:如果成功,返回共享内存段的标识符,如果失败,返回-1。
  3. shmat(int shmid, const void *shmaddr, int shmflg):

    • shmat()函数用于将共享内存连接到当前进程的地址空间。
    • 参数shmid是共享内存段的标识符,shmaddr通常为NULL(表示系统自动选择地址),shmflg是标志位,通常为0。
    • 返回值:如果成功,返回指向共享内存段的指针,如果失败,返回(void *)-1。
  4. shmdt(const void *shmaddr):

    • shmdt()函数用于将共享内存从当前进程的地址空间分离。
    • 参数shmaddr是指向共享内存段的指针。
    • 返回值:如果成功,返回0,如果失败,返回-1。
  5. shmctl(int shmid, int cmd, struct shmid_ds *buf):

    • shmctl()函数用于对共享内存进行控制操作,如删除共享内存段。
    • 参数shmid是共享内存段的标识符,cmd是控制命令,buf是一个指向shmid_ds结构体的指针,用于获取共享内存的状态信息。
    • 返回值:如果成功,返回0,如果失败,返回-1。

读端

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>#define SHM_SIZE 1024int main() 
{key_t key = ftok("/", 'R'); // 生成共享内存的key// 创建共享内存段int shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666);if (shmid == -1) {perror("shmget");exit(1);}// 将共享内存连接到当前进程的地址空间char *shmaddr = shmat(shmid, NULL, 0);if (shmaddr == (void *)-1) {perror("shmat");exit(1);}// 写入数据到共享内存strcpy(shmaddr, "Hello, shared memory!");// 分离共享内存if (shmdt(shmaddr) == -1) {perror("shmdt");exit(1);}printf("数据已写入共享内存\n");return 0;
}

 写端

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>#define SHM_SIZE 1024int main() 
{key_t key = ftok("/", 'R'); // 生成共享内存的key// 获取共享内存段int shmid = shmget(key, SHM_SIZE, 0666);if (shmid == -1) {perror("shmget");exit(1);}// 将共享内存连接到当前进程的地址空间char *shmaddr = shmat(shmid, NULL, 0);if (shmaddr == (void *)-1){perror("shmat");exit(1);}printf("从共享内存中读取到的消息:%s\n", shmaddr);// 分离共享内存if (shmdt(shmaddr) == -1) {perror("shmdt");exit(1);}return 0;
}

写端执行后:

数据已写入共享内存

读端执行后:

从共享内存中读取到的消息:Hello, shared memory! 

四、信号量

1.信号量实现互斥

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>#define KEY 1234 // 信号量的键值// 定义一个联合体,用于semctl初始化
union semun {int val;struct semid_ds *buf;unsigned short *array;
};// 创建一个二值信号量并初始化为1
int create_semaphore() 
{// 创建一个信号量集,包含1个信号量int semid = semget(KEY, 1, IPC_CREAT | 0666); if (semid == -1) {perror("semget");exit(1);
}union semun arg;arg.val = 1; // 初始值为1,表示资源可用if (semctl(semid, 0, SETVAL, arg) == -1) // 初始化信号量{perror("semctl");exit(1);}return semid;
}// P操作(等待资源)
void P(int semid) 
{struct sembuf op;op.sem_num = 0; // 信号量集中的第一个信号量op.sem_op = -1; // 对信号量执行P操作op.sem_flg = 0;if (semop(semid, &op, 1) == -1) {perror("semop");exit(1);}
}// V操作(释放资源)
void V(int semid) 
{struct sembuf op;op.sem_num = 0; // 信号量集中的第一个信号量op.sem_op = 1; // 对信号量执行V操作op.sem_flg = 0;if (semop(semid, &op, 1) == -1) {perror("semop");exit(1);}
}int main()
{int semid = create_semaphore(); // 创建信号量pid_t pid = fork(); // 创建子进程if (pid == -1) {perror("fork");exit(1);}if (pid == 0)  // 子进程{P(semid); // 等待资源printf("Child process: Counter decremented by 1.\n");V(semid); // 释放资源} else // 父进程{sleep(1); // 让子进程有机会先运行P(semid); // 等待资源printf("Parent process: Counter incremented by 1.\n");V(semid); // 释放资源}return 0;
}
  1. semget(key_t key, int nsems, int semflg)

    • 这个函数用于创建一个新的信号量集或获取一个现有信号量集的标识符。
    • key是一个用于唯一标识信号量集的键值。
    • nsems指定了信号量集中的信号量数量。
    • semflg是一组标志,用于指定创建信号量集的权限和行为。
    • 返回值:成功时返回信号量集的标识符,失败时返回-1。
  2. semctl(int semid, int semnum, int cmd, union semun arg)

    • 这个函数用于对信号量集进行控制操作,如初始化、设置值、获取值等。
    • semid是信号量集的标识符。
    • semnum是指定的信号量在集合中的索引,通常为0。
    • cmd是指定要执行的控制命令。
    • arg是一个联合体,用于传递控制命令的参数。
    • 返回值:根据控制命令不同而不同。
  3. semop(int semid, struct sembuf *sops, size_t nsops)

    • 这个函数用于执行一组信号量操作,如等待资源(P操作)和释放资源(V操作)。
    • semid是信号量集的标识符。
    • sops是一个指向信号量操作结构体数组的指针。
    • nsops是指定的信号量操作结构体数组的大小。
    • 返回值:成功时返回0,失败时返回-1。
  4. struct sembuf

    • 这是一个结构体,用于描述信号量操作。
    • 它包含了三个字段:
      • sem_num:信号量集中的信号量索引。
      • sem_op:信号量操作,通常是-1(P操作,等待资源)或1(V操作,释放资源)。
      • sem_flg:信号量操作的标志位,通常为0。
  5. union semun

    • 这是一个联合体,用于传递给semctl()函数的参数。
    • 它包含了多个字段,其中的一个字段可以根据不同的控制命令来使用,比如用于设置或获取信号量值时使用的val字段,用于获取信号量状态信息时使用的buf字段等

  1. create_semaphore():

    • 这个函数用于创建一个二值信号量,并将其初始化为1。
    • 首先,它调用 semget() 函数创建一个包含一个信号量的信号量集,如果创建失败则会打印错误信息并退出程序。
    • 然后,它使用 semctl() 函数将信号量的值初始化为1,表示资源可用。
    • 最后,它返回创建的信号量集的标识符。
  2. P(int semid):

    • 这个函数用于执行 P 操作,即等待资源。
    • 首先,它定义了一个 struct sembuf 结构体 op,用于描述信号量操作。
    • 然后,它将 op.sem_num 设置为0,表示信号量集中的第一个信号量。
    • 接着,它将 op.sem_op 设置为-1,表示对信号量执行 P 操作。
    • 最后,它调用 semop() 函数执行信号量操作,如果操作失败则会打印错误信息并退出程序。
  3. V(int semid):

    • 这个函数用于执行 V 操作,即释放资源。
    • 它的实现与 P() 函数类似,只是将 op.sem_op 设置为1,表示对信号量执行 V 操作。

2.信号量实现同步互斥 

交替实现奇偶数打印,打印一个奇数后必须是一个偶数

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>#define KEY 1234 // 信号量的键值// 定义一个联合体,用于semctl初始化
union semun {int val;struct semid_ds *buf;unsigned short *array;
};// 创建一个信号量并初始化为1
int create_semaphore() 
{int semid = semget(KEY, 1, IPC_CREAT | 0666); // 创建一个信号量集,包含1个信号量if (semid == -1) {perror("semget");exit(1);}union semun arg;arg.val = 0; // 初始值为0if (semctl(semid, 0, SETVAL, arg) == -1) // 初始化信号量{ perror("semctl");exit(1);}return semid;
}// P操作(等待资源)
void P(int semid) 
{struct sembuf op;op.sem_num = 0; // 信号量集中的第一个信号量op.sem_op = -1; // 对信号量执行P操作op.sem_flg = 0;if (semop(semid, &op, 1) == -1) {perror("semop");exit(1);}
}// V操作(释放资源)
void V(int semid) 
{struct sembuf op;op.sem_num = 0; // 信号量集中的第一个信号量op.sem_op = 1; // 对信号量执行V操作op.sem_flg = 0;if (semop(semid, &op, 1) == -1) {perror("semop");exit(1);}
}int main() 
{int  i = 1;int j = 2;int semid = create_semaphore(); // 创建信号量pid_t pid = fork(); // 创建子进程if (pid == -1) {perror("fork");exit(1);}if (pid == 0)  // 子进程打印奇数{while(1){ 		printf("i = %d\n",i);i += 2;V(semid); // 释放资源sleep(1);}       } else // 父进程打印偶数{while(1){P(semid); // 等待资源printf("j = %d\n",j);j += 2;}		}return 0;
}

执行结果

i = 1
j = 2
i = 3
j = 4
i = 5
j = 6
i = 7
j = 8
i = 9
j = 10
i = 11
j = 12
i = 13
j = 14
i = 15
j = 16
 

五、消息队列

发送进程

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>#define MSG_KEY 1234 // 消息队列的键值// 定义消息结构体
struct msg_buffer 
{long msg_type; // 消息类型char msg_text[100]; // 消息内容
};int main() 
{int msgid;struct msg_buffer message_send;// 创建消息队列msgid = msgget(MSG_KEY, IPC_CREAT | 0666);if (msgid == -1) {perror("msgget");exit(1);}// 准备发送的消息strcpy(message_send.msg_text, "Hello, Message Queue!");message_send.msg_type = 1; // 消息类型为1// 发送消息if (msgsnd(msgid, &message_send, sizeof(message_send) - sizeof(long), 0) == -1) {perror("msgsnd");exit(1);}printf("Parent process sent message: %s\n", message_send.msg_text);return 0;
}

 接收进程

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>#define MSG_KEY 1234 // 消息队列的键值// 定义消息结构体
struct msg_buffer {long msg_type; // 消息类型char msg_text[100]; // 消息内容
};int main() 
{int msgid;struct msg_buffer message_rcv;// 创建消息队列msgid = msgget(MSG_KEY, IPC_CREAT | 0666);if (msgid == -1) {perror("msgget");exit(1);}// 接收消息if (msgrcv(msgid, &message_rcv, sizeof(message_rcv) - sizeof(long), 1, 0) == -1) {perror("msgrcv");exit(1);}printf("Child process received message: %s\n", message_rcv.msg_text);return 0;
}

执行结果:

./s

Parent process sent message: Hello, Message Queue!
./r
Child process received message: Hello, Message Queue!
 

在这两个示例代码中,主要使用了以下系统调用:

  1. msgget: 用于创建或打开一个消息队列。它接受一个参数,即消息队列的键值(一个唯一的标识符),并返回一个消息队列的标识符(msgid)。

  2. msgsnd: 用于向消息队列发送消息。它接受四个参数:消息队列的标识符(msgid)、指向要发送的消息的指针、消息的大小(不包括消息类型字段的大小)、消息的标志(通常为0)。

  3. msgrcv: 用于从消息队列接收消息。它接受五个参数:消息队列的标识符(msgid)、指向用于接收消息的缓冲区的指针、缓冲区大小(不包括消息类型字段的大小)、消息类型(通常为正整数)、接收消息的标志(通常为0)。

  4. msgctl: 用于控制消息队列的属性,如删除消息队列。在这个示例中,我们使用它来删除消息队列。它接受三个参数:消息队列的标识符(msgid)、命令(IPC_RMID 表示删除消息队列)、指向用于控制消息队列属性的结构体的指针(在这个例子中不需要)。

六、套接字

网络部分的内容

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

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

相关文章

免费开源语音克隆-GPT-SoVITS-WebUI只需 5 秒的声音样本

语音克隆-GPT-SoVITS-WebUI 强大的少样本语音转换与语音合成Web用户界面。 功能&#xff1a; 零样本文本到语音&#xff08;TTS&#xff09;&#xff1a; 输入 5 秒的声音样本&#xff0c;即刻体验文本到语音转换。 少样本 TTS&#xff1a; 仅需 1 分钟的训练数据即可微调模型…

OpenCV如何在图像中寻找轮廓(60)

返回:OpenCV系列文章目录&#xff08;持续更新中......&#xff09; 上一篇&#xff1a;OpenCV如何模板匹配(59) 下一篇 :OpenCV检测凸包(61) 目标 在本教程中&#xff0c;您将学习如何&#xff1a; 使用 OpenCV 函数 cv::findContours使用 OpenCV 函数 cv::d rawContours …

【Flask 系统教程 4】Jinjia2模版和语法

Jinjia2 模板 模板的介绍 Jinja2 是一种现代的、设计优雅的模板引擎&#xff0c;它是 Python 的一部分&#xff0c;由 Armin Ronacher 开发。Jinja2 允许你在 HTML 文档中嵌入 Python 代码&#xff0c;以及使用变量、控制结构和过滤器来动态生成内容。它的语法简洁清晰&#…

【Qt QML】用CMake管理Qt工程

CMake是一个开源、跨平台的工具系列&#xff0c;用于构建、测试和打包软件。CMake使用简单的独立配置文件来控制软件编译过程。与许多跨平台系统不同&#xff0c;CMake被设计为与本地构建环境结合使用。 下面我们在CMake项目中使用Qt的最基本方法。首先&#xff0c;创建一个基本…

系统架构设计师错题集

在实时操作系统中&#xff0c;两个任务并发执行&#xff0c;一个任务要等待另一个任务发来消息&#xff0c;或建立某个条件后再向前执行&#xff0c;这种制约性合作关系被称为任务的&#xff08;9&#xff09;。 (9)A.同步 B.互斥 C.调度 D.执行 【答案】A 【解析】本题考查…

Linux图形化界面怎么进入?CentOS 7图形界面切换

CentOS 7默认只安装命令行界面。要切换到图形界面&#xff0c;需要先检查系统是否安装图形界面&#xff0c;在终端输入以下命令&#xff1a; systemctl get-default若是返回结果是“multi-user.target”表示系统没有安装图形界面&#xff1b;若是返回结果是“graphical.target…

Linux migrate_type进一步探索

文章接着上回Linux migrate_type初步探索 1、物理页面添加到buddy系统 我们都知道物理内存一开始是由memblock进行分配管理&#xff0c;后面会切换到buddy系统管理。那么接下来我们看一下&#xff0c;memblock管理的物理页面是怎么添加到buddy系统中的。 start_kernel() -&g…

Python的定义和调用函数

Python中的函数是一种可重复使用的代码块&#xff0c;它接受输入参数、执行特定的任务&#xff0c;并返回结果。函数能够提高代码的模块化和可读性&#xff0c;同时可以减少代码的重复性。本文将详细介绍Python中函数的定义和调用方法&#xff0c;包括函数的语法、参数传递方式…

关于前后端的参数传递

以前端javascript&#xff0c;后端nodejsexpress为例&#xff0c;后端可以从前端发来的request里获取这些属性&#xff1a;header、query、url、params、body&#xff0c;其中params和query是从url中解析获得&#xff0c;不过express已帮我们做好了&#xff0c;就不用我们自己再…

双指针(C++)

文章目录 1、移动零2、复写零3、快乐数4、盛最多水的容器5、有效三角形的个数6、和为s的两个数7、三数之和8、四数之和 需要理解的是&#xff0c;双指针并非只有指针&#xff0c;双指针的意思是两个位置。比如对于数组来说&#xff0c;两个下标也是双指针。当然&#xff0c;也可…

merge and rebase

文章目录 什么是merge什么是rebasemerge和rebase的区别操作执行git merge操作git rebase操作冲突解决解决冲突的步骤 Git Merge 和 Git Rebase 都是用于集成来自不同分支的修改的 Git 命令。 什么是merge Git Merge 是将一个分支的改动合并到另一个分支的方式。当你执行一个 m…

LabVIEW机械臂控制与图像处理示教平台

LabVIEW机械臂控制与图像处理示教平台 随着工业自动化技术的快速发展&#xff0c;工业机器人在制造业中的应用越来越广泛&#xff0c;它们在提高生产效率、降低人工成本以及保证产品质量方面发挥着重要作用。然而&#xff0c;传统的工业机器人编程和操作需要专业知识&#xff…

常见面试题:XSS和CSRF原理及防范方法

XSS和CSRF原理及防范方法 XSS 跨站脚本攻击 浏览器向服务器请求的时候被注入脚本攻击 类型恶意代码有效的位置插入点反射型URLHTML存储型服务端数据库HTML基于DOM服务端数据库/客户端存储/URL前端javascript 反射型XSS&#xff08;非持久性跨站脚本攻击&#xff09; 攻击方法…

2024-5-3学习笔记 虚拟继承原理

目录 原理 总结 前面提到过&#xff0c;解决菱形继承产生的数据二义性问题和数据冗余&#xff0c;就需要用到虚拟继承&#xff0c;关于它是如何解决的&#xff0c;我们来一起研究。 class Person { public :string _name ; // 姓名 }; class Student : virtual public Perso…

排序算法(2)

文章目录 概要原理及实现归并排序定义性能代码Python 快速排序定义代码 小结 概要 接上回 在上篇说过经典的排序算法&#xff0c;有冒泡&#xff0c;插入&#xff0c;选择&#xff1b;归并&#xff0c;快排。其中讲了冒泡&#xff0c;插入&#xff0c;选择&#xff1b;这一回写…

某招聘网站搜索结果接口逆向之webpack扣取

逆向网址 aHR0cHM6Ly93ZS41MWpvYi5jb20v 逆向链接 aHR0cHM6Ly93ZS41MWpvYi5jb20vcGMvc2VhcmNoP2pvYkFyZWE9MDAwMDAwJmtleXdvcmQ9cGhwJnNlYXJjaFR5cGU9MiZrZXl3b3JkVHlwZT0 逆向接口 aHR0cHM6Ly93ZS41MWpvYi5jb20vYXBpL2pvYi9zZWFyY2gtcGM 逆向过程 请求方式 POST 逆向参数 …

华为Pura70发布,供应链公司进入静默保密期

保密措施&#xff1a;与华为Pura70发布相关的供应链公司在产品发布前后处于静默保密期。这可能是由于华为对于手机供应链的一些信息处于保密状态&#xff0c;尤其是关于麒麟芯片的代工厂商等敏感信息。这种保密措施有助于保持产品的神秘感&#xff0c;调动用户的好奇心&#xf…

微信小程序+esp8266温湿度读取

本文主要使用微信小程序显示ESP8266读取的温湿度并通过微信小程序控制LED灯。小程序界面如下图所示 原理讲解 esp8266 通过mqtt发布消息,微信小程序通过mqtt 订阅消息,小程序订阅后,就可以实时收到esp8266 传输来的消息。 个人可免费注册五个微信小程序账号,在微信小程序官…

开源框架 NanUI 项目宣布将暂停开发,作者转行卖钢材

NanUI 界面组件是一个开源的 .NET 窗体应用程序界面框架&#xff0c;适用于希望使用 HTML5 / CSS3 等前端技术来构建 Windows 窗体应用程序用户界面的 .NET 开发人员。 该项目的作者林选臣日前在 GitHub 上发布了停更公告&#xff0c;称因去年被裁员失业&#xff0c;他目前已经…

Adobe软件全家桶:从平面到视频再到音频的创意之旅

在创意设计的广阔天地里&#xff0c;Adobe公司旗下的系列软件无疑是设计师们手中的魔法棒&#xff0c;它们串联起平面设计、视频剪辑直至音频处理的每一个环节&#xff0c;成为跨越视觉创意门槛的必备工具集。本文将深入浅出地介绍这些软件的应用场景、特色功能及其相互间的协作…