进程通信 - d13

  • 1.简单进程间通信:命令行参数、环境变量、信号、文件
  • 2.传统进程间通信:管道(pipe/mkfifo)
  • 3.现代进程间通信:共享内存、消息队列、信号量
  • 4.网络进程间通信:套接字

练习:本地银行(ATM)

alternate text
#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。