一、 设计目的
理解虚拟设备的工作原理,理解守护程序的概念。
图7-1表示假脱机打印程序的工作原理。
在网络环境下,连在网络服务器上的打印机要为多个终端服务,每个终端上的用户都可以通过客户端程序向服务器发送打印请求,服务器端的打印请求接收程序接收来自客户端的打印请求,并将该请求存放到磁盘上的打印请求队列中,由服务器端的假脱机打印程序在CPU空闲时从打印请求队列中取出请求信息,并将文件输出到打印机中。这种工作方式不是将文件直接输出到打印机,而是先将待打印的文件缓存到磁盘上,然后立即返回用户程序,从而缩短了用户响应时间,为用户提供了虚拟的快速打印机。这里的磁盘缓存空间就是虚拟设备。服务器端的打印请求接收程序和打印程序都是守护程序,即从开机后就一直运行的程序。
二、 设计要求
利用多线程技术编写假脱机打印程序,并设计测试数据以验证程序的正确性。
1、界面要求:
程序采用简单的控制台界面,运行后在屏幕上显示功能菜单,列出该程序具有的功能,供用户选择。
2、功能要求:
(1)发送打印请求;
(2)查看假脱机打印队列;
(3)打印文件;
(4)退出。
用户选择功能后应该转到相应的处理程序,并在需要时显示程序的执行结果。
若用户选择(1)则提示用户输入待打印的文件名称,程序接收输入后将打印请求传送到打印队列中,并回到主菜单;
若用户选择(2)则在屏幕上列出打印队列情况,提示按任意键回到主界面;
若用户选择(3)则打印队首的文件,显示所打印的文件名称,按任意键回到主界面;
若用户选择(4)则退出程序的执行。
三、 算法设计与分析
字段名称 | 作用 |
file_name | 文件名称 |
file_size | 文件大小(以KB为单位) |
需要两个数据结构,一个FILE_INFO用来描述打印请求,包括文件名称和文件大小,如图7-2所示。另一个数据结构SPOOL用来描述打印请求队列,如图7-3所示。图7-4描述了程序运行时刻结构体SPOOL的内容。
字段名称 | 作用 |
spool_count | 记录打印队列中的文件个数 |
spool_in | 记录下一个打印请求存放的位置 |
spool_out | 记录下一个被打印文件的位置 |
spool_queue | 打印请求队列(用数组实现) |
spool_count | 3 |
|
spool_in | 3 |
|
spool_out | 0 |
|
spool_queue[0] | sever | 4KB |
spool_queue[1] | client | 3KB |
spool_queue[2] | myfile | 7KB |
spool_queue[3] |
|
|
spool_queue[4] |
|
1、线程划分
为了模拟假脱机打印程序,需要三个线程,主线程用于显示主菜单,接收用户的功能选择,显示打印队列情况;打印请求接收/发送线程接收用户的打印请求,并将打印请求存放到打印请求队列;打印线程用来从打印队列中取文件并将其输出到屏幕。
2、线程互斥要求
三个线程都需要通过控制台终端与用户交互,因此对终端的使用要互斥,以免屏幕混乱(用互斥体h_screen_mutex实现);三个线程都要访问打印请求队列,因此对它要进行互斥操作(用互斥体h_spool_mutex实现);
3、同步要求
主线程与打印请求接收/发送线程要同步,当用户选择功能(1)时,主线程要通知打印请求接收/发送线程开始接收用户请求(用初始值为0的信号量h_print实现),然后主线程要等待打印请求接收/发送线程发来接收完毕的信号(用初始值为0的信号量h_sendthread_to_mainthread实现);
主线程要与打印线程同步,当用户选择功能(3)时,主线程要通知打印线程开始打印文件(用初始值为0的信号量h_semaphore_spool实现),然后主线程要等待打印线程发来打印完毕的信号(用初始值为0的信号量h_spoolthread_to_mainthread实现);
(注意:在实际的系统中,打印线程不会等待用户从控制台发命令才开始打印,而是只要有打印请求且CPU空闲就循环不停地打印文件,直至打印队列为空,这时打印线程睡眠。本设计这样做是为了避免打印队列迅速变空。)
请求接收/发送线程和打印线程要同步,当打印队列为空时打印线程阻塞,直到请求接收/发送线程将新的请求放入队列;当打印队列满时,请求接收/发送线程阻塞,直到打印线程打印完一个文件空出新位置才能将其唤醒。这两个线程的同步遵循生产者/消费者模型,同步信号量为h_spool_empty和h_spool_full,前者跟踪空位置,后者跟踪打印请求。
4、函数设计
为了简化程序设计,我们只考虑单机环境,而且将打印请求队列存放在内存中,而不是存放在磁盘中,另外用屏幕输出模拟实际的打印机输出。该程序包括三个线程:主线程模拟客户端程序,sendthread线程模拟打印请求接收程序,spool_thread线程模拟打印程序。
该程序使用以下全局变量:
spool_buffer是打印请求队列,互斥体h_spool_mutex用来实现对spool_buffer的互斥访问,互斥体h_screen_mutex用来实现对终端的互斥访问,h_send和h_spool_thread分别是打印请求接收/发送线程和打印线程的句柄,h_semaphore_spool和h_spoolthread_to_mainthread是主线程和打印线程之间的同步信号量,h_print和h_sendthread_to_mainthread是主线程和打印请求接收/发送线程之间的同步信号量,h_spool_full和h_spool_empty是打印线程和打印请求接收/发送线程之间的同步信号量。
该程序共有五个函数,它们的名称及作用如图7-5所示。
函数名称 | 作用 |
sendthread | 接收用户的打印请求并将其发送到打印请求队列中 |
spool_thread | 从打印请求队列中取待打印文件并将其输出到屏幕上 |
print_space | 显示若干个空格 |
list_spool_queue | 列出打印队列 |
main | 创建线程,初始化信号量,显示主菜单,根据用户选择执行相应功能 |
图7-5 假脱机打印程序包括的函数及其作用 |
下面给出每个函数的算法描述。
(1)list_spool_queue函数
{
申请打印队列互斥使用权P(h_spool_mutex);
申请屏幕互斥使用权P(h_screen_mutex);
清屏;
显示打印队列中当前请求个数;
显示表头;
对请求队列中的每一个请求
{在屏幕上输出待打印文件名称以及大小};
释放打印队列互斥使用权V(h_spool_mutex);
释放屏幕互斥使用权V(h_screen_mutex);
}
(2)sendthread函数
{
while(1){
等待主线程发送唤醒信号直到用户选择“发送打印请求功能”P(h_print);
申请屏幕互斥使用权P(h_screen_mutex);
清屏;
提示并接收用户输入文件名称;
释放屏幕互斥使用权V(h_screen_mutex);
产生随机数作为文件大小;
申请打印队列中的空闲位置P(h_spool_empty);
申请打印队列互斥使用权P(h_spool_mutex);
将请求放入打印队列当前位置;
调整位置指针至下一位置;
释放打印队列互斥使用权V(h_spool_mutex);
通知打印线程多了一个打印请求V(h_spool_full);
唤醒主线程继续画主菜单V(h_sendthread_to_mainthread);
}
}
(3)spool_thread函数
{
while(1){
等待主线程发送唤醒信号直到用户选择“打印文件功能”P(h_semaphore_spool);
等待直到sendthread线程发来打印请求P(h_spool_full);
申请打印队列互斥使用权P(h_spool_mutex);
申请屏幕互斥使用权P(h_screen_mutex);
将打印队列中的文件数减1;
在屏幕上输出正在打印的文件名称;
回收该位置(置文件名称为空白,文件大小为0);
下调打印指针;
释放屏幕互斥使用权V(h_screen_mutex);
释放打印队列互斥使用权V(h_spool_mutex);
通知sendthread线程多了一个空位置V(h_spool_empty);
唤醒主线程继续画主菜单V(h_spoolthread_to_mainthread);
}
}
(4)main函数
{
创建两个互斥体;
创建六个同步信号量;
创建两个线程;
while(1){
申请屏幕互斥使用权P(h_screen_mutex);
显示主菜单;
接收用户功能选择;
清屏;
释放屏幕互斥使用权V(h_screen_mutex);
根据功能选择进行分支{
选择了功能1:
唤醒发送线程允许其发送请求到打印队列V(h_print);
等待发送线程发送完打印请求P(h_sendthread_to_mainthread);
跳出循环;
选择了功能2:
显示打印队列;
跳出循环;
选择了功能3:
唤醒打印线程允许其打印文件V(h_semaphore_spool);
等待打印线程打印完文件P(h_spoolthread_to_mainthread);
跳出循环;
选择了功能4:
返回;
}
}
}
然后就直接放代码好了
1 #include 由于实在是太菜了,所以就写了一大堆注释。 也不一定完全对,做个参考吧
2 #include
3 #include
4 #include
5 #include <string.h>
6 #define SIZE rand()%1000//取0-999中的一个随机数
7 typedef struct{
8 char file_name[100];//文件名称
9 int file_size; //文件大小
10 int v; //排队序号
11 }FILE_INFO; //文件结构体
12
13 typedef struct{
14 int spool_count; //队列中文件的个数
15 int spool_in; //下一个打印请求存放的位置
16 int spool_out; //下一个被打印文件的位置
17 FILE_INFO spool_queue[5];//打印请求队列
18 }SPOOL; //打印请求队列
19
20 SPOOL spool_buffer; //打印请求队列
21 HANDLE h_spool_mutex; //线程互斥 (三个线程都要访问打印请求队列,实现对spool_buffer的互斥访问)
22 HANDLE h_screen_mutex; //屏幕互斥 (对终端的互斥访问;避免屏幕混乱)
23
24 HANDLE h_send; //申明打印请求接收/发送线程
25 HANDLE h_spool_thread; //申明打印线程
26
27 HANDLE h_semaphore_spool; //主线程通知打印线程开始打印文件(主线程同步信号量)
28 HANDLE h_spoolthread_to_mainthread;//等待打印线程发来打印结束的信号量(打印线程同步信号量)
29
30 HANDLE h_sendthread_to_mainthread; //等待打印请求接收和发送线程结束的信号量
31 HANDLE h_print; //主线程要通知打印请求接收和发送线程开始接收用户请求的信号量
32
33 HANDLE h_spool_full; //打印请求个数
34 HANDLE h_spool_empty; //空位置
35
36 //接收用户的打印请求并将其发送到打印请求队列中
37 DWORD WINAPI sendthread(LPVOID lpParameter)
38 {
39 FILE_INFO file_info;
40 while(1)
41 {
42 WaitForSingleObject(h_print,INFINITE); //等待主线程发送唤醒信号直到用户选择“发送打印请求功能”
43 WaitForSingleObject(h_screen_mutex,INFINITE); // 申请屏幕互斥使用权
44 printf("输入文件名:"); //提示并接收用户输入文件名称
45 scanf("%s",file_info.file_name);
46 ReleaseMutex(h_screen_mutex); // 释放屏幕互斥使用权
47 srand( (unsigned)time( NULL ) ); //产生随机数作为文件大小
48 file_info.file_size=SIZE;
49 printf("文件大小为:%d\n",file_info.file_size);
50 WaitForSingleObject(h_spool_empty,INFINITE); //申请打印队列中的空闲位置
51 WaitForSingleObject(h_spool_mutex,INFINITE); //申请打印队列互斥使用权
52 spool_buffer.spool_count++;
53 file_info.v=spool_buffer.spool_count;
54 spool_buffer.spool_queue[spool_buffer.spool_in]=file_info; //将请求放入打印队列当前位置
55 spool_buffer.spool_in=(spool_buffer.spool_in+1)%5; //调整位置指针至下一位置
56 ReleaseMutex(h_spool_mutex); //释放打印队列互斥使用权
57 ReleaseSemaphore(h_spool_full,1,NULL); //通知打印线程多了一个打印请求
58 ReleaseSemaphore(h_sendthread_to_mainthread,1,NULL); //唤醒主线程继续画主菜单
59 }
60 }
61
62
63 //输出空格
64 void print_space(int num){
65 int i;
66 for(i=0;i
67 printf(" ");
68 }
69 }
70
71 //列出打印队列
72 void list_spool_queue()
73 {
74 char buffer[10];//存一下转换后的数据,字符型形式比较灵活?
75 WaitForSingleObject(h_spool_mutex,INFINITE); //结束阻塞状态,申请打印队列互斥使用权
76 WaitForSingleObject(h_screen_mutex,INFINITE); //申请屏幕互斥使用权
77 // system("cls"); 终端弹出所以清屏用不了
78 //显示表头
79 printf(" 假脱机队列中的文件数:%d\n\n",spool_buffer.spool_count);
80 printf(" 打印序列 \n");
81 printf("|--------|-------------------------------------|--------------|--------------|\n");
82 printf("| 序号 | 文件名 | 文件大小(KB) | 排队序号 |\n");
83 printf("|--------|-------------------------------------|--------------|--------------|\n");
84 for(int i=0;i<5;i++)
85 {
86 printf("| %d",i);
87 itoa(i, buffer, 10);//整型数字变量转换成字符数组变量
88 print_space(7-strlen(buffer));
89 printf("| %s",spool_buffer.spool_queue[i].file_name);
90 print_space(36-strlen(spool_buffer.spool_queue[i].file_name));
91 printf("| %d",spool_buffer.spool_queue[i].file_size);
92 itoa(spool_buffer.spool_queue[i].file_size,buffer,10);
93 print_space(12-strlen(buffer));
94 printf("| %d",spool_buffer.spool_queue[i].v);
95 itoa(spool_buffer.spool_queue[i].v,buffer,10);
96 print_space(12-strlen(buffer));
97 printf(" |\n");
98 }
99 printf("|--------|-------------------------------------|--------------|--------------|\n");
100
101 ReleaseMutex(h_spool_mutex); //释放打印队列互斥使用权V(h_spool_mutex);
102 ReleaseMutex(h_screen_mutex); //释放屏幕互斥使用权V(h_screen_mutex);
103 }
104
105
106 //从打印请求队列中取待打印文件并将其输出到屏幕上
107 DWORD WINAPI spool_thread(LPVOID lpParameter)
108 {
109 while(1)
110 {
111 WaitForSingleObject(h_semaphore_spool,INFINITE); //等待主线程发送唤醒信号直到用户选择“打印文件功能”
112 WaitForSingleObject(h_spool_full,INFINITE); //等待直到sendthread线程发来打印请求
113 WaitForSingleObject(h_spool_mutex,INFINITE); //申请打印队列互斥使用权
114 WaitForSingleObject(h_screen_mutex,INFINITE); //申请屏幕互斥使用权
115
116 spool_buffer.spool_count--; //将打印队列中的文件数减1
117 printf("打印一个文件:\n文件名:%s 文件大小:%d\n",spool_buffer.spool_queue[spool_buffer.spool_out].file_name,spool_buffer.spool_queue[spool_buffer.spool_out].file_size);
118 //在屏幕上输出正在打印的文件名称
119 strcpy(spool_buffer.spool_queue[spool_buffer.spool_out].file_name,"");
120 spool_buffer.spool_queue[spool_buffer.spool_out].file_size=0; // 回收该位置(置文件名称为空白,文件大小为0)
121 spool_buffer.spool_queue[spool_buffer.spool_out].v=0; //下调打印指针
122 for(int i=0;i<5;i++)
123 if(spool_buffer.spool_queue[i].v>0)
124 spool_buffer.spool_queue[i].v--;
125 spool_buffer.spool_out=(spool_buffer.spool_out+1)%5; //寻找下一个要打印文件的位置
126 ReleaseMutex(h_screen_mutex); //释放屏幕互斥使用权
127 ReleaseMutex(h_spool_mutex); //释放打印队列互斥使用权
128 ReleaseSemaphore(h_spool_empty,1,NULL); //通知sendthread线程多了一个空位置
129 ReleaseSemaphore(h_spoolthread_to_mainthread,1,NULL); //唤醒主线程继续画主菜单
130 }
131 return 0;
132 }
133
134 //主函数
135 int main(){
136 char select;
137 h_send=CreateThread(NULL,0,sendthread,NULL,0,NULL); //创建线程
138
139 h_spool_thread=CreateThread(NULL,0,spool_thread,NULL,0,NULL);
140 h_spool_mutex=CreateMutex(NULL,FALSE,NULL); //创建两个互斥体
141
142 h_screen_mutex=CreateMutex(NULL,FALSE,NULL);
143 h_spool_full=CreateSemaphore(NULL,0,5,NULL); //打印线程和打印请求接收/发送线程之间的同步信号量
144
145 h_spool_empty=CreateSemaphore(NULL,5,5,NULL);
146 h_print=CreateSemaphore(NULL,0,1,NULL); //主线程和打印请求接收/发送线程之间的同步信号量
147
148 h_sendthread_to_mainthread=CreateSemaphore(NULL,0,1,NULL);
149 h_semaphore_spool=CreateSemaphore(NULL,0,1,NULL); //主线程和打印线程之间的同步信号量
150
151 h_spoolthread_to_mainthread=CreateSemaphore(NULL,0,1,NULL);
152 WaitForSingleObject(h_screen_mutex,INFINITE); //申请屏幕互斥使用权
153
154 while(1){
155 printf("|-----------------------------------|\n");
156 printf("| (1):发送打印请求 |\n");
157 printf("| (2):查看假脱机打印队列 |\n");
158 printf("| (3):打印文件 |\n");
159 printf("| (4):退出 |\n");
160 printf("|-----------------------------------|\n");
161 printf("| 输入选择功能:");
162 do{
163 scanf("%c",&select);
164 }while(select!='1'&&select!='2'&&select!='3'&&select!='4');
165 //system("cls");
166 ReleaseMutex(h_screen_mutex);
167 switch(select){
168 case '1':
169 if(spool_buffer.spool_count<5){
170 ReleaseSemaphore(h_print,1,NULL); //唤醒发送线程允许其发送请求到打印队列V(h_print);
171 WaitForSingleObject(h_sendthread_to_mainthread,INFINITE); //等待发送线程发送完打印请求P(h_sendthread_to_mainthread);
172 }
173 else
174 printf("当前打印队列已满!\n");
175 break;
176
177 case '2':
178 if(spool_buffer.spool_count==0)
179 printf("当前打印队列为空!\n");
180 else
181 list_spool_queue(); //全部显示
182 break;
183
184 case '3':
185 if(spool_buffer.spool_count>0){
186 ReleaseSemaphore(h_semaphore_spool,1,NULL); //唤醒打印线程允许其打印文件V(h_semaphore_spool);
187 WaitForSingleObject(h_spoolthread_to_mainthread,INFINITE); //等待打印线程打印完文件P(h_spoolthread_to_mainthread);
188 }
189 else
190 printf("当前打印队列为空!\n");
191 break;
192
193 case '4':
194 return 0;
195 }
196 WaitForSingleObject(h_screen_mutex,INFINITE);
197 printf("\n按任意键回到菜单\n");
198 getch();
199 //system("cls");
200 ReleaseMutex(h_screen_mutex);
201 }
202 return 0;
203 }