进程间通信是为了数据传输、资源共享、通知事件、进程控制和信号。实现进程间通行的方式有管道、共享内存和消息队列。接下来要实现的售票系统就是通过共享内存的方式实现进程间通信。共享内存的实现有两个步骤,一是创建共享内存,二是映射共享内存。为了防止一张票被多次售出,即多个进程同时访问共享内存,售票系统还使用了信号量机制,以实现进程同步。信号量提供了这样的一种访问机制,它可以通过生成并使用令牌来授权,让一个临界区同一时间只有一个进程在访问它,也就是说信号量是用来调协进程对共享资源的访问的。临界区域是指执行数据更新的代码,需要独占式地执行。这样防止了出现因多个程序同时访问一个共享资源而引发的一系列问题。
本售票系统实现了普通用户与管理员的登录。当管理员没有登录时,普通用户不能售票,会被阻塞。当管理员登录后会初始化共享内存和信号量,各用户可以正常售票。同一次售票只能有一个管理员登录,管理员退出后,负责删除共享内存和信号量,普通用户无法售票,被强制退出。如果票售完,也会退出系统。各进程抢占式的访问共享内存。
接下来是售票系统的具体实现代码:
头文件:
semaphore.h
#ifndef __SEMAPHORE_H__
#define __SEMAPHORE_H__
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
union semun
{
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO(Linux specific) */
};
//信号量的初始化
int sem_init(int sem_id)
{
union semun sem;
sem.val = 1;
int ret = semctl(sem_id,0,SETVAL,sem);
return ret;
}
//信号量的P操作
int sem_p(int sem_id)
{
struct sembuf sem;
sem.sem_num = 0;
sem.sem_op = -1;
sem.sem_flg = SEM_UNDO;
int ret = semop(sem_id,&sem,1);
return ret;
}
//信号量的V操作
int sem_v(int sem_id)
{
struct sembuf sem;
sem.sem_num = 0;
sem.sem_op = 1;
sem.sem_flg = SEM_UNDO;
int ret = semop(sem_id,&sem,1);
return ret;
}
//信号量的删除
int sem_del(int sem_id)
{
int ret = semctl(sem_id,0,IPC_RMID);
return ret;
}
#endif //__SEMAPHORE_H__
源文件:
ticket.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <time.h>
#include "semaphore.h"
#define MAX 1024
#define NUM 5 //票的初始数量
char tmpch[MAX]; //用来使界面停留
int user; //用户权限标志
typedef struct _shm
{
int flag; //管理员退出标志
int ticket; //票数
}SHM;
//登录界面
int PrintA(void)
{
system("clear");
printf ("\t\t++++++++++++++++++++++++++++++++++++++++++++++\n\n");
printf ("\t\t+ 欢迎登录售票系统(v1.0) +\n\n");
printf ("\t\t+ 1、普通登录 +\n\n");
printf ("\t\t+ 2、管理员登录 +\n\n");
printf ("\t\t+ 3、退出登录 +\n\n");
printf ("\t\t+ MADE BY ZC +\n\n");
printf ("\t\t++++++++++++++++++++++++++++++++++++++++++++++\n\n");
printf ("\t\t请选择所要的操作:\n");
char num[3];
printf ("\t\t请输入整数1~3选择功能:");
scanf ("%2s",num);
if (strcmp("1",num) == 0)
return 1;
else if (strcmp("2",num) == 0)
return 2;
else if (strcmp("3",num) == 0)
return 3;
else
return 0;
}
//售票界面
int Interface(void)
{
system("clear");
printf ("\t\t++++++++++++++++++++++++++++++++++++++++++++++\n\n");
printf ("\t\t+ 欢迎使用售票系统(v1.0) +\n\n");
printf ("\t\t+ 1、售票 +\n\n");
printf ("\t\t+ 2、退出系统 +\n\n");
printf ("\t\t+ MADE BY ZC +\n\n");
printf ("\t\t++++++++++++++++++++++++++++++++++++++++++++++\n\n");
if (user == 1)
{
printf ("\t\t当前为普通用户\n");
}
else
{
printf ("\t\t当前为管理员\n");
}
printf ("\t\t请选择所要的操作:\n");
char num[3];
printf ("\t\t请输入整数1或2选择功能:");
scanf ("%2s",num);
if (strcmp("1",num) == 0)
return 1;
else if (strcmp("2",num) == 0)
return 2;
else
return 0;
}
//售票函数
int SellTicket(SHM * shm,int sem_id)
{
while(1)
{
switch (Interface())
{
case 1:
{
printf("\t\t等待开放票库...\n");
sem_p(sem_id); //P操作,上锁
if(shm->flag == 0) //判断管理员是否退出,若退出,当前用户强行退出
{
printf ("\t\t管理员已退出\n");
printf ("\t\t强制退出。。。\n");
return 0;
}
if(shm->ticket == 0) //判断票是否售完
{
sem_v(sem_id); //V操作,解锁
printf ("\t\t票售光,退出系统\n");
return 0;
}
printf ("\t\t卖出一张票,座位号是%d\n",shm->ticket);
shm->ticket--; //票数-1
sem_v(sem_id); //V操作,解锁
printf ("\t\t输入回车键。。。");
fgets(tmpch,MAX,stdin);
fgets(tmpch,MAX,stdin);
break;
}
case 2:
{
printf ("\t\t退出成功\n");
//判断是否为管理员退出,若是,将管理员退出标志置0
if(user == 2)
{
shm->flag = 0;
}
return 0;
}
default: //错误输入
{
printf ("\t\t输入有误,请输入整数1或2\n");
printf ("\t\t输入回车键。。。");
fgets(tmpch,MAX,stdin);
fgets(tmpch,MAX,stdin);
break;
}
}
}
}
int main()
{
char pwd[21] = {0}; //保存输入的密码
while(1)
{
user = PrintA(); //登录
if(user == 0)
{
printf ("\t\t输入有误,请输入整数1~3\n");
printf ("\t\t输入回车键。。。");
fgets(tmpch,MAX,stdin);
fgets(tmpch,MAX,stdin);
}
else if(user == 3)
{
printf ("\t\t退出成功\n");
return 0;
}
else if(user == 2)
{
printf ("\t\t请输入管理员密码:");
fgets (pwd,21,stdin);
fgets (pwd,21,stdin);
if (strcmp("123456\n",pwd)) //判断管理员密码
{
printf ("\t\t密码错误\n");
printf ("\t\t输入回车键。。。");
fgets(tmpch,MAX,stdin);
continue;
}
printf ("\t\t登录成功\n");
printf ("\t\t输入回车键。。。");
fgets(tmpch,MAX,stdin);
break;
}
else
{
printf ("\t\t登录成功\n");
printf ("\t\t输入回车键。。。");
fgets(tmpch,MAX,stdin);
fgets(tmpch,MAX,stdin);
break;
}
}
srand((unsigned int)time(NULL)); //随机数种子
int shmid = shmget((key_t)1234,sizeof(SHM),0666 | IPC_CREAT); //创建或获取一块共享内存
if (shmid == -1)
{
perror("shmget");
return -1;
}
int sem_id = semget((key_t)123, 1, 0666 | IPC_CREAT); //创建一个信号量
if(sem_id == -1)
{
perror("semget");
return -1;
}
SHM * shm = (SHM*)shmat(shmid,NULL,0); //映射共享内存
if (shm == (SHM*)-1)
{
perror("shmat");
return -1;
}
if(user == 2 && shm->flag == 1) //当管理员登录时,判断是否已有管理员登录,若有,强制退出
{
printf ("\t\t管理员已登录,不可以再次有管理员登录\n");
printf ("\t\t强制退出。。。\n");
return 0;
}
if(user == 2) //管理员负责初始化管理员退出标志、票数、信号量
{
shm->flag = 1; //将管理员退出标志置1
shm->ticket = NUM; //初始化票数
sem_init(sem_id); //初始化信号量
}
SellTicket(shm,sem_id); //售票
if(user == 2) //管理员负责解除映射、删除共享内存、删除信号量
{
shmdt(NULL); //解除映射
shmctl(shmid,IPC_RMID,NULL); //删除共享内存
sem_del(sem_id); //删除信号量
}
return 0;
}
有关共享内存和信号量的更多知识,希望和大家一起讨论分享。