假脱机打印程序与虚拟设备


一、 设计目的

 理解虚拟设备的工作原理,理解守护程序的概念。

7-1表示假脱机打印程序的工作原理。

在网络环境下,连在网络服务器上的打印机要为多个终端服务,每个终端上的用户都可以通过客户端程序向服务器发送打印请求,服务器端的打印请求接收程序接收来自客户端的打印请求,并将该请求存放到磁盘上的打印请求队列中,由服务器端的假脱机打印程序在CPU空闲时从打印请求队列中取出请求信息,并将文件输出到打印机中。这种工作方式不是将文件直接输出到打印机,而是先将待打印的文件缓存到磁盘上,然后立即返回用户程序,从而缩短了用户响应时间,为用户提供了虚拟的快速打印机。这里的磁盘缓存空间就是虚拟设备。服务器端的打印请求接收程序和打印程序都是守护程序,即从开机后就一直运行的程序。

 

二、 设计要求

  利用多线程技术编写假脱机打印程序,并设计测试数据以验证程序的正确性。

1、界面要求:

程序采用简单的控制台界面,运行后在屏幕上显示功能菜单,列出该程序具有的功能,供用户选择。

2、功能要求:

1)发送打印请求;

2)查看假脱机打印队列;

3)打印文件;

4)退出。

用户选择功能后应该转到相应的处理程序,并在需要时显示程序的执行结果。

若用户选择(1)则提示用户输入待打印的文件名称,程序接收输入后将打印请求传送到打印队列中,并回到主菜单;

若用户选择(2)则在屏幕上列出打印队列情况,提示按任意键回到主界面;

若用户选择(3)则打印队首的文件,显示所打印的文件名称,按任意键回到主界面;

若用户选择(4)则退出程序的执行。

三、 算法设计与分析

  1. 程序结构设计

字段名称

作用

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 #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 }

由于实在是太菜了,所以就写了一大堆注释。

也不一定完全对,做个参考吧

相关