假脱机打印程序与虚拟设备
一、 设计目的
理解虚拟设备的工作原理,理解守护程序的概念。
图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] |
|
7.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 #include2 #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 }
由于实在是太菜了,所以就写了一大堆注释。
也不一定完全对,做个参考吧