📄 17.htm
字号:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<meta name="GENERATOR" content="Microsoft FrontPage 3.0">
<title>C:\WINDOWS\Desktop\UnixProg\7.htm</title>
</head>
<body>
<font SIZE="2">
<h1 align="center">第十七章 与PostScript打印机通信 </h1>
<p>17.1 引言 </p>
<p>我们现在开发一个可以与Postscript打印机通信的程序。PostScript打印机目
</p>
<p>前使用很广,它一般通过RS-232端口与主机相连。这样就使得我们有可能使用第十
</p>
<p>一章中的终端I/O函数。同样,与PostScript打印机通信是全双工的,我们发送数
</p>
<p>据给打印机时也要准备好从打印机读取状态消息。这样,我们又有可能使用12.5节
</p>
<p>中的I/O多路转接函数:select 和poll。我们所开发的这个程序是基于James
Cla </p>
<p>rk 所写的lprps程序。这个程序和其他一些程序共同组成lprps软件包,可以在co
</p>
<p>mp.sources.misc新闻组中找到(Volume 21,1991年7月)。 </p>
<p>17.2 PostScript 通信机制 </p>
<p>关于PostScript打印机所需要知道的第一件事就是我们并不是发送一个文件给
</p>
<p>打印机去打印-我们只是发送一个PostScript程序给打印机让它去执行。在PostSc
</p>
<p>ript打印机中通常有一个PostScript解释器来执行这个程序,生成输出的页面。如
</p>
<p>果PostScript程序有错误,PostScript打印机(实际上是PostScript解释器)返回
</p>
<p>一个错误消息,或许还会产生其他输出。 </p>
<p>下面的PostScript程序在输出页面上生成一个熟悉的字符串hello, world。(
</p>
<p>我们这里并不叙述PostScript编程,详细情况请参见Adobe Systems[1985和1986]
</p>
<p>。我们着重在与PostScript打印机的通信上)。 </p>
<p>%! </p>
<p>/Times-Roman findfont </p>
<p>15 scalefont % point size of 15 </p>
<p>setfont % establish current font </p>
<p>300 350 moveto % x=300 y=350 (position on page) </p>
<p>(hello, world) show % output the string to current page </p>
<p>showpage % and output page to output device </p>
<p>如果我们在PostScript程序中改变setfont 为ssetfont,再把它发送到PostS </p>
<p>cript打印机,结果是什么也没有被打印。相反的,我们从打印机得到以下消息:
</p>
<p>%% [ Error: undefined; OffendingCommand: ssetfont ]%% </p>
<p>%% [ Flushing: rest of job (to end-of-file) will be ignored ]%% </p>
<p>这些错误消息随时都可能产生,这也是处理PostScript打印机复杂的地方。我
</p>
<p>们不能只是将整个PostScript程序发送给打印机后就不管了-我们必须处理这些潜
</p>
<p>在的错误消息。(在这一章中,我们所说的"打印机",就是指PostScript解释器。
</p>
<p>) </p>
<p>PostScript打印机通常通过RS-232串口与主机相连。这就如同终端的连接一样
</p>
<p>,所以我们在第十一章中的终端I/O函数在这里也适用。(PostScript打印机也可
</p>
<p>以通过其它方式连接到主机上,例如网络接口逐渐流行。但目前占主导地位的还是
</p>
<p>串口相连。)图17.2 显示了典型的工作过程。一个PostScript程序可以产生两种
</p>
<p>形式的输出:通过showpage操作输出到打印机页面上,或者通过print操作输出到
</p>
<p>它的标准输出(在这里是与主机的串口连接)。 </p>
<p>PostScript解释器发送和接受的是七位ASCII字符。PostScript程序可包含所
</p>
<p>有可打印的ASCII字符。一些不可以打印的字符有着特殊的含义(参见图17.1)。
</p>
<p>图17.1 从主机发送到PostScript打印机的特殊字符 </p>
<p>图17.2 用串口连接与Postscript打印机通信 </p>
<p>PostScript的文件终止符(Control-D)用来同步打印机和主机。我们发送一
</p>
<p>个PostScript程序到打印机,然后再发送一个EOF到打印机。当打印机执行完Post
</p>
<p>Script程序后,它就发回一个EOF。 </p>
<p>当PostScript解释器正在执行一个PostScript程序时,我们可以向它发送一个
</p>
<p>中断(Control-C)。这通常使正在被打印机执行的程序终止。 </p>
<p>状态查询消息(Control-T)会使得打印机返回一个一行的状态消息。所有的打印
</p>
<p>机消息都是如下格式: </p>
<p>%% [ key : val ] %% </p>
<p>所有可能出现在状态消息中的key: val对,被分号分开。回忆前面例子的返回
</p>
<p>消息: </p>
<p>%% [ Error: undefined; OffendingCommand: ssetfont ]%% </p>
<p>%% [ Flushing: rest of job (to end-of-file) will be ignored ]%% </p>
<p>这个状态消息具有这个格式: </p>
<p>%% [status : idle ]%% </p>
<p>除了idle(没有作业)外,其它状态指示还有busy(正在执行一个PostScrip
</p>
<p>t程序)、 waiting(正在等待执行PostScript程序)、 printing(打印中)、i
</p>
<p>nitializing(初始化)和printing test page(正打印测试页)。 </p>
<p>现在来考虑PostScript解释器自己产生的状态消息。我们看到以下的消息
</p>
<p>%% [ Error: error ; OffendingCommand : operator ]%% </p>
<p>总共大约会发生25种不同的错误。通常的错误有dictstackunderflow,
invalidac </p>
<p>cess, typecheck, 和undefined。这里的operator是产生错误的PostScript操作。 </p>
<p>一个打印机的错误用以下形式来指示。 </p>
<p>%% [ PrinterError: reason ]%% </p>
<p>这儿的reason一般是Out Of Paper(缺纸)、Cover Open(盖打开)等错误。
</p>
<p>当错误发生时,PostScript解释器经常会发出另外一个消息 </p>
<p>%% [ Flushing : rest of job (to end-of-file) will be ignored ] %% </p>
<p>我们看一下在特殊字符序列%%[和]%%中的字符串,为了处理这个消息,我们必
</p>
<p>须分析该字符串。一个PostScript程序也可以通过PostScript的print操作来产生
</p>
<p>输出。这个输出应当传给发送程序给打印机的用户(虽然这并不是我们的打印程序
</p>
<p>所期望解释的)。 </p>
<p>图17.3 列出了PostScript解释器传送给主机的特殊字符。 </p>
<p>图17.3 PostScript解释器传送给主机的特殊字符 </p>
<p>17.3 假脱机打印 </p>
<p>我们在本章所开发的程序通过两种方式发送PostScript程序给PostScript打印
</p>
<p>机,单独的方式或者通过BSD行式打印机假脱机系统。通常使用假脱机系统,但提
</p>
<p>供一个独立的方式也是很有用的,如用于测试等。 </p>
<p>Unix SVR4同样提供了一个假脱机打印系统,在AT&T手册[1991]
第一部分以l </p>
<p>p开头的手册页中可以找到假脱机系统的详细资料。Stevens[1990]的第13章详细说
</p>
<p>明了BSD和pre-SVR4的假脱机系统。我们在这一章中并不着重在假脱机系统上,而
</p>
<p>在于与PostScript打印机的通信。 </p>
<p>在BSD的假脱机系统中,我们以如下形式打印一个文件 </p>
<p>lpr -pps main.c </p>
<p>这个命令发送文件main.c到名为ps的打印机。如果我们没有指定-pps的选项,
</p>
<p>那么或者输出到PRINTER环境变量对应的打印机上,或者输出到缺省的lp打印机上
</p>
<p>。所用的打印机参数可以在/etc/printcap文件中查到。图17.4是对应一个PostSc
</p>
<p>ript打印机的一项。 </p>
<p>lp|ps:\ </p>
<p>:br#19200:lp=/dev/ttyb:\ </p>
<p>:sf:sh:rw:\ </p>
<p>:fc#0000374:fs#0000003:xc#0:xs#0040040:\ </p>
<p>:af=/var/adm/psacct:lf=/var/adm/pslog:sd=/var/spool/pslpd:\ </p>
<p>:if=/usr/local/lib/psif: </p>
<p>图17.4 一个PostScript打印机对应的printcap项 </p>
<p>第一行给出了这个项的名称,ps或者lp。br的值指定了波特率是19200。lp指
</p>
<p>定了该打印机的特殊设备文件路径名。sf是格式送纸,sh是指打印作业的开始加入
</p>
<p>一个打印页头,rw是指定打印机以读写方式打开。如17.2节中所述,这一项是Pos
</p>
<p>tScript打印机所必须的。 </p>
<p>下面的四个域指定了在旧版本BSD风格的stty结构中需要打开和关闭的位。(
</p>
<p>我们在这里对此进行叙述是因为大多数使用printcap文件的BSD系统都支持这种老
</p>
<p>式的设置终端方式的方法。在这一章的源程序文件中,我们可以看到如何使用第十
</p>
<p>一章中所述的POSIX.1函数来设置所有的终端参数。)首先,fc掩码清除sg_flags
</p>
<p>元素中的下列值:EVENP和ODDP(关闭了奇偶校验)、RAW(关闭raw模式)、CRMO
</p>
<p>D(关闭了在输入输出中的CR/LF映射)、ECHO(关闭回显)和LCASE(关闭输入输
</p>
<p>出中的大小写映射)。然后,fs掩码打开了以下位: CBREAK(一次输入一个字符
</p>
<p>)和TANDEM(主机产生Control-S,Control-Q流控制)。接着,xc掩码清除了本地
</p>
<p>模式字中各位。最后,xs掩码设置了本地模式字中的下列两位:LDECCTQ(Contro
</p>
<p>l-Q重新开始输出,Control-S则停止输出)和LLITOUT(压缩输出转换)。
</p>
<p>af和lf字符串分别指定了记帐文件和日志文件。sd指定了假脱机的目录,而i
</p>
<p>f指定了输入过滤器。 </p>
<p>输入过滤器可被所有的打印文件所激活,格式如下: </p>
<p>filter -n loginname -h hostname acctfile </p>
<p>这里还有几个可选的参数(这些参数有可能被PostScript打印机所忽略)。要
</p>
<p>打印的文件在标准输入中,打印机(printcap文件中的lp项)设在标准输出。标准
</p>
<p>输入也可以是一个管道。 </p>
<p>使用一个PostScript打印机,输入过滤器首先查询输入文件的开始两个字符,
</p>
<p>确定这个文件是一个ASCII文本文件还是一个PostScript程序。通常的惯例是前两
</p>
<p>个字符为%!表示这是一个PostScript程序。如果这个文件是PostScript程序,lpr
</p>
<p>ps程序(在下面会详细讨论)就把它发送到打印机。如果这个文件是文本文件,就
</p>
<p>使用其他程序将它转换成PostScript程序。 </p>
<p>在printcap文件中提到的psif过滤器是lprps软件包提供的。在这个包中的te
</p>
<p>xtps可以将文本文件转换成PostScript程序。图17.5概略表示了这些程序。
</p>
<p>图17.5 lprps系统示意图 </p>
<p>图中有一个程序psrev没有表示出来,该程序将PostScript程序生成的页面反
</p>
<p>转过来,当PostScript程序打印机在正面而不是在反面打印输出时,就可以使用此
</p>
<p>程序。 </p>
<p>概述以后,我们就来看一下lprps程序的设计和编码。 </p>
<p>17.4 源程序 </p>
<p>我们先看main()调用的函数,以及它们是如何与打印机交互作用的。图17.
</p>
<p>6详细表明了这种相互作用。图中第二列标注为"int?",它指定该函数是否可以通
</p>
<p>过接受SIGINT信号而中断。第三栏指定了各个函数的超时设置(以秒为单位)。注
</p>
<p>意,当我们发送用户的PostScript程序到打印机时,没有超时设置。这是因为一个
</p>
<p>PostScript程序可能用任意长的时间来执行。函数get_page行中的"我们的PostSc
</p>
<p>ript程序"是指程序17.9,这个程序是用来记录当前页码的。 </p>
<p>程序17.1列出了头文件lprps.h。该文件包含在所有的源文件中。该头文件包
</p>
<p>含了各个源程序所需的系统头文件,定义了全局变量和全局函数的原型。
</p>
<p>图17.6 被主程序调用的函数 </p>
<p>_______________________________________________________________________ </p>
<p>________ </p>
<p>#include <sys/types.h> </p>
<p>#include <sys/time.h> </p>
<p>#include <errno.h> </p>
<p>#include <signal.h> </p>
<p>#include <syslog.h> /* since we're a daemon */ </p>
<p>#include "ourhdr.h" </p>
<p>#define EXIT_SUCCESS 0 /* defined by BSD spooling system */ </p>
<p>#define EXIT_REPRINT 1 </p>
<p>#define EXIT_THROW_AWAY 2 </p>
<p>#define DEF_DEVICE "/dev/ttyb" /* defaults for debug mode */ </p>
<p>#define DEF_BAUD B19200 </p>
<p>/* modify following as a </p>
<p>propriate */ </p>
<p>#define MAILCMD "mail -s \"printer job\" %s@%s < %s" </p>
<p>#define OBSIZE 1024 /* output buffer */ </p>
<p>#define IBSIZE 1024 /* input buffer */ </p>
<p>#define MBSIZE 1024 /* message buffer */ </p>
<p>/* declare global variables */ </p>
<p>extern char *loginname; </p>
<p>extern char *hostname; </p>
<p>extern char *acct_file; </p>
<p>extern char eofc; /* PS end-of-file (004) */ </p>
<p>extern int debug; /* true if interactive (not a daemon) */ </p>
<p>extern int in_job; /* true if sending user's PS job to printer */ </p>
<p>extern int psfd; /* file descriptor for PostScript printer */ </p>
<p>extern int start_page;/* starting page# */ </p>
<p>#include <syslog.h> /* since we're a daemon */ </p>
<p>#include "ourhdr.h" </p>
<p>#define EXIT_SUCCESS 0 /* defined by BSD spooling system */ </p>
<p>#define EXIT_REPRINT 1 </p>
<p>#define EXIT_THROW_AWAY 2 </p>
<p>#define DEF_DEVICE "/dev/ttyb" /* defaults for debug mode */ </p>
<p>#define DEF_BAUD B19200 </p>
<p>/* modify following as a </p>
<p>propriate */ </p>
<p>#define MAILCMD "mail -s \"printer job\" %s@%s < %s" </p>
<p>#define OBSIZE 1024 /* output buffer */ </p>
<p>#define IBSIZE 1024 /* input buffer */ </p>
<p>#define MBSIZE 1024 /* message buffer */ </p>
<p>/* declare global variables */ </p>
<p>extern char *loginname; </p>
<p>extern char *hostname; </p>
<p>extern char *acct_file; </p>
<p>extern char eofc; /* PS end-of-file (004) */ </p>
<p>extern int debug; /* true if interactive (not a daemon) */ </p>
<p>extern int in_job; /* true if sending user's PS job to printer */ </p>
<p>extern int psfd; /* file descriptor for PostScript printer */ </p>
<p>extern int start_page;/* starting page# */ </p>
<p>void msg_init(void); /* message.c */ </p>
<p>void msg_char(int); </p>
<p>void proc_msg(void); </p>
<p>void out_char(int); /* output.c */ </p>
<p>void get_page(int *); /* pagecount.c */ </p>
<p>void send_file(void); /* sendfile.c */ </p>
<p>void block_write(const char *, int); /* tty.c */ </p>
<p>void tty_flush(void); </p>
<p>void set_block(void); </p>
<p>void set_nonblock(void); </p>
<p>void tty_open(void); </p>
<p>_______________________________________________________________________ </p>
<p>________ </p>
<p>程序17.1 lprps.h头文件 </p>
<p>文件vars.c(程序17.2)定义了全局变量。 </p>
<p>程序17.3是main函数。因为此程序一般是作为精灵进程运行的,所以main函数调用
</p>
<p>log_open函数(见附录B)。我们不能将错误消息写到标准错误上-为此我们使用了
</p>
<p>13.4.2小节中描述的syslog。 </p>
<p>_______________________________________________________________________ </p>
<p>________ </p>
<p>#include "lprps.h" </p>
<p>char *loginname; </p>
<p>char *hostname; </p>
<p>char *acct_file; </p>
<p>char eofc = '\004'; /* Control-D = PostScript EOF */ </p>
<p>int psfd = STDOUT_FILENO; </p>
<p>int start_page = -1; </p>
<p>int end_page = -1; </p>
<p>int debug; </p>
<p>int in_job; </p>
<p>volatile sig_atomic_t intr_flag; </p>
<p>volatile sig_atomic_t alrm_flag; </p>
<p>enum status status = INVALID; </p>
<p>_______________________________________________________________________ </p>
<p>________ </p>
<p>程序17.2 声明全局变量 </p>
<p>_______________________________________________________________________ </p>
<p>________ </p>
<p>#include "lprps.h" </p>
<p>static void usage(void); </p>
<p>int </p>
<p>main(int argc, char *argv[]) </p>
<p>{ </p>
<p>int c; </p>
<p>log_open("lprps", LOG_PID, LOG_LPR); </p>
<p>opterr = 0; /* don't want getopt() writing to stderr */ </p>
<p>while ( (c = getopt(argc, argv, "cdh:i:l:n:x:y:w:")) != EOF) { </p>
<p>switch (c) { </p>
<p>case 'c': /* control chars to be passed */ </p>
<p>case 'x': /* horizontal page size */ </p>
<p>case 'y': /* vertical page size */ </p>
<p>case 'w': /* width */ </p>
<p>case 'l': /* length */ </p>
<p>case 'i': /* indent */ </p>
<p>break; /* not interested in these */ </p>
<p>case 'd': /* debug (interactive) */ </p>
<p>debug = 1; </p>
<p>break; </p>
<p>case 'n': /* login name of user */ </p>
<p>loginname = optarg; </p>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -