进程通信 - d13¶
- 1.简单进程间通信:命令行参数、环境变量、信号、文件
- 2.传统进程间通信:管道(pipe/mkfifo)
- 3.现代进程间通信:共享内存、消息队列、信号量
- 4.网络进程间通信:套接字
练习:本地银行(ATM)¶
#ifndef _BANK_H
#define _BANK_H
#include <sys/types.h>
#define KEY_REQUEST 0x12345678 // 请求队列
#define KEY_RESPOND 0x87654321 // 响应队列
#define TYPE_OPEN 8001 // 开户
#define TYPE_CLOSE 8002 // 清户
#define TYPE_SAVE 8003 // 存款
#define TYPE_WITHDRAW 8004 // 取款
#define TYPE_QUERY 8005 // 查询
#define TYPE_TRANSFER 8006 // 转账
// 账户
typedef struct tag_Account {
int id; // 账号
char name[256]; // 户名
char passwd[9]; // 密码
double balance; // 余额
} ACCOUNT;
// 开户请求
typedef struct tag_OpenRequest {
long type; // 类型
pid_t pid; // 进程ID
char name[256]; // 户名
char passwd[9]; // 密码
double balance; // 余额
} OPEN_REQUEST;
// 开户响应
typedef struct tag_OpenRespond {
long type; // 类型
char error[512]; // 错误
int id; // 账号
} OPEN_RESPOND;
// 查询请求
typedef struct tag_QueryRequest {
long type; // 类型
pid_t pid; // 进程ID
int id; // 账号
char name[256]; // 户名
char passwd[9]; // 密码
} QUERY_REQUEST;
// 查询响应
typedef struct tag_QueryRespond {
long type; // 类型
char error[512]; // 错误
double balance; // 余额
} QUERY_RESPOND;
// ...
#endif // _BANK_H
#ifndef _DAO_H
#define _DAO_H
#include "../inc/bank.h"
int dao_unique (void);
int dao_insert (ACCOUNT const* acc);
int dao_select (int id, ACCOUNT* acc);
int dao_update (ACCOUNT const* acc);
int dao_delete (int id);
#endif // _DAO_H
#include <stdio.h>
#include <unistd.h>
#include <limits.h>
#include <fcntl.h>
#include "../inc/dao.h"
char const* ID_FILE = "../db/id.dat";
int dao_unique (void) {
int id = 1000;
int fd = open (ID_FILE, O_RDWR | O_CREAT,
0644);
if (fd == -1) {
perror ("open");
return -1;
}
if (read (fd, &id, sizeof (id)) == -1) {
perror ("read");
return -1;
}
++id;
if (lseek (fd, 0, SEEK_SET) == -1) {
perror ("lseek");
return -1;
}
if (write (fd, &id, sizeof (id)) == -1) {
perror ("write");
return -1;
}
close (fd);
return id;
}
int dao_insert (ACCOUNT const* acc) {
char pathname[PATH_MAX+1];
sprintf (pathname, "../db/%d.acc",
acc->id);
int fd = creat (pathname, 0644);
if (fd == -1) {
perror ("creat");
return -1;
}
if (write (fd, acc, sizeof (*acc)) == -1) {
perror ("write");
return -1;
}
close (fd);
return 0;
}
int dao_select (int id, ACCOUNT* acc) {
char pathname[PATH_MAX+1];
sprintf (pathname, "../db/%d.acc", id);
int fd = open (pathname, O_RDONLY);
if (fd == -1) {
perror ("open");
return -1;
}
if (read (fd, acc, sizeof (*acc)) == -1) {
perror ("read");
return -1;
}
close (fd);
return 0;
}
int dao_update (ACCOUNT const* acc) {
char pathname[PATH_MAX+1];
sprintf (pathname, "../db/%d.acc",
acc->id);
int fd = open (pathname, O_WRONLY);
if (fd == -1) {
perror ("open");
return -1;
}
if (write (fd, acc, sizeof (*acc)) == -1) {
perror ("write");
return -1;
}
close (fd);
return 0;
}
int dao_delete (int id) {
char pathname[PATH_MAX+1];
sprintf (pathname, "../db/%d.acc", id);
if (unlink (pathname) == -1) {
perror ("unlink");
return -1;
}
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/msg.h>
#include "../inc/bank.h"
static int g_reqid = -1; // 请求队列
static int g_resid = -1; // 响应队列
void menu_loop (int (*menu) (void),
int (*on_menu[]) (void), size_t size) {
for (;;) {
int id = menu ();
if (id < 0 || size <= id)
printf ("无效选择!\n");
else if (on_menu[id] () == -1)
break;
}
}
int main_menu (void) {
printf ("--------\n");
printf ("本地银行\n");
printf ("--------\n");
printf ("[1] 开户\n");
printf ("[2] 清户\n");
printf ("[3] 存款\n");
printf ("[4] 取款\n");
printf ("[5] 查询\n");
printf ("[6] 转账\n");
printf ("[0] 退出\n");
printf ("--------\n");
printf ("请选择:");
int id = -1;
scanf ("%d", &id);
scanf ("%*[^\n]");
scanf ("%*c");
return id;
}
int on_quit (void) {
printf ("谢谢使用,再见!\n");
return -1;
}
int on_open (void) {
pid_t pid = getpid ();
OPEN_REQUEST req = {TYPE_OPEN, pid};
printf ("户名:");
scanf ("%s", req.name);
printf ("密码:");
scanf ("%s", req.passwd);
printf ("金额:");
scanf ("%lf", &req.balance);
if (msgsnd (g_reqid, &req, sizeof (req) -
sizeof (req.type), 0) == -1) {
perror ("msgsnd");
return 0;
}
OPEN_RESPOND res;
if (msgrcv (g_resid, &res, sizeof (res) -
sizeof (res.type), pid, 0) == -1) {
perror ("msgrcv");
return 0;
}
if (strlen (res.error)) {
printf ("%s\n", res.error);
return 0;
}
printf ("账号:%d\n", res.id);
return 0;
}
int on_close (void) {
printf ("此业务暂未开通。\n");
return 0;
}
int on_save (void) {
printf ("此业务暂未开通。\n");
return 0;
}
int on_withdraw (void) {
printf ("此业务暂未开通。\n");
return 0;
}
int on_query (void) {
pid_t pid = getpid ();
QUERY_REQUEST req = {TYPE_QUERY, pid};
printf ("账号:");
scanf ("%d", &req.id);
printf ("户名:");
scanf ("%s", req.name);
printf ("密码:");
scanf ("%s", req.passwd);
if (msgsnd (g_reqid, &req, sizeof (req) -
sizeof (req.type), 0) == -1) {
perror ("msgsnd");
return 0;
}
QUERY_RESPOND res;
if (msgrcv (g_resid, &res, sizeof (res) -
sizeof (res.type), pid, 0) == -1) {
perror ("msgrcv");
return 0;
}
if (strlen (res.error)) {
printf ("%s\n", res.error);
return 0;
}
printf ("余额:%.2lf\n", res.balance);
return 0;
}
int on_transfer (void) {
printf ("此业务暂未开通。\n");
return 0;
}
int main (void) {
if ((g_reqid = msgget (KEY_REQUEST,
0)) == -1) {
perror ("msgget");
return -1;
}
if ((g_resid = msgget (KEY_RESPOND,
0)) == -1) {
perror ("msgget");
return -1;
}
int (*on_menu[]) (void) = {
on_quit,
on_open,
on_close,
on_save,
on_withdraw,
on_query,
on_transfer};
menu_loop (main_menu, on_menu,
sizeof (on_menu) / sizeof (on_menu[0]));
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/msg.h>
#include "../inc/bank.h"
#include "../inc/dao.h"
void sigint (int signum) {
printf ("开户服务:即将停止。\n");
exit (0);
}
int main (void) {
if (signal (SIGINT, sigint) == SIG_ERR) {
perror ("signal");
return -1;
}
int reqid = msgget (KEY_REQUEST, 0);
if (reqid == -1) {
perror ("msgget");
return -1;
}
int resid = msgget (KEY_RESPOND, 0);
if (resid == -1) {
perror ("msgget");
return -1;
}
printf ("开户服务:启动就绪。\n");
for (;;) {
OPEN_REQUEST req;
if (msgrcv (reqid, &req, sizeof (req) -
sizeof (req.type), TYPE_OPEN, 0) == -1){
perror ("msgrcv");
return -1;
}
OPEN_RESPOND res = {req.pid, ""};
ACCOUNT acc;
if ((acc.id = dao_unique ()) == -1) {
sprintf (res.error, "创立账户失败!");
goto send_respond;
}
strcpy (acc.name, req.name);
strcpy (acc.passwd, req.passwd);
acc.balance = req.balance;
if (dao_insert (&acc) == -1) {
sprintf (res.error, "保存账户失败!");
goto send_respond;
}
res.id = acc.id;
send_respond:
if (msgsnd (resid, &res, sizeof (res) -
sizeof (res.type), 0) == -1) {
perror ("msgsnd");
continue;
}
}
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/msg.h>
#include "../inc/bank.h"
#include "../inc/dao.h"
void sigint (int signum) {
printf ("查询服务:即将停止。\n");
exit (0);
}
int main (void) {
if (signal (SIGINT, sigint) == SIG_ERR) {
perror ("signal");
return -1;
}
int reqid = msgget (KEY_REQUEST, 0);
if (reqid == -1) {
perror ("msgget");
return -1;
}
int resid = msgget (KEY_RESPOND, 0);
if (resid == -1) {
perror ("msgget");
return -1;
}
printf ("查询服务:启动就绪。\n");
for (;;) {
QUERY_REQUEST req;
if (msgrcv (reqid, &req, sizeof (req) -
sizeof (req.type), TYPE_QUERY, 0) ==-1){
perror ("msgrcv");
return -1;
}
QUERY_RESPOND res = {req.pid, ""};
ACCOUNT acc;
if (dao_select (req.id, &acc) == -1) {
sprintf (res.error, "无效账号!");
goto send_respond;
}
if (strcmp (req.name, acc.name)) {
sprintf (res.error, "无效户名!");
goto send_respond;
}
if (strcmp (req.passwd, acc.passwd)) {
sprintf (res.error, "密码错误!");
goto send_respond;
}
res.balance = acc.balance;
send_respond:
if (msgsnd (resid, &res, sizeof (res) -
sizeof (res.type), 0) == -1) {
perror ("msgsnd");
continue;
}
}
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>
#include <signal.h>
#include <sys/msg.h>
#include <wait.h>
#include "../inc/bank.h"
static int g_reqid = -1; // 请求队列
static int g_resid = -1; // 响应队列
typedef struct tag_Service {
char srv_path[PATH_MAX+1]; // 服务路径
pid_t srv_pid; // 服务进程ID
} SERVICE;
static SERVICE g_srv[] = {
{"./open", -1},
{"./query", -1}
// ...
};
int init (void) {
printf ("服务器初始化...\n");
if ((g_reqid = msgget (KEY_REQUEST,
0600 | IPC_CREAT | IPC_EXCL)) == -1) {
perror ("msgget");
return -1;
}
printf ("创建请求消息队列成功!\n");
if ((g_resid = msgget (KEY_RESPOND,
0600 | IPC_CREAT | IPC_EXCL)) == -1) {
perror ("msgget");
return -1;
}
printf ("创建响应消息队列成功!\n");
return 0;
}
void deinit (void) {
printf ("服务器终结化...\n");
if (msgctl (g_reqid, IPC_RMID, NULL) == -1)
perror ("msgctl");
else
printf ("销毁请求消息队列成功!\n");
if (msgctl (g_resid, IPC_RMID, NULL) == -1)
perror ("msgctl");
else
printf ("销毁响应消息队列成功!\n");
}
void start (void) {
printf ("启动业务服务...\n");
size_t i;
for (i = 0; i < sizeof (g_srv) /
sizeof (g_srv[0]); ++i) {
g_srv[i].srv_pid = vfork ();
if (g_srv[i].srv_pid == -1) {
perror ("vfork");
continue;
}
if (g_srv[i].srv_pid == 0)
if (execl (g_srv[i].srv_path,
g_srv[i].srv_path, NULL) == -1) {
perror ("execl");
_exit (EXIT_FAILURE);
}
}
}
void stop (void) {
printf ("停止业务服务...\n");
size_t i;
for (i = 0; i < sizeof (g_srv) /
sizeof (g_srv[0]); ++i) {
if (g_srv[i].srv_pid == -1)
continue;
if (kill (g_srv[i].srv_pid, SIGINT) == -1)
perror ("kill");
}
for (;;)
if (wait (0) == -1) {
if (errno == ECHILD)
break;
perror ("wait");
}
}
int main (void) {
atexit (deinit);
if (init () == -1)
return -1;
start ();
sleep (1);
printf ("按<回车>退出...\n");
getchar ();
stop ();
return 0;
}
一、信号量¶
1.基本特点¶
1)资源计数器,用于限制多个进程对有限共享资源的访问。
2)多个进程获取有限共享资源的操作模式
A.测试控制该资源的信号量;
B.若信号量大于0,则进程可以使用该资源,
为了表示此进程已获得该资源,需将信号量减1;
C.若信号量等于0,则进程休眠等待该资源,
直到信号量大于0,进程被唤醒,执行步骤A;
D.当某进程不再使用该资源时,信号量增1,
正在休眠中等待该资源的进程将被唤醒。
2.常用函数¶
#include <sys/sem.h>
/* 1)创建/获取信号量(集合) */
int semget (key_t key, int nsems, int semflg);
// A.该函数 以key参数为键值创建一个信号量集合
// (nsems参数表示该集合中信号量的个数),
// 或获取已有信号量集合(nsems取0)。
// B.semflg取值
// 0 - 获取,不存在即失败。
// IPC_CREAT - 创建,不存在即创建,
// 已存在即获取,除非...
// IPC_EXCL - 排斥,已存在即失败。
// C.成功返回信号量集合的标识,失败返回-1。
/* 2)操作信号量 */
int semop (int semid, struct sembuf* sops, unsigned int nsops);
struct sembuf {
unsigned short sem_num; // 信号量下标
short sem_op; // 操作数
short sem_flg; // 操作标记
};
// A.该函数对semid参数所标识的信号量集合中,
// 由sops参数所指向的包含nsops个元素的,
// 结构体数组中的每个元素,依次执行如下操作:
//
// a)若sem_op大于0,
// 则将sem_op加到信号量集合中第sem_num个信号量的计数值上,
// 以表示对资源的释放。
// b)若sem_op小于0,
// 则从信号量集合中第sem_num个信号量的计数值中减去其绝对值,
// 以表示对资源的获取。
// c)若信号量集合中第sem_num个信号量的计数值不够减(信号量的计数值不能为负),
// 此函数会阻塞,直到够减为止,以表示对资源的等待。
// d)若sem_flg包含IPC_NOWAIT位,即使被操作的信号量不够减,也不会阻塞,
// 而是返回-1,同时将errno置为EAGAIN。
// 以允许进程在等待资源的同时,完成其它任务。
// 5本三国演义,4本水浒传,3本西游记
int semid = semget (key, 3, IPC_CREAT | IPC_EXEL);
// 将所创建信号量集合中的3个元素依次初始化为:5、4和3
// ...
// 某人欲借2本三国演义和1本西游记
// struct sembuf sops[2];
// sops[0].sem_num = 0;
// sops[0].sem_op = -2;
// sops[1].sem_num = 2;
// sops[1].sem_op = -1;
// semop (semid, sops, 2);
// 还1本三国演义同时借2本水浒传
// struct sembuf sops[2];
// sops[0].sem_num = 0;
// sops[0].sem_op = 1;
// sops[1].sem_num = 1;
// sops[1].sem_op = -2;
// semop (semid, sops, 2);
/* 3)销毁/控制信号量 */
int semctl (int semid, int semnum, int cmd);
int semctl (int semid, int semnum, int cmd, union semun arg);
union semun {
int val; // cmd取SETVAL
struct semid_ds* buf; // cmd取IPC_STAT/IPC_SET
unsigned short* array; // cmd取GETALL/SETALL
struct seminfo* __buf; // cmd取IPC_INFO
};
// A.cmd取值
// IPC_STAT - 获取信号量集合的属性,通过arg.buf输出
// union semun arg;
// struct semid_ds buf;
// arg.buf = &buf;
semctl (semid, 0, IPC_STAT, arg);
struct semid_ds {
struct ipc_perm sem_perm; // 权限信息
time_t sem_otime; // 最后操作时间
time_t sem_ctime; // 最后改变时间
unsigned shrot sem_nsems; // 信号量个数
};
struct ipc_perm {
key_t __key; // 键值
uid_t uid; // 用户ID
gid_t gid; // 组ID
uid_t cuid; // 创建者用户ID
gid_t cgid; // 创建者组ID
unsigned short mode; // 权限字
unsigned short __seq; // 序列号
};
// IPC_SET - 设置信号量集合的属性,通过arg.buf输入。
// 只有以下三个属性可以设置:
// semid_ds::sem_perm.uid; // 用户ID
// semid_ds::sem_perm.gid; // 组ID
// semid_ds::sem_perm.mode; // 权限字
// IPC_RMID - 删除信号量集合。
semctl (semid, 0, IPC_RMID);
// 此时所有阻塞在该信号量集合上的semop函数都会立即返回失败,
// errno为EIDRM。
// GETALL - 获取信号量集合中所有信号量计数值。
short array[3];
union semun arg;
arg.array = array;
semctl (semid, 0, GETALL, arg);
// SETALL - 设置信号量集合中所有信号量计数值。
short array[3] = {5, 4, 3};
union semun arg;
arg.array = array;
semctl (semid, 0, SETALL, arg);
// GETVAL - 获取一个信号量的计数值。
int shuihuzhuan = semctl (semid, 1, GETVAL);
// SETVAL - 设置一个信号量的计数值。
union semun arg;
arg.val = 6;
semctl (semid, 1, SETVAL, arg);
// 注意:只有针对信号量集合中某个具体信号量的操作,
// 才会使用semnum参数。凡是针对信号量集合整体的操作,
// 该参数会被忽略。
// 成功返回值因cmd而异,失败返回-1。