一.管道(分为命名管道和匿名管道)
管道的特点:
①无论是命名管道还是匿名管道,写入管道的数据都存放在内存之中。
②管道是一种半双工的通信方式(半双工是指终端A能发信号给终端B,终端B也能发信号给终端A,但这两个过程不能同时进行)
③命名管道和匿名管道的区别:命名管道可以在任意进程间使用,匿名管道主要在父子进程间使用。
命名管道
int mkfifo( const char *filename, mode_t mode);//filename 是管道名 mode 是创建的文件访问权限
实践:
打开两个独立的终端窗口,分别运行两个进程。
//终端1 进程a.c 创建管道
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
const char *fifoPath = "./myfifo";//'./'是指在当前目录下创建
mode_t fifoMode = 0600;//第一个0代表10进制,第二个6代表用户有读写权限,后面两个0代表组用户和其他用户无权限
// 使用 mkfifo 创建命名管道
if (mkfifo(fifoPath, fifoMode) == -1)
{
printf("mkfifo error\n");
exit(1);
}
printf("Named pipe created at %s\n", fifoPath);
exit(0);
}
先运行终端1,其运行结果如图:
//终端2 进程b.c 读取数据
#include <stdio.h>
#include<stdlib.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{
int fd = open("./myfifo", O_RDONLY);
char buffer[20];
read(fd, buffer, sizeof(buffer));
close(fd);
printf("Received: %s\n", buffer);
exit(0);
}
由于程序是以阻塞模式打开 命名管道并尝试读取数据。这意味着如果没有其他进程写入数据到该管道,它会一直等待,直到有数据可读为止。所以b.c运行结果如图:
再次打开终端1,并在其中写入如下程序:
//终端1 进程c.c 写入数据
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<string.h>
int main()
{
int fd = open("./myfifo", O_WRONLY);
const char *message = "Hello, Process B!";
write(fd, message, strlen(message) + 1);
close(fd);
exit(0);
}
当运行c.c后,终端2中b.c的运行结果如图:
匿名管道
int pipe( int fds[2]);//pipe()成功返回 0,失败返回-1;fds[0]是管道读端的描述符;fds[1]是管道写端的描述符。
实践:
用fork()创建子进程,父进程写入数据,子进程读取并输出数据。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include<string.h>
#include <sys/types.h>
int main()
{
int fd[2];
char buffer[100];
int res = pipe(fd); // 创建管道
pid_t pid = fork(); // 创建子进程
if (res == -1)
{
printf("pipe failed\n");
exit(1);
}
if (pid == -1)
{
printf("fork failed\n");
exit(1);
}
if (pid == 0) // 子进程
{
//sleep(1);
close(fd[1]); // 关闭写入端
read(fd[0], buffer, sizeof(buffer));
//fflush(stdout);
printf("Child received: %s\n", buffer);
}
else // 父进程
{
close(fd[0]); // 关闭读取端
const char *message = "Hello, Child!";
write(fd[1], message, strlen(message) + 1);
}
close(fd[0]);
close(fd[1]);
exit(0);
}
二.信号
kill()
int kill(pid_t pid, int sig);//用于向指定的进程发送信号。
//pid是要发送信号的目标进程的进程编号。
//sig是要发送的信号的编号。
实践:
打开两个独立的终端窗口,分别运行两个进程
//终端1 send.cpp
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
pid_t receiver_pid; // 需要知道接收方进程的pid
// 获取接收方进程的PID
printf("输入接收进程的pid: ");
scanf("%d", &receiver_pid);
// 发送自定义信号到接收方进程
if (kill(receiver_pid, SIGUSR1) == 0)
{
printf(" 发送给进程号为:%d的进程\n", receiver_pid);
}
else
{
perror("信号量发送失败");
}
return 0;
}
//终端2 recv.cpp
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
// 信号处理函数
void signal_handler(int signum)
{
if (signum == SIGUSR1)
{
printf("自定义信号量收到\n");
}
}
int main()
{
// 约定信号
signal(SIGUSR1, signal_handler);
printf("接收进程的pid为: %d\n", getpid());
while (1)
{
// 等待信号的到来
sleep(1);
}
return 0;
}
运行结果:
三.信号量
信号量是一个特殊的变量,一般取正数值。它的值代表允许访问的资源数目。
p操作--原子减一--代表获取资源--可能阻塞(当信号量值为0,即无资源可用时,会阻塞)
v操作--原子加一--代表释放资源--不会阻塞
semget()
int semget(key_t key, int nsems, int semflg);//创建一个新信号量或取得一个已有信号量的键。
//key,算是一个标识符,拥有相同key值的进程可以进行通讯
//nesms,通常取值为1
//semflg,通常是IPC_CREAT,不过使用IPC_CREAT时要加权限,如0666
//成功返回一个正数,失败返回-1
semctl()
int semctl(int semid, int semnum, int cmd, ... );//控制信号量信息。
//semid,由semget()返回
//semnum,一般取0
//cmd,用于指定要进行的操作,如:GETVAL(获取信号量的值) 、SETVAL(设置信号量的值), IPC_RMID(删除信号量)
//...,也许会用到这个参数,它是union semun类型的参数,用于传递额外信息
union semun
{
int val; //用于设置或获取单个信号量的值
struct semid_ds *buf; //用于传递IPC_STAT或IPC_SET命令的参数
unsigned short *array;//用于传递GETALL或SETALL命令的参数
};
实践:
用信号量和两个进程来模拟打印机:进程A输出'a',代表开始使用,再次输出'a'代表结束使用,进程B输出'b'代表开始使用,再次输出'b'代表结束使用,所以预期结果输出为aabbaabb......
/*sem.h文件*/
//封装P、V操作
#include<iostream>
#include<unistd.h>
#include<string.h>
#include<sys/sem.h>
using namespace std;
union semnum
{
int val;
struct semid_ds *buf;
unsigned short *array;
};
void sem_init(); //创建信号量
void sem_p(); //p操作
void sem_v(); //v操作
void sem_destroy();//销毁信号量
/*sem.cpp文件*/
#include"sem.h"
static int semid = -1;//初始化信号量
void sem_init()
{
semid = semget((key_t)1234, 1, IPC_CREAT | 0600);
union semnum a;
a.val = 1;//信号量的初始值
if(semctl(semid,0,SETVAL,a)==-1)
{
cout << "semctl failed" << endl;
}
}
void sem_p()
{
struct sembuf buf;
// 信号量的标志,通常使用 IPC_NOWAIT 或 SEM_UNDO
buf.sem_flg = SEM_UNDO;
// 信号量的编号,如果有多个信号量,可以使用不同的编号进行区分
buf.sem_num = 0;
buf.sem_op = -1;//p操作
if(semop(semid,&buf,1)==-1)
{
cout << "semop falied" << endl;
}
}
void sem_v()
{
struct sembuf buf;
buf.sem_flg = SEM_UNDO;
buf.sem_num = 0;
buf.sem_op = 1;//v操作
if(semop(semid,&buf,1)==-1)
{
cout << "semop falied" << endl;
}
}
void sem_destroy()
{
if(semctl(semid,0,IPC_RMID)==-1)
{
cout << "semctl move failed" << endl;
}
}
/*a.cpp文件*/
#include"sem.h"
int main()
{
sem_init();
for (int i = 0; i < 5;i++)
{
sem_p();
cout << "a";
fflush(stdout);
sleep(5);
cout << "a";
fflush(stdout);
sem_v();
sleep(5);
}
sleep(10);//等待另一个进程完成
sem_destroy();
return 0;
}
/*b.cpp文件*/
#include"sem.h"
int main()
{
sem_init();
for (int i = 0; i < 5;i++)
{
sem_p();
cout << "b";
fflush(stdout);
sleep(5);
cout << "b";
fflush(stdout);
sem_v();
sleep(5);
}
return 0;
}
另外两个重要的概念
临界资源:同一时刻,只允许被一个进程或线程访问的资源
临界区:访问临界资源的代码段
四.共享内存
共享内存是先在物理内存上申请一块空间,多个进程可以将其映射到自己的虚拟地址空间中。这是是一种用于在不同进程之间共享数据的机制,它允许多个进程访问同一块内存区域,从而实现高效的进程间通信。
shmget()
int shmget(key_t key, size_t size, int shmflg); //创建或获取共享内存段,并返回一个共享内存标识符,通常称为 shmid。
//key:共享内存标识键值
//size:指定要创建的共享内存段的大小
//shmflg:用于指定创建共享内存的权限和行为,通常是IPC_CREAT(表示如果指定的 IPC 资源不存在,则创建它,如果已经存在则忽略)或者IPC_EXCL(表示只创建 IPC 资源,如果资源已经存在,创建操作将失败)
shmat()
void *shmat(int shmid, const void *shmaddr, int shmflg);//用于将共享内存附加到进程的地址空间,以便进程可以访问共享内存中的数据。
//shmid:是由 shmget 函数创建的共享内存的标识符
//shmaddr:通常设置为 NULL
//shmflg:通常设置为0
shmdt()
int shmdt(const void *shmaddr);//用于将共享内存从进程的地址空间中分离,使得该进程不能再访问共享内存中的数据。
//shmaddr:是共享内存的地址,通常是在使用 shmat 函数时获得的指针。
shmctl()
int shmctl(int shmid, int cmd, struct shmid_ds *buf);//用于获取关于共享内存段的信息、修改权限、删除共享内存段等。
//shmid:使用 shmget 函数获得。
//cmd:指定要执行的操作,例如获取信息(IPC_STAT)、修改权限(IPC_SET)、删除共享内存段(IPC_RMID)等。
//buf:用于传递或接收有关共享内存段的信息。如果不需要传递或接收信息,可以将其设置为 NULL。
实践:
write进程和read进程通过共享内存分别来写入数据和读取数据。
/*shm.h文件*/
//封装P、V操作
#include<iostream>
#include<unistd.h>
#include<string.h>
#include<sys/sem.h>
using namespace std;
//创建两个信号量
enum INDEX
{
SEM1=0,
SEM2
};
union semnum
{
int val;
struct semid_ds *buf;
unsigned short *array;
};
void sem_init(); //创建信号量
void sem_p(enum INDEX i); //p操作
void sem_v(enum INDEX i); //v操作
void sem_destroy();//销毁信号量
/*shm.cpp文件*/
#include "shm.h"
static int semid = -1; // 初始化信号量
void sem_init()
{
semid = semget((key_t)1234, 2, IPC_CREAT | 0600); // 创建2个信号量
int arr[2] = {1, 0};
union semnum a;
for (int i = 0; i < 2; i++)
{
a.val = arr[i]; // 信号量的初始值
if (semctl(semid, i, SETVAL, a) == -1)
{
cout << "semctl failed" << endl;
}
}
}
void sem_p(enum INDEX i)
{
struct sembuf buf;
buf.sem_flg = SEM_UNDO;
buf.sem_num = i;
buf.sem_op = -1; // p操作
if (semop(semid, &buf, 1) == -1)
{
cout << "semop falied" << endl;
}
}
void sem_v(enum INDEX i)
{
struct sembuf buf;
buf.sem_flg = SEM_UNDO;
buf.sem_num = i;
buf.sem_op = 1; // v操作
if (semop(semid, &buf, 1) == -1)
{
cout << "semop falied" << endl;
}
}
void sem_destroy()
{
if (semctl(semid, 0, IPC_RMID) == -1)//销毁所有信号量
{
cout << "semctl move failed" << endl;
}
}
/*write.cpp文件*/
#include"shm.h"
#include <sys/shm.h>
int main()
{
// 创建共享内存
int shmid = shmget((key_t)1234, 128, IPC_CREAT | 0600);
if (shmid == -1)
{
cout << "write shget filed" << endl;
exit(1);
}
// 将共享内存映射到当前的地址空间
char *s = (char *)shmat(shmid, NULL, 0);
if (s == (char *)-1)
{
cout << "write shmat failed" << endl;
exit(1);
}
sem_init();
// 往共享内存中写入数据
while (1)
{
cout << "请输入内容:" << endl;
char buff[128] = {0};
cin >> buff;
sem_p(SEM1);
strcpy(s, buff);
sem_v(SEM2);
if (strncmp(buff, "end", 3) == 0)
{
break;
}
}
//分离共享内存
shmdt(s);
return 0;
}
/*read.cpp文件*/
#include"shm.h"
#include <sys/shm.h>
int main()
{
// 创建共享内存
int shmid = shmget((key_t)1234, 128, IPC_CREAT | 0600);
if (shmid == -1)
{
cout << "read shget filed" << endl;
exit(1);
}
// 将共享内存映射到当前的地址空间
char *s = (char *)shmat(shmid, NULL, 0);
if (s == (char *)-1)
{
cout << "read shmat failed" << endl;
exit(1);
}
sem_init();
// 从共享内存中读取数据
while (1)
{
sem_p(SEM2);
if (strncmp(s, "end", 3) == 0)
{
break;
}
cout << s << endl;
sem_v(SEM1);
}
//分离共享内存
shmdt(s);
//销毁共享内存
shmctl(shmid, IPC_RMID, NULL);
//销毁信号量
sem_destroy();
return 0;
}
运行结果:
五.消息队列
msgget()
int msgget(key_t key, int msgflg);//创建消息队列
//key,键值
//msgflg,用于指定消息队列的创建方式和权限
msgctl()
int msgctl(int msqid, int cmd, struct msqid_ds *buf);//控制消息队列
//msqid,消息队列的标识符,由msgget()返回
//cmd,操作命令
//*buf,消息队列信息的结构体指针
msgrcv()
int msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);//从消息队列中接收消息
//msgid,消息队列的标识符,由msgget()返回
//*msgp,消息缓冲区的指针,数据将被复制到这个缓冲区
//msgsz,缓冲区的大小,以字节为单位
//msgtyp,接收消息的类型,如果设为0,则不区分类型
//msgflg,控制信息接收的行为
msgsnd()
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);//用于向消息队列中发送信息
//msgid,消息队列的标识符,由msgget()返回
//*msgp,消息缓冲区的指针,其中包含要发送的数据
//msgsz,缓冲区的大小,以字节为单位
//msgflg,控制信息接收的行为
/*msga.cpp文件*/
#include<iostream>
#include<unistd.h>
#include<string.h>
#include<sys/msg.h>
using namespace std;
// 定义消息结构
struct message
{
long type;//必须为长整形
char buff[32];
};
int main()
{
int msgid = msgget((key_t)1234, IPC_CREAT | 0600);
if(msgid==-1)
{
cout << "msgget failed" << endl;
exit(1);
}
struct message dt;
dt.type = 1;//消息类型为1
strcpy(dt.buff, "hello");
msgsnd(msgid, &dt, 32, 0);
return 0;
}
/*msgb.cpp文件*/
#include<iostream>
#include<unistd.h>
#include<string.h>
#include<sys/msg.h>
using namespace std;
// 定义消息结构
struct message
{
long type;//必须为长整形
char buff[32];
};
int main()
{
int msgid = msgget((key_t)1234, IPC_CREAT | 0600);
if(msgid==-1)
{
cout << "msgb.cpp msgget failed" << endl;
exit(0);
}
struct message dt;
msgrcv(msgid, &dt, 32, 1, 0);
cout << "接收到内容为:" << dt.buff << "的消息" << endl;
return 0;
}
运行结果:
入上图:运行了2此msga.cpp,消息队列中有2条消息,当运行msgb.cpp之后,消息队列剩余消息如下:
六.socket
此部分详见文章用c语言编写简单的一对一服务器和多对一服务器。