2022年12月29日发(作者:abraham)发信人: SuperSB (孤鹰), 信区: Linux
标 题: [转载]unix环境高级编程-17
发信站: BBS 水木清华站 (Wed Mar 15 14:31:48 2000)
发信人: taosm (128+64-->cool), 信区: unix
标 题: unix环境高级编程--第17章 与PostScript打印机
发信站: 西十八BBS (Sat Mar 11 14:13:45 2000), 转信
第十七章 与PostScript打印机通信
17.1 引言
我们现在开发一个可以与Postscript打印机通信的程序。PostScript打印机目
前使用很广,它一般通过RS-232端口与主机相连。这样就使得我们有可能使用第十
一章中的终端I/O函数。同样,与PostScript打印机通信是全双工的,我们发送数
据给打印机时也要准备好从打印机读取状态消息。这样,我们又有可能使用12.5节
中的I/O多路转接函数:lect 和poll。我们所开发的这个程序是基于James Cla
rk 所写的lprps程序。这个程序和其他一些程序共同组成lprps软件包,可以在co
新闻组中找到(Volume 21,1991年7月)。
17.2 PostScript 通信机制
关于PostScript打印机所需要知道的第一件事就是我们并不是发送一个文件给
打印机去打印-我们只是发送一个PostScript程序给打印机让它去执行。在PostSc
ript打印机中通常有一个PostScript解释器来执行这个程序,生成输出的页面。如
果PostScript程序有错误,PostScript打印机(实际上是PostScript解释器)返回
一个错误消息,或许还会产生其他输出。
下面的PostScript程序在输出页面上生成一个熟悉的字符串hello, world。(
我们这里并不叙述PostScript编程,详细情况请参见Adobe Systems[1985和1986]
。我们着重在与PostScript打印机的通信上)。
%!
/Times-Roman findfont
15 scalefont % point size of 15
tfont % establish current font
300 350 moveto % x=300 y=350 (position on page)
(hello, world) show % output the string to current page
showpage % and output page to output device
如果我们在PostScript程序中改变tfont 为stfont,再把它发送到PostS
cript打印机,结果是什么也没有被打印。相反的,我们从打印机得到以下消息:
%% [ Error: undefined; OffendingCommand: stfont ]%%
%% [ Flushing: rest of job (to end-of-file) will be ignored ]%%
这些错误消息随时都可能产生,这也是处理PostScript打印机复杂的地方。我
们不能只是将整个PostScript程序发送给打印机后就不管了-我们必须处理这些潜
在的错误消息。(在这一章中,我们所说的"打印机",就是指PostScript解释器。
)
PostScript打印机通常通过RS-232串口与主机相连。这就如同终端的连接一样
,所以我们在第十一章中的终端I/O函数在这里也适用。(PostScript打印机也
可
以通过其它方式连接到主机上,例如网络接口逐渐流行。但目前占主导地位的还是
串口相连。)图17.2 显示了典型的工作过程。一个PostScript程序可以产生两种
形式的输出:通过showpage操作输出到打印机页面上,或者通过print操作输出到
它的标准输出(在这里是与主机的串口连接)。
PostScript解释器发送和接受的是七位ASCII字符。PostScript程序可包含所
有可打印的ASCII字符。一些不可以打印的字符有着特殊的含义(参见图17.1)。
图17.1 从主机发送到PostScript打印机的特殊字符
图17.2 用串口连接与Postscript打印机通信
PostScript的文件终止符(Control-D)用来同步打印机和主机。我们发送一
个PostScript程序到打印机,然后再发送一个EOF到打印机。当打印机执行完Post
Script程序后,它就发回一个EOF。
当PostScript解释器正在执行一个PostScript程序时,我们可以向它发送一个
中断(Control-C)。这通常使正在被打印机执行的程序终止。
状态查询消息(Control-T)会使得打印机返回一个一行的状态消息。所有的打印
机消息都是如下格式:
%% [ key : val ] %%
所有可能出现在状态消息中的key: val对,被分号分开。回忆前面例子的返回
消息:
%% [ Error: undefined; OffendingCommand: stfont ]%%
%% [ Flushing: rest of job (to end-of-file) will be ignored ]%%
这个状态消息具有这个格式:
%% [status : idle ]%%
除了idle(没有作业)外,其它状态指示还有busy(正在执行一个PostScrip
t程序)、 waiting(正在等待执行PostScript程序)、 printing(打印中)、i
nitializing(初始化)和printing test page(正打印测试页)。
现在来考虑PostScript解释器自己产生的状态消息。我们看到以下的消息
%% [ Error: error ; OffendingCommand : operator ]%%
总共大约会发生25种不同的错误。通常的错误有dictstackunderflow, invalidac
cess, typecheck, 和undefined。这里的operator是产生错误的PostScript操作。
一个打印机的错误用以下形式来指示。
%% [ PrinterError: reason ]%%
这儿的reason一般是Out Of Paper(缺纸)、Cover Open(盖打开)等错误。
当错误发生时,PostScript解释器经常会发出另外一个消息
%% [ Flushing : rest of job (to end-of-file) will be ignored ] %%
我们看一下在特殊字符序列%%[和]%%中的字符串,为了处理这个消息,我们必
须分析该字符串。一个PostScript程序也可以通过PostScript的print操作来产生
输出。这个输出应当传给发送程序给打印机的用户(虽然这并不是我们的打印程序
所期望解释的)。
图17.3 列出了PostScript解释器传送给主机的特殊字符。
图17.3 PostScript解释器传送
给主机的特殊字符
17.3 假脱机打印
我们在本章所开发的程序通过两种方式发送PostScript程序给PostScript打印
机,单独的方式或者通过BSD行式打印机假脱机系统。通常使用假脱机系统,但提
供一个独立的方式也是很有用的,如用于测试等。
Unix SVR4同样提供了一个假脱机打印系统,在AT&T手册[1991] 第一部分以l
p开头的手册页中可以找到假脱机系统的详细资料。Stevens[1990]的第13章详细说
明了BSD和pre-SVR4的假脱机系统。我们在这一章中并不着重在假脱机系统上,而
在于与PostScript打印机的通信。
在BSD的假脱机系统中,我们以如下形式打印一个文件
lpr -pps main.c
这个命令发送文件main.c到名为ps的打印机。如果我们没有指定-pps的选项,
那么或者输出到PRINTER环境变量对应的打印机上,或者输出到缺省的lp打印机上
。所用的打印机参数可以在/etc/printcap文件中查到。图17.4是对应一个PostSc
ript打印机的一项。
lp|ps:
:br#19200:lp=/dev/ttyb:
:sf:sh:rw:
:fc#0000374:fs#0000003:xc#0:xs#0040040:
:af=/var/adm/psacct:lf=/var/adm/pslog:sd=/var/spool/pslpd:
:if=/usr/local/lib/psif:
图17.4 一个PostScript打印机对应的printcap项
第一行给出了这个项的名称,ps或者lp。br的值指定了波特率是19200。lp指
定了该打印机的特殊设备文件路径名。sf是格式送纸,sh是指打印作业的开始加入
一个打印页头,rw是指定打印机以读写方式打开。如17.2节中所述,这一项是Pos
tScript打印机所必须的。
下面的四个域指定了在旧版本BSD风格的stty结构中需要打开和关闭的位。(
我们在这里对此进行叙述是因为大多数使用printcap文件的BSD系统都支持这种老
式的设置终端方式的方法。在这一章的源程序文件中,我们可以看到如何使用第十
一章中所述的POSIX.1函数来设置所有的终端参数。)首先,fc掩码清除sg_flags
元素中的下列值:EVENP和ODDP(关闭了奇偶校验)、RAW(关闭raw模式)、CRMO
D(关闭了在输入输出中的CR/LF映射)、ECHO(关闭回显)和LCASE(关闭输入输
出中的大小写映射)。然后,fs掩码打开了以下位: CBREAK(一次输入一个字符
)和TANDEM(主机产生Control-S,Control-Q流控制)。接着,xc掩码清除了本地
模式字中各位。最后,xs掩码设置了本地模式字中的下列两位:LDECCTQ(Contro
l-Q重新开始输出,Control-S则停止输出)和LLITOUT(压缩输出转换)。
af和lf字符串分别指定了记帐文件和日志文件。sd指定了假脱机的目录,而i
f指定了输入过滤器。
输入过滤器可被所有的打印文件所激活,格式如下:
filter -n loginname -h hostname acctfile
这里还有几个可选
的参数(这些参数有可能被PostScript打印机所忽略)。要
打印的文件在标准输入中,打印机(printcap文件中的lp项)设在标准输出。标准
输入也可以是一个管道。
使用一个PostScript打印机,输入过滤器首先查询输入文件的开始两个字符,
确定这个文件是一个ASCII文本文件还是一个PostScript程序。通常的惯例是前两
个字符为%!表示这是一个PostScript程序。如果这个文件是PostScript程序,lpr
ps程序(在下面会详细讨论)就把它发送到打印机。如果这个文件是文本文件,就
使用其他程序将它转换成PostScript程序。
在printcap文件中提到的psif过滤器是lprps软件包提供的。在这个包中的te
xtps可以将文本文件转换成PostScript程序。图17.5概略表示了这些程序。
图17.5 lprps系统示意图
图中有一个程序psrev没有表示出来,该程序将PostScript程序生成的页面反
转过来,当PostScript程序打印机在正面而不是在反面打印输出时,就可以使用此
程序。
概述以后,我们就来看一下lprps程序的设计和编码。
17.4 源程序
我们先看main()调用的函数,以及它们是如何与打印机交互作用的。图17.
6详细表明了这种相互作用。图中第二列标注为"int?",它指定该函数是否可以通
过接受SIGINT信号而中断。第三栏指定了各个函数的超时设置(以秒为单位)。注
意,当我们发送用户的PostScript程序到打印机时,没有超时设置。这是因为一个
PostScript程序可能用任意长的时间来执行。函数get_page行中的"我们的PostSc
ript程序"是指程序17.9,这个程序是用来记录当前页码的。
程序17.1列出了头文件lprps.h。该文件包含在所有的源文件中。该头文件包
含了各个源程序所需的系统头文件,定义了全局变量和全局函数的原型。
图17.6 被主程序调用的函数
_______________________________________________________________________
________
#include
#include
#include
#include
#include /* since we're a daemon */
#include "ourhdr.h"
#define EXIT_SUCCESS 0 /* defined by BSD spooling system */
#define EXIT_REPRINT 1
#define EXIT_THROW_AWAY 2
#define DEF_DEVICE "/dev/ttyb" /* defaults for debug mode */
#define DEF_BAUD B19200
/* modify following as a
propriate */
#define MAILCMD "mail -s "printer job" %s@%s < %s"
#define OBSIZE 1024 /* output buffer */
#define IBSIZE 1024 /* input buffer */
#define MBSIZE 1024 /* message buffer */
/* declare global variables */
extern char *loginname;
extern char *ho
stname;
extern char *acct_file;
extern char eofc; /* PS end-of-file (004) */
extern int debug; /* true if interactive (not a daemon) */
extern int in_job; /* true if nding ur's PS job to printer */
extern int psfd; /* file descriptor for PostScript printer */
extern int start_page;/* starting page# */
#include /* since we're a daemon */
#include "ourhdr.h"
#define EXIT_SUCCESS 0 /* defined by BSD spooling system */
#define EXIT_REPRINT 1
#define EXIT_THROW_AWAY 2
#define DEF_DEVICE "/dev/ttyb" /* defaults for debug mode */
#define DEF_BAUD B19200
/* modify following as a
propriate */
#define MAILCMD "mail -s "printer job" %s@%s < %s"
#define OBSIZE 1024 /* output buffer */
#define IBSIZE 1024 /* input buffer */
#define MBSIZE 1024 /* message buffer */
/* declare global variables */
extern char *loginname;
extern char *hostname;
extern char *acct_file;
extern char eofc; /* PS end-of-file (004) */
extern int debug; /* true if interactive (not a daemon) */
extern int in_job; /* true if nding ur's PS job to printer */
extern int psfd; /* file descriptor for PostScript printer */
extern int start_page;/* starting page# */
void msg_init(void); /* message.c */
void msg_char(int);
void proc_msg(void);
void out_char(int); /* output.c */
void get_page(int *); /* pagecount.c */
void nd_file(void); /* ndfile.c */
void block_write(const char *, int); /* tty.c */
void tty_flush(void);
void t_block(void);
void t_nonblock(void);
void tty_open(void);
_______________________________________________________________________
________
程序17.1 lprps.h头文件
文件vars.c(程序17.2)定义了全局变量。
程序17.3是main函数。因为此程序一般是作为精灵进程运行的,所以main函数调用
log_open函数(见附录B)。我们不能将错误消息写到标准错误上-为此我们使用了
13.4.2小节中描述的syslog。
_______________________________________________________________________
________
#include "lprps.h"
char *loginname;
char *hostname;
char *acct_file;
char eofc = '004'; /* Control-D = PostScript EOF */
int psfd = STDOUT_FILENO;
int start_page = -1;
int end_page = -1;
int debug;
int in_job;
volatile sig_atomic_t intr_flag;
volatile sig_atomic_t alrm_flag;
enum status status = INVALID;
_______________________________________________________
________________
________
程序17.2 声明全局变量
_______________________________________________________________________
________
#include "lprps.h"
static void usage(void);
int
main(int argc, char *argv[])
{
int c;
log_open("lprps", LOG_PID, LOG_LPR);
opterr = 0; /* don't want getopt() writing to stderr */
while ( (c = getopt(argc, argv, "cdh:i:l:n:x:y:w:")) != EOF) {
switch (c) {
ca 'c': /* control chars to be pasd */
ca 'x': /* horizontal page size */
ca 'y': /* vertical page size */
ca 'w': /* width */
ca 'l': /* length */
ca 'i': /* indent */
break; /* not interested in the */
ca 'd': /* debug (interactive) */
debug = 1;
break;
ca 'n': /* login name of ur */
loginname = optarg;
break;
ca 'h': /* host name of ur */
hostname = optarg;
break;
ca '?':
log_msg("unrecognized option: -%c", optopt);
usage();
}
}
if (hostname == NULL || loginname == NULL)
usage(); /* require both hostname and loginname */
if (optind < argc)
acct_file = argv[optind]; /* remaining arg = acct file */
if (debug)
tty_open();
if (atexit(clo_mailfp) < 0) /* register func for exit() */
log_sys("main: atexit error");
get_status();
get_page(&start_page);
nd_file(); /* copies stdin to printer */
get_page(&end_page);
do_acct();
exit(EXIT_SUCCESS);
}
static void
usage(void)
{
log_msg("lprps: invalid arguments");
exit(EXIT_THROW_AWAY);
}
_______________________________________________________________________
________
程序17.3 main函数
然后处理命令行参数,很多参数会被PostScript打印机所忽略。我们使用-d标
志指示这个程序是交互运行,而不是作为精灵进程。如果设置了这个标志,我们需
要初始化终端模式(tty_open)。然后我们将函数clo_mailfp指定为退出处理程
序。
我们就可以调用在图17.6中提到的函数:取得打印机状态保证它是就绪的(ge
t_status),得到打印机的起始页码(get_page),发送文件(PostScript程序)到
打印机(nd_file),得到打印机的结束页码(get_page),写记帐记
录(do_acct),
然后终止。
文件acct.c定义了函数do_acct(程序17。4)。它在main函数的结尾处被调用,
用来写下记帐记录。记帐文件的路径和名字从printcap文件中的相应记录项(图1
7.4)获得,并作为命令行的最后一个参数。
_______________________________________________________________________
________
#include "lprps.h"
/* Write the number of pages, hostname, and loginname to the
* accounting file. This function is called by main() at the end
* (if all was OK, by printer_flushing(), and by handle_intr() if
* an interrupt is received. */
void
do_acct(void)
{
FILE *fp;
if (end_page > start_page &&
acct_file != NULL &&
(fp = fopen(acct_file, "a")) != NULL) {
fprintf(fp, "%7.2f %s:%s
",
(double)(end_page - start_page),
hostname, loginname);
if (fclo(fp) == EOF)
log_sys("do_acct: fclo error");
}
}
_______________________________________________________________________
________
程序17.4 do_acct函数
从历史上看,所有的BSD打印过滤器都使用%7.2f的printf格式,把输出的页数
写到记帐文件中。这样就允许光栅设备不使用页数,而以英尺为单位报告输出长度
。
下面一个文件是tty.c(程序17.5),它包含了所有的终端I/O函数。它们调用
我们在第三章中提到的函数(fcntl, write和open)和第11章中的POSIX..1终端函
数(tcflush, tcgetattr, tctattr, cftispeed 和cftospeed)。如果我们
允许发生写阻塞,那么要调用block_write函数。如果我们不希望发生阻塞,则调
用t_nonblock函数,然后再调用read或者write函数。因为PostScript是一个全
双工的设备,打印机有可能发送数据回来(例如出错消息等),所以我们不希望阻
塞一个方向的写操作。如果打印机发送错误消息时,我们正因向其发送数据而处于
阻塞状态,则会出现死锁。
一般是核心为终端输入和输出进行缓冲,所以如果发生错误,我们可以调用t
ty_flush来刷清输入和输出队列。
如果以交互方式运行该程序,那么main函数将调用函数tty_open。我们需要把
终端设为非规范模式,设置波特率和其他一些终端标志。注意各种PostScript打印
机的这些设置并不都一样。检查你的打印机手册确定它的设置。(数据的位数可能
是7位或8位,起始位、停止位的数目以及奇偶校验等都可能因打印机而异。)
_______________________________________________________________________
_______
#include "lprps.h"
#include
#include
static int
block_flag = 1; /* default is blocking I/O */
void
t_block(void) /* turn off nonblocking flag */
{ /* called only by block_write() below */
int val;
if (block_flag == 0) {
if ( (val = fcntl(psfd, F_GETFL, 0)) < 0)
log_sys("t_block: fcntl F_GETFL error");
val &= ~O_NONBLOCK;
if (fcntl(psfd, F_SETFL, val) < 0)
log_sys("t_block: fcntl F_SETFL error");
block_flag = 1;
}
}
void
t_nonblock(void) /* t descriptor nonblocking */
{
int val;
if (block_flag) {
if ( (val = fcntl(psfd, F_GETFL, 0)) < 0)
log_sys("t_nonblock: fcntl F_GETFL error");
val |= O_NONBLOCK;
if (fcntl(psfd, F_SETFL, val) < 0)
log_sys("t_nonblock: fcntl F_SETFL error");
block_flag = 0;
}
}
void
block_write(const char *buf, int n)
{
t_block();
if (write(psfd, buf, n) != n)
log_sys("block_write: write error");
}
void
tty_flush(void) /* flush (empty) tty input and output queues */
{
if (tcflush(psfd, TCIOFLUSH) < 0)
log_sys("tty_flush: tcflush error");
}
void
tty_open(void)
{
struct termios term;
if ( (psfd = open(DEF_DEVICE, O_RDWR)) < 0)
log_sys("tty_open: open error");
if (tcgetattr(psfd, &term) < 0) /* fetch attributes */
log_sys("tty_open: tcgetattr error");
term.c_cflag = CS8 | /* 8-bit data */
CREAD | /* enable receiv
r */
CLOCAL; /* ignore modem
tatus lines */
/* no pa
ity, 1 stop bit */
term.c_oflag &= ~OPOST; /* turn off post processing */
term.c_iflag = IXON | IXOFF | /* Xon/Xoff flow control */
IGNBRK | /* ignore breaks
*/
ISTRIP | /* strip input t
7 bits */
IGNCR; /* ignore receiv
d CR */
term.c_lflag = 0; /* everything off in local flag:
disables canonical mo
e, disables
signal generation, di
ables echo */
term.c_cc[VMIN] = 1; /* 1 byte at a time, no timer */
term.c_cc[VTIME] = 0;
cftispeed(&term, DEF_BAUD);
cftospeed(&term, DEF_BAUD);
if (tctattr(psfd,
TCSANOW, &term) < 0) /* t attributes */
log_sys("tty_open: tctattr error");
}
_______________________________________________________________________
_______
程序17.5 终端函数
这个程序处理两个信号:SIGINT和SIGALRM。处理SIGINT是对BSD假脱机系统调
用的任何一种过滤器的要求。如果打印机作业被lprm(1)命令删除,那么这个信号
被发送给过滤器。我们使用SIGALRM来设置超时,对这两个信号用类似的方式处理
:我们提供了t_XXX函数来建立信号处理器,clear_XXX函数来取消这个信号处理
器。如果有信号传送给这个进程,信号处理器在设置一个全局的标记intr_flag和
alrm_flag后返回。程序的其它部分可在适当的时间来检测这些标记。有一个明显
的时间是在I/O函数返回错误EINTR时,该程序然后调用handle_intr或者handle_a
lrm来处理这种情况,调用signal_intr函数(程序10.13)来中断一个慢速的系统
调用。程序17.6是处理SIGINT信号的interrupt.c文件。
当一个中断发生时,我们必须发送PostScript的中断字符(Control-C)给打
印机,接着发送一个文件终止符(EOF)。这通常引起PostScript解释器终止它正在
解释的程序。然后我们等待从打印机返回的EOF(我们稍后将描述proc_upto_eof函
数)。我们读取最后的页码,写下记帐记录,然后就可以终止了。
_______________________________________________________________________
______
#include "lprps.h"
static void
sig_int(int signo) /* SIGINT handler */
{
intr_flag = 1;
return;
}
/* This function is called after SIGINT has been delivered,
* and the main loop has recognized it. (It not called as
* a signal handler, t_intr() above is the handler.) */
void
handle_intr(void)
{
char c;
intr_flag = 0;
clear_intr(); /* turn signal off */
t_alrm(30); /* 30 cond timeout to interrupt printer */
tty_flush(); /* discard any queued output */
c = '003';
block_write(&c, 1); /* Control-C interrupts the PS job */
block_write(&eofc, 1); /* followed by EOF */
proc_upto_eof(1); /* read & ignore up through EOF */
clear_alrm();
get_page(&end_page);
do_acct();
exit(EXIT_SUCCESS); /* success since ur lprm'ed the job */
}
void
t_intr(void) /* enable signal handler */
{
if (signal_intr(SIGINT, sig_int) == SIG_ERR)
log_sys("t_intr: signal_intr error");
}
void
clear_intr(void) /* ignore signal */
{
if (signal(SIGINT, SIG_IGN) == SIG_ERR)
log_sys("clear_intr: signal error");
}
______________________________________________
_________________________
______
程序17.6 处理中断信号的interrupt.c文件
图17.6写明了哪些函数设置超时时间。我们只是在以下情况下设置超时:查询
打印机状态(get_status)、读取打印机的页码(get_page)或者当我们正中断打印机
时(handle_intr)。如果发生了超时,我们只需要记录下错误,过一段时间后终
止。程序17.7是alarm.c文件。
_______________________________________________________________________
______
#include "lprps.h"
static void
sig_alrm(int signo) /* SIGALRM handler */
{
alrm_flag = 1;
return;
}
void
handle_alrm(void)
{
log_ret("printer not responding");
sleep(60); /* it will take at least this long to warm up */
exit(EXIT_REPRINT);
}
void /* Establish the signal handler and t the alarm. */
t_alrm(unsigned int nc)
{
alrm_flag = 0;
if (signal_intr(SIGALRM, sig_alrm) == SIG_ERR)
log_sys("t_alrm: signal_intr error");
alarm(nc);
}
void
clear_alrm(void)
{
alarm(0);
if (signal(SIGALRM, SIG_IGN) == SIG_ERR)
log_sys("clear_alrm: signal error");
alrm_flag = 0;
}
_______________________________________________________________________
______
程序17.7 处理超时的alarm.c文件
程序17.8是函数get_status,这个函数由main函数调用。它发送一个Control
-T到打印机以获取打印机的状态消息。打印机回送一行消息。如果我们接到的消息
是:
%%[ status : idle]%%
这意味着打印机准备好执行一个新的作业。这个消息被函数proc_some_input
读取和处理(下面我们会讨论这个函数)。
_______________________________________________________________________
______
#include "lprps.h"
/* Called by main() before printing job.
* We nd a Control-T to the printer to fetch its status.
* If we timeout before reading the printer's status, something
* is wrong. */
void
get_status(void)
{
char c;
t_alrm(5); /* 5 cond timeout to fetch status */
tty_flush();
c = '024';
block_write(&c, 1); /* nd Control-T to printer */
init_input(0);
while (status == INVALID)
proc_some_input(); /* wait for something back */
switch (status) {
ca IDLE: /* this is what we're looking for ... */
clear_alrm();
return;
ca WAITING: /* printer thinks it's in the middle of a job */
block_write(&eofc, 1); /* nd EOF to printer */
sleep(5);
exit(EXIT_REPRINT);
ca BUSY:
ca UNKNOWN:
sleep(15);
exit(E
XIT_REPRINT);
}
}
_______________________________________________________________________
______
程序17.8 get_status函数
如果我们收到下列消息:
%%[ status: waiting ]%%
这说明打印机正等待我们发送更多的数据以用于当前正打印的作业,这很可能
是前一打印作业出了些问题。为了清除这个状态,我们发送给打印机一个EOF终止
符。
PostScript打印机维护着一个页码计数器。这个计数器每打印一页就会增加,
它即使在关闭电源时也会保存着。为了读此计数器,我们需要发送给打印机一个P
ostScript程序。文件pagecount.c(程序17.9)包含了这个小PostScript程序(含
有大约10个PostScript操作符)和函数get_page。
_______________________________________________________________________
______
#include "lprps.h"
/* PostScript program to fetch the printer's pagecount.
* Notice that the string returned by the printer:
* %%[ pagecount: N ]%%
* will be pard by proc_msg(). */
static char pagecount_string[] =
"(%%[ pagecount: ) print " /* print writes to current output file *
"statusdict begin pagecount end " /* push pagecount onto stack */
"20 string " /* creates a string of length 20 */
"cvs " /* convert to string */
"print " /* write to current output file */
"( ]%%) print "
"flush
"; /* flush current output file */
/* Read the starting or ending pagecount from the printer.
* The argument is either &start_page or &end_page. */
void
get_page(int *ptrcount)
{
t_alrm(30); /* 30 cond timeout to read pagecount *
tty_flush();
block_write(pagecount_string, sizeof(pagecount_string) - 1);
/* nd query to printer
*/
init_input(0);
*ptrcount = -1;
while (*ptrcount < 0)
proc_some_input(); /* read results from printer */
block_write(&eofc, 1); /* nd EOF to printer */
proc_upto_eof(0); /* wait for EOF from printer */
clear_alrm();
}
_______________________________________________________________________
______
程序17.9 pagecount.c文件-得到打印机的计数器值
pagecount_string数组包含了这个小PostScript程序。虽然我们可以用如下方
法得到并打印页码:
statusdict begin pagecuont end = flush
但我们希望得到类似于打印机返回的状态消息的输出格式:
%% [ pagecount: N]%%
然后,proc_some_input函数处理这个消息,其方式与处理打印机的状态消息
相类似。
程序17.10中的函数nd_file由main函数调用,它将用户的PostScript程序发
送
到打印机上。
_______________________________________________________________________
______
#include "lprps.h"
void
nd_file(void) /* called by main() to copy stdin to printer */
{
int c;
init_input(1);
t_intr(); /* we catch SIGINT */
while ( (c = getchar()) != EOF) /* main loop of program */
out_char(c); /* output each character */
out_char(EOF); /* output final buffer */
block_write(&eofc, 1); /* nd EOF to printer */
proc_upto_eof(0); /* wait for printer to nd EOF back */
}
_______________________________________________________________________
______
程序17.10 nd_file函数
这个函数主要是一个while循环,其中先读取标准输入(getchar),然后调用
函数out_char把字符发送给打印机。当在标准输入上遇到EOF时,就发送一个EOF给
打印机(指示作业完成),然后我们就等待从打印机返回一个EOF(proc_upto_eof
)。
回忆图17.2中,连接在串口的PostScript解释器的输出可能是打印机状态消息
或者是PostScript的print操作符的输出。所以,我们所认为的"文件被打印"甚至
可能一页都不输出。这个PostScript程序文件执行后,把它的结果送回主机。Pos
tScript不是一种编程语言。但是,有时我们确实需要发送一个PostScript程序到
打印机并将结果返回主机,而不是打印在纸上。一个例子是取页码数的PostScrip
t程序,用其可以了解打印机的使用情况。
%!
statusdict begin pagecount end =
如果从PostScript解释器返回的不是状态消息,就以电子邮件形式发送给用户
。程序17.11的mail.c完成这一功能。
_______________________________________________________________________
______
#include "lprps.h"
static FILE *mailfp;
static char temp_file[L_tmpnam];
static void open_mailfp(void);
/* Called by proc_input_char() when it encounters characters
* that are not message characters. We have to nd the
* characters back to the ur. */
void
mail_char(int c)
{
static int done_intro = 0;
if (in_job && (done_intro || c != '
')) {
open_mailfp();
if (done_intro == 0) {
fputs("Your PostScript printer job "
"produced the following output:
", mailfp);
done_intro = 1;
}
putc(c, mailfp);
}
}
/* Called by proc_msg() when an "Error" or "OffendingCommand" key
* is returned by the PostScript interpreter. Send the key and
* val to the ur. */
void
mail_line(const char *msg, const char *val)
{
if (in_job) {
open_mailfp();
fprintf(mailfp, msg, val);
}
}
/* Create and open a temporary mail file, if not already open.
* Called by mail_char() and mail_line() above. */
static void
open_mailfp(void)
{
if (mailfp == NULL) {
if ( (mailfp = fopen(tmpnam(temp_file), "w")) == NULL)
log_sys("open_mailfp: fopen error");
}
}
/* Clo the temporary mail file and nd it to the ur.
* Registered to be called on exit() by atexit() in main(). */
void
clo_mailfp(void)
{
char command[1024];
if (mailfp != NULL) {
if (fclo(mailfp) == EOF)
log_sys("clo_mailfp: fclo error");
sprintf(command, MAILCMD, loginname, hostname, temp_file);
system(command);
unlink(temp_file);
}
}
_______________________________________________________________________
______
程序17.11 mail.c文件
每次在打印机返回一个字符,而且这个字符不是状态消息的一部分时,那么就
调用函数mail_char(下面我们会讨论函数proc_input_char,它调用mail_char)
。只有当函数nd_file正发送一个文件给打印机时,变量in_job才被设置。在其
它时候,例如我们正在读取打印机的状态消息或者打印机的页码计数器值时,它都
不会被设置。然后调用函数mail_line,它将一行写入邮件文件中。
当第一次调用函数open_mailfp时,它生成一个临时文件并把它打开。函数cl
o_mailfp由main函数设置为终止处理程序,当调用exit时就会调用该函数。如果
此时临时邮件文件已经产生,那么关闭这个文件,邮件传给用户。
如果我们发送一行的PostScript程序
%!
statusdict begin pagecount end =
来获得打印机的页码计数,那么返回给我们的邮件消息是
Your postscript printer job produced the following output:
11185
output.c(程序17.12)包含了函数out_char,nd_file调用此函数以便将字
符输出到打印机。
_______________________________________________________________________
______
#include "lprps.h"
static char outbuf[OBSIZE];
static int outcnt = OBSIZE; /* #bytes remaining */
static char *outptr = outbuf;
static void out_buf(void);
/* Output a single character.
* Called by main loop in nd_file(). */
void
out_char(int c)
{
if (c == EOF) {
out_buf(); /* flag that we're all done */
return;
}
if (outcnt <= 0)
out_buf(); /* buffer is full, write it first */
*outptr++ = c; /* just store in buffer */
outcnt--;
}
/* Output the buffer that out_char() has been storing into.
* We have our own output function, so that we never block on a write
* to the printer. Each time we output our buffer to the printer,
* we also e if the printer has something to nd us. If so,
* we call proc_input_char() to process each character. */
static void
out_buf(void)
{
char *wptr, *rptr, ibuf[IBSIZE];
int wcnt, nread, nwritten;
fd_t rfds, wfds;
FD_ZERO(&wfds);
FD_ZERO(&rfds);
t_nonblock(); /* don't want the write() to block */
wptr = outbuf; /* ptr to first char to output */
wcnt = outptr - wptr; /* #bytes to output */
while (wcnt > 0) {
FD_SET(psfd, &wfds);
FD_SET(psfd, &rfds);
if (intr_flag)
handle_intr();
while (lect(psfd + 1, &rfds, &wfds, NULL, NULL) < 0) {
if (errno == EINTR) {
if (intr_flag)
handle_intr(); /* no return */
} el
log_sys("out_buf: lect error");
}
if (FD_ISSET(psfd, &rfds)) { /* printer is readable */
if ( (nread = read(psfd, ibuf, IBSIZE)) < 0)
log_sys("out_buf: read error");
rptr = ibuf;
while (--nread >= 0)
proc_input_char(*rptr++);
}
if (FD_ISSET(psfd, &wfds)) { /* printer is writeable */
if ( (nwritten = write(psfd, wptr, wcnt)) < 0)
log_sys("out_buf: write error");
wcnt -= nwritten;
wptr += nwritten;
}
}
outptr = outbuf; /* ret buffer pointer and count */
outcnt = OBSIZE;
}
_______________________________________________________________________
______
程序17.12 output.c 文件
当传送给out_char的参数是EOF,那表明输入已经结束,最后的输出缓冲内容
应当发送到打印机。
函数out_char把每个字符放到输出缓冲中,当缓冲满了时调用out_buf函数。
我们必须小心编写out_buf函数:我们发送数据到打印机,打印机也可能同时送回
数据。为了避免写操作的阻塞,必须把描述符设置为非阻塞的。(可回忆程序12.
1的例子。)我们使用lect 函数来多路转接双向的I/O:输入和输出。我们在读
取和写入时都设置同一个描述符。还有一种可能是lect函数可能会被一个信号(
SIGINT)所中断,所以我们必须在任何错误返回时对此进行检查。
如果我们从打印机收到异步输入,我们调用proc_input_char来处理每一个字
符。这个输入可能是打印机状态消息或者发送给用户的邮件。
当我们写打印机时,我们必须处理write返回的计数比期望的数量少的情况。
同样,回忆程序12.1的例子,其中在每次写入时终端可以接收任意数量的数据。
文件input.c(见程序17.13),定义了处理所有从打印机输入的函数。这种输
入可以是打印机状态消息或者给用户的输出。
_______________________________________________________________________
______
#include "lprps.h"
static int eof_count;
static int ignore_input;
static enum par_state { /* state of parsing input from printer */
NORMAL,
HAD_ONE_PERCENT,
HAD_TWO_PERCENT,
IN_MESSAGE,
HAD_RIGHT_BRACKET,
HAD_RIGHT_BRACKET_AND_PERCENT
} par_state
;
/* Initialize our input machine. */
void
init_input(int job)
{
in_job = job; /* only true when nd_file() calls us */
par_state = NORMAL;
ignore_input = 0;
}
/* Read from the printer until we encounter an EOF.
* Whether or not the input is procesd depends on "ignore". */
void
proc_upto_eof(int ignore)
{
int ec;
ignore_input = ignore;
ec = eof_count; /* proc_input_char() increments eof_count */
while (ec == eof_count)
proc_some_input();
}
/* Wait for some data then read it.
* Call proc_input_char() for every character read. */
void
proc_some_input(void)
{
char ibuf[IBSIZE];
char *ptr;
int nread;
fd_t rfds;
FD_ZERO(&rfds);
FD_SET(psfd, &rfds);
t_nonblock();
if (intr_flag)
handle_intr();
if (alrm_flag)
handle_alrm();
while (lect(psfd + 1, &rfds, NULL, NULL, NULL) < 0) {
if (errno == EINTR) {
if (alrm_flag)
handle_alrm(); /* doesn't return */
el if (intr_flag)
handle_intr(); /* doesn't return */
} el
log_sys("proc_some_input: lect error");
}
if ( (nread = read(psfd, ibuf, IBSIZE)) < 0)
log_sys("proc_some_input: read error");
el if (nread == 0)
log_sys("proc_some_input: read returned 0");
ptr = ibuf;
while (--nread >= 0)
proc_input_char(*ptr++); /* process each character */
}
/* Called by proc_some_input() above after some input has been read.
* Also called by out_buf() whenever asynchronous input appears. */
void
proc_input_char(int c)
{
if (c == '004') {
eof_count++; /* just count the EOFs */
return;
} el if (ignore_input)
return; /* ignore everything except EOFs */
switch (par_state) { /* par the input */
ca NORMAL:
if (c == '%')
par_state = HAD_ONE_PERCENT;
el
mail_char(c);
break;
ca HAD_ONE_PERCENT:
if (c == '%')
par_state = HAD_TWO_PERCENT;
el {
mail_char('%'); mail_char(c);
par_state = NORMAL;
}
break;
ca HAD_TWO_PERCENT:
if (c == '[') {
msg_init(); /* message starting; init buffer */
par_state = IN_MESSAGE;
} el {
mail_char('%'); mail_char('%'); mail_char(c);
par_state = NORMAL;
}
break;
ca IN_MESSAGE:
if (c == ']')
par_state = HAD_RIGHT_BRACKET;
el
msg_char(c);
break;
ca HAD_RIGHT_BRACKET:
if (c == '%')
par_state = HAD_RIGHT_BRACKET_AND_PERCENT;
el {
msg_char(']'); msg_char(c);
par_state = IN_MESSAGE;
}
break;
ca HAD_RIGHT_BRACKET_AND_PERCENT:
if (c == '%') {
par_state = NORMAL;
proc_msg(); /* we have a message; process it */
} el {
msg_char(']'); msg_char('%'); msg_char(c);
par_state = IN_MESSAGE;
}
break;
default:
abort();
}
}
_______________________________________________________________________
______
程序17.13 input.c文件-读取和处理从打印机的
输入
每当我们等待从打印机返回EOF时就会调用函数proc_upto_eof。
函数proc_some_input从串口读取。注意我们调用lect 函数来确定什么时候
该描述符是可以读取的。这是因为lect 函数通常被一个捕捉到的信号所中断-它
并不自动地重启动。因为lect 函数能被SIGALRM或SIGINT所中断,我们并不希望
它重启动。回忆一下在12.5节中我们关于lect函数被正常中断的讨论。同样回忆
在10.5节中我们设置SA_RESTART来说明当一个特定信号出现时,应当自动重启动的
I/O函数。但是因为并不总是有一个附加的标志,使得我们可以说明I/O 函数不应
当重启动。如果不设置SA_RESTART,我们只能倚赖系统的缺省值,而这可能是自动
重新启动被中断的I/O函数。当从打印机返回输入时,我们以非阻塞模式读取,得
到打印机准备就绪的数据。然后调用函数proc_input_char来处理每一个字符。
处理打印机发送给我们的消息是由proc_input_char完成的。我们必须检查每
一个字符并记住我们的状态。变量par_state跟踪记录当前状态。调用msg_char
函数把序列%%[以后所有的字符串储存在消息缓冲中。当遇到结束序列]%%时,我们
调用proc_msg来处理消息。除了开始%%[ 和最后 ]%%序列以及二者之间的状态消息
其他字符串,都被认为是用户的输出,被邮递给用户(调用mail_char)。
我们现在查看那些处理由输入函数积累消息的函数。程序17.14是文件messag
e.c。
当检测到%%[ 后,调用函数msg_init。它只是初始化缓冲计数器。然后对于消
息中的每一个字符都调用msg_char函数。
函数proc_msg将消息分解为key:val对(关键字:值对),并检查key。调用A
NSI C的strtok函数将消息分解为记号,每个key: val对用分号分隔。
一个以下形式的消息
%%[ Flushing : rest of job (to end-of-fiel) will be ignored ]%%
引起函数printer_flushing 被调用。它清理终端的缓冲,发送一个EOF给打印
机,然后等待打印机返回一个EOF。
如果收到一个以下形式的消息
%%[ PrinterError: reason]%%
就调用log_msg函数来记录这个错误。带有Error Key的出错消息邮递传送回用
户。这些一般是PostScript程序的错误。
如果返回一个带有关键字status的状态消息,它很可能是由于函数get_statu
s发送给打印机一个状态请求(Control-T)而引起的。我们查看val,并按照它来
设置变量status。
_______________________________________________________________________
______
#include "lprps.h"
#include
static char msgbuf[MBSIZE];
static int msgcnt;
static void printer_flushing(void);
/* Called by proc_input_char() after it's en the "%%[" that
* starts a message. */
void
ms
g_init(void)
{
msgcnt = 0; /* count of chars in message buffer */
}
/* All characters received from the printer between the starting
* %%[ and the terminating ]%% are placed into the message buffer
* by proc_some_input(). This message will be examined by
* proc_msg() below. */
void
msg_char(int c)
{
if (c != '0' && msgcnt < MBSIZE - 1)
msgbuf[msgcnt++] = c;
}
/* This function is called by proc_input_char() only after the final
* percent in a "%%[ ]%%" has been en. It pars the
* , which consists of one or more "key: val" pairs.
* If there are multiple pairs, "val" can end in a micolon. */
void
proc_msg(void)
{
char *ptr, *key, *val;
int n;
msgbuf[msgcnt] = 0; /* null terminate message */
for (ptr = strtok(msgbuf, ";"); ptr != NULL;
ptr = strtok(NULL, ";")) {
while (isspace(*ptr))
ptr++; /* skip leading spaces in key */
key = ptr;
if ( (ptr = strchr(ptr, ':')) == NULL)
continue; /* missing colon, something wrong, ignore */
*ptr++ = '0'; /* null terminate key (overwrite colon) */
while (isspace(*ptr))
ptr++; /* skip leading spaces in val */
val = ptr;
/* remove trailing spaces in val */
ptr = strchr(val, '0');
while (ptr > val && isspace(ptr[-1]))
--ptr;
*ptr = '0';
if (strcmp(key, "Flushing") == 0) {
printer_flushing(); /* never returns */
} el if (strcmp(key, "PrinterError") == 0) {
log_msg("proc_msg: printer error: %s", val);
} el if (strcmp(key, "Error") == 0) {
mail_line("Your PostScript printer job "
"produced the error `%s'.
", val);
} el if (strcmp(key, "status") == 0) {
if (strcmp(val, "idle") == 0)
status = IDLE;
el if (strcmp(val, "busy") == 0)
status = BUSY;
el if (strcmp(val, "waiting") == 0)
status = WAITING;
el
status = UNKNOWN; /* "printing", "PrinterError",
"initializing", or "printing test page". */
} el if (strcmp(key, "OffendingCommand") == 0) {
mail_line("The offending command was `%s'.
", val);
} el if (strcmp(key, "pagecount") == 0) {
if (sscanf(val, "%d", &n) == 1 && n >= 0) {
if (start_page < 0)
start_page = n;
el
end_page = n;
}
}
}
}
/* Called only by proc_msg() when the "Flushing" message
* is received from the printer. We exit. */
static void
printer_flushing(void)
{
clear_intr(); /* don't catch SIGINT */
tty_flush(); /* empty tty input and output queues */
block_write(&eofc, 1); /* nd an EOF to the printer */
proc_upto_eof(1); /* this call won't be recursive,
since we specify to ignore input */
get_page(&end_page);
do_acct();
exit(EXIT_SUCCESS);
}
_______________________________________________________________________
______
程序17.14 message.c文件,处理从打印机返回消息
一个OffendingComman
d的关键字一般总是与其它key : val对一起出现,如
%% [ Error : stackunderflow ; OffendingCommand : pop ]%%
我们在送回给用户的邮件中就要添加一行。
最后,在函数get_page(程序17.9)中的PostScript程序产生一个页面计数值
。我们调用sscanf把val转换为二进制,设置起始或结束页面值变量。函数get_pa
ge中的while循环就在等待这个变量变成非负值。
17.5 摘要
在这一章中实现一个完整的程序-它发送一个PostScript程序给连接在RS-232
端口的PostScript打印机。这给我们一个实践机会,把前些章所介绍的很多函数用
到一个实用的程序中:例如I/O多路转接、非阻塞的I/O、终端I/O和信号等。
习题:
17.1 我们需要使用lprps来打印标准输入的文件,它也可能是一个管道。因为程序
psif一定要查看输入文件的前两个字节,那么应当如何开发psif程序(图17.5)来
处理这种情况呢?
17.2 实现psif过滤器,处理前一个练习中的实例。
17.3 参考12.5节中Adobe Systems[1998]关于在Postscript程序中字体请求的处理
,修改本章中的lprps程序以处理字体请求。
--
--
※ 来源:·BBS 水木清华站 ·[FROM: 202.38.248.38]