☰
IPP互联网打印协议详解:HTTP请求、631端口与无驱动打印排障
2026/11/6 5:26:53 网站建设 项目流程

简介:围绕IPP(Internet Printing Protocol)网络打印协议展开的C语言源码包,面向希望理解HTTP之上打印作业交互流程的开发者、网络协议学习者以及需要做打印方案集成的工程人员。代码覆盖IPP请求与响应报文解析、连接管理、打印作业提交与状态查询、打印机属性获取、错误处理等核心环节,并附带说明文档与编译脚本,便于在本地编译运行、抓包调试并对照协议规范阅读实现细节。读者可从中梳理协议状态机模型、数据编码方式与HTTPS加密认证的处理思路,为自建打印服务、客户端开发或系统集成提供可参照的工程范例。

压缩包共32个文件,以8个C源文件与5个头文件为主体,另有15个SVN版本元数据文件及格式说明、条目记录、说明文档和Makefile等辅助内容,整体约35KB,体积轻量而结构完整。目前已有3876人学习下载,适合作为网络协议从入门到进阶的动手素材。

1. 到底什么是 IPP:把一次打印变成一场网络对话

办公室里点一下打印,纸就从走廊尽头那台机器里吐出来。中间那几秒,电脑和打印机其实在说话。**IPP(Internet Printing Protocol,互联网打印协议)**就是这场对话的语法:谁先开口、第一句报什么、用什么格式说、对方怎么回、出了岔子怎么讲清楚。

打个比方,它像去餐厅点菜的那套规矩:先报桌号(我是谁、打印哪台机器),再说点什么菜(打哪个文件)、几成熟(单面还是双面)、要不要打包(打几份),服务员回一句“收到,预计二十分钟”,中间你还能问一句“做到哪一步了”,甚至反悔撤单。没有这套规矩,你得自己走进厨房跟厨师比划。

值得搞懂它,是因为几乎所有“点了打印没反应”“手机上搜不到打印机”“换了台电脑又装不上驱动”的麻烦,最后都落到这一层。搞清楚它在哪一层干活、又不负责哪一层,排障时就不会到处乱撞。

2. 为什么打印需要一个协议:从「驱动地狱」说起

2.1 驱动地狱:每台打印机都自带一套方言

打印这件事,听起来简单,实际上是个很别扭的活儿。打印机并不理解“页面”是什么,它只知道一串指令:纸走到哪个位置、喷头在哪一行喷几个点、什么时候停。这套指令集各家不一样,有的用 PCL,有的用 PostScript,有的用一套只有自家懂的私有指令。你电脑里的“驱动”,干的就是翻译这件事——把你的文档翻译成这台机器听得懂的方言。

问题在于方言太多了。N 种操作系统乘上 M 种打印机,就是 N 乘 M 份翻译工作。十年前装打印机是什么体验:随机器送一张光盘,装驱动,装完重启,重启完发现还是认不出来;换台电脑,重装一遍;系统升了级,驱动不兼容,再去找新版;同事换了工位,打印机列表里那台机器后面挂着一个奇怪的后缀,删也删不掉,重装折腾两个小时。某开发者跟我聊过一次,说他那台老打印机在某个系统上装驱动装到凌晨,第二天发现打印出来只有半页,原因是纸张尺寸单位被当成了英寸。

更麻烦的是 32 位和 64 位的分水岭,还有不同系统对驱动的签名要求。绕回那座机房里,问题往往不是“打印机坏了”,而是“翻译没对上”。这套 N 乘 M 的烂摊子,业内有个不太好听但很贴切的名字:驱动地狱。

2.2 IPP 的做法:把打印抽象成一次 HTTP 请求

IETF 在 1990 年代末给出的解法,思路非常“网络时代”:别再把打印当成“往串口发指令”,把它当成“对网络上的一个资源做一次操作”。这个思路的正式定义写在 RFC 2911 里,后来演化到 RFC 8010 和 RFC 8011,也就是今天常说的 IPP/1.1 和 IPP/2.x 体系。

它做了三个关键抽象。第一,打印机是一个 URI,长得像ipp://192.168.1.50:631/printers/office,跟一个网页地址是同一种东西。第二,一次打印任务是一个资源,也就是 job,它有编号、有状态、能被查询、能被取消,不是发出去就石沉大海。第三,打印参数是一组键值对属性,份数、双面、纸张大小、彩色还是黑白,都写成属性名加属性值。

于是整个打印过程变成:往那个 URI 发一个 HTTP POST,请求体里带着属性组和文档数据,响应回来告诉你作业编号是多少。之后你还能随时再发一个请求问它“现在什么情况”。这个设计的精髓是借用了 HTTP 的壳——端口、加密、认证、代理,全都是现成的,不用重新发明。

2.3 三层分工:发现、控制、传数据

很多人对 IPP 的第一印象是“把文件传到打印机”,其实它只负责其中一段。打印这件事从头到尾分三层,各管各的。

第一层是发现:这台机器在哪、叫什么名字、支不支持彩色、能不能双面。这一层通常靠 mDNS/Bonjour 广播(服务类型_ipp._tcp和_ipps._tcp),或者靠 SNMP 扫描网段。你在手机打印菜单里看到的那台打印机,就是被这一层捞出来的。

第二层是控制:我要打印什么、参数是什么、现在到哪一步、能不能取消。这一层才是 IPP 的地盘。

第三层是数据:文件内容本身。这一层由文档格式决定,可能是 PDF,可能是 JPEG,可能是一种叫 PWG Raster 的位图格式,也可能是老的 PCL。IPP 只在属性里声明一句“我这个文档是 PDF”,它不管 PDF 内部长什么样。

这个分层是理解后面所有易混概念的关键。IPP 是信封和信封上的寄件信息,不是信纸本身。

2.4 为什么偏偏建在 HTTP 上,代价是什么

选 HTTP 当传输层,好处摆在那里:端口统一好管,TLS 能直接套上去(这就成了 IPPS),认证机制现成,抓包工具和调试手段一抓一大把。对于一个要跨 Windows、macOS、Linux 和移动端工作的协议来说,这是最省事的一条路。

代价也有。一些企业网络对“看起来像 HTTP 但又不是标准网页”的流量格外敏感,631 端口在防火墙策略里经常被默认为“不必要”,直接关掉。设备侧的 HTTP 服务如果实现得粗糙,也容易成为隐患。这些问题不是协议本身的错,但确实会让人在排障时多绕几圈。

3. IPP 的内部长什么样:请求、属性、作业三件套

3.1 一次打印请求的完整旅程

把一次打印拆开看,标准流程大概六步,每一步对应一个操作码。

第一步,发现。客户端通过 mDNS 拿到打印机地址、端口和队列名,或者你手动填一个 URI。

第二步,问能力。客户端发Get-Printer-Attributes(操作码0x000B),问这台机器支持哪些纸张、支不支持双面、现在是什么状态。这一步很关键,很多“打印出来不对”的问题,在这一步就能看出端倪。

第三步,可选地校验参数。Validate-Job(0x0004)会先问一句“我这套参数你收不收”,不实际下作业。

第四步,提交作业。可以一步到位用Print-Job(0x0002),把属性和文档一起塞进去;也可以分两步,先Create-Job(0x0005)拿到作业号,再Send-Document(0x0006)把文档传过去。分步的好处是,一个作业里能塞多份文档。

第五步,查状态。Get-Job-Attributes(0x0009)轮询作业状态,或者用Get-Jobs(0x000A)把队列里的活儿全看一遍。

第六步,收尾。Cancel-Job(0x0008)撤单,或者等它自己走到完成。

这六个操作码是 IPP 的骨架,剩下的都是扩展。

3.2 二进制属性编码:为什么不用 JSON

IPP 的报文是二进制,不是文本。这一点让很多人第一次看抓包结果时一头雾水。原因很实在:这套东西设计于 1990 年代末,那时候 XML 还没普及,JSON 更没影,而二进制编码解析快、体积小,适合塞进打印机的廉价芯片里。

报文结构分成两部分:头部是版本号 2 字节、操作码 2 字节、请求号 4 字节,全是高位在前的大端序;后面是一组一组的属性,每组之间有个分隔标记,最后用0x03收尾表示“属性到这儿结束”,再往后就是文档数据。

import struct def ipp_header(operation_id, request_id=1, version=(2, 0)): # 版本号 2 字节:主版本 + 次版本,2.0 是目前最常见的组合 ver = struct.pack(">BB", *version) # 操作码 2 字节,0x000B 就是 Get-Printer-Attributes op = struct.pack(">H", operation_id) # 请求号 4 字节,客户端自己递增,用来把响应和请求对上号 rid = struct.pack(">I", request_id) return ver + op + rid def attr_keyword(tag, name, value): # 每个属性:值标签 1 字节 + 名字长度 2 字节 + 名字 + 值长度 2 字节 + 值 n = name.encode() v = value.encode() return (struct.pack(">BH", tag, len(n)) + n + struct.pack(">H", len(v)) + v) body = ipp_header(0x000B) # 0x47 表示 uri 类型,告诉打印机我们要问的是哪个队列 body += attr_keyword(0x47, "printer-uri", "ipp://192.168.1.50:631/printers/office") # 0x42 表示 nameWithoutLanguage,这里列出想查的字段 body += attr_keyword(0x42, "requested-attributes", "printer-state") body += attr_keyword(0x42, "requested-attributes", "printer-state-reasons") body += b"\x03" # end-of-attributes-tag,属性区到此为止

这段代码只做了一件事:拼出一个合法的“问能力”请求。0x47是 URI 类型的值标签,0x42是不带语言标记的名字类型,两者都在 IPP 的值标签表里有明确定义。常见的还有0x21整数、0x22布尔、0x23枚举、0x44关键字、0x45URI、0x48字符集。请求号request_id是客户端自己维护的计数器,发出去什么号,响应里就带回什么号,多线程并发时靠它区分是谁的回复。

拼好之后,把它当成一个普通的 POST 请求体发出去就行:

# 把上面拼好的二进制存成 req.bin,用原始二进制方式 POST 过去 curl -s --data-binary @req.bin \ -H "Content-Type: application/ipp" \ http://192.168.1.50:631/printers/office \ --output resp.bin # 响应也是二进制,用 xxd 看前几十个字节就能找到状态字段 xxd resp.bin | head -20

这里有两个参数不能错。Content-Type必须是application/ipp,写错了服务器会当成普通表单处理。--data-binary不能换成-d,后者会吃掉二进制里的特殊字节,请求直接变成畸形包。响应回来之后用十六进制看一眼,如果看到0x23后面跟着一个03、04或05,那就是打印机的状态:空闲、处理中、已停止。

3.3 属性:打印参数是怎么传过去的

IPP 的属性分几大类,理解分类比记住名字更有用。

描述类属性说明“这个作业是谁的、叫什么”:job-name、requesting-user-name、document-format。最后这个特别关键,它告诉打印机“我这份数据是 PDF 还是 JPEG”,打印机据此决定用哪条解析路径。

模板类属性说明“怎么打”:copies是份数,sides是单双面,media是纸张,print-color-mode是彩色还是黑白,orientation-requested是横竖方向,job-priority是优先级。这几个名字要记准,因为写错名字不会报错,会被静默忽略。

状态类属性说明“打到哪了”,属于只读字段:job-state、job-state-reasons、job-impressions-completed。

sides这个属性值得单独说一句。它的合法值不是duplex,而是one-sided、two-sided-long-edge、two-sided-short-edge三选一。长边翻转适合纵向文档,短边翻转适合横向文档,选错了装订时就会发现背面是倒的——这是个很典型的翻车点,而且因为属性名是对的、值不对,有些实现会忽略,有些会直接拒绝,行为还不统一。

3.4 作业状态机与打印机状态:排障时的两个抓手

job-state是一组固定的枚举值:3 是待处理,4 是挂起,5 是处理中,6 是处理中断,7 是已取消,8 是异常终止,9 是已完成。这个状态机是单向推进的,从 3 出发,正常情况下走到 9。

printer-state只有三个值:3 空闲、4 处理中、5 停止。光看这个太粗,真正有用的是配套的printer-state-reasons,它用一串关键字告诉你原因:media-empty缺纸、media-jam卡纸、toner-low墨粉将尽、cover-open盖板没关、paused被暂停。

排障时的顺序应该是:先看job-state判断这个作业还活着没有,再看printer-state判断设备本身能不能干活,最后看printer-state-reasons定位具体卡在哪。这三步走完,绝大多数“打印没反应”的问题都能定位到具体环节,而不是靠重启大法。

3.5 端口 631 与 URI 的写法

IPP 的默认端口是 631,这个数字没什么玄机,就是在 IANA 注册时分配的一个端口。URI 的标准写法是ipp://主机:端口/printers/队列名,加密版本把协议头换成ipps://,端口不变。

有些设备还会额外暴露一个无驱动打印端点,路径通常是/ipp/print,不带队列名。这个端点是给“不需要装驱动”的场景准备的,后面会细说。

同一个 631 端口上,通常还挂着一个网页管理界面,浏览器访问http://主机:631/能看到队列列表和作业历史。排障时先打开这个页面,比上来就抓包高效得多。

4. 常见误区与易混概念:IPP 不是驱动,也不是 AirPrint

4.1 误区一:IPP 是一种打印机驱动

易混点:在系统里添加打印机时,“IPP”这个词经常出现在连接方式的下拉框里,看起来跟“厂商驱动”是并列选项。

为什么混:因为这两个东西在操作界面上挨着,而且无驱动打印普及之后,两者的边界确实变模糊了——有些场景下真的不用装驱动,填个地址就能打。

怎么区分:驱动解决的是“把你的文档翻译成页面描述语言”,IPP 解决的是“把这份文档和参数送到打印机,并告诉你进度”。一个是翻译,一个是快递。哪怕完全不用驱动(直接发 PDF 过去),快递这一层照样是 IPP 在干活。反过来,某些老设备只认自家的私有指令,你必须装驱动,但送过去的那一步仍然可以走 IPP。

4.2 误区二:IPP、IPPS、LPD、9100 端口是一回事

易混点:这几个词全都出现在“添加打印机”的端口类型里,选错了就打不出来,但界面上的说明往往只有一行字。

为什么混:它们确实都在做同一件事——把打印数据送到设备上,只是年代和设计思路不同。

怎么区分:看有没有“控制通道”。

名称端口控制能力典型特征
IPP631完整:查状态、改参数、取消作业二进制属性 + HTTP
IPPS631同 IPP,外加 TLS 加密URI 以ipps://开头
LPD515很弱:只能提交和查队列长度纯文本控制行 + 数据流
RAW / 91009100几乎没有:发过去就打印裸 TCP 流,无协议头
WSD5357中等:基于 SOAP 和 WS-Discovery多见于 Windows 生态

最需要警惕的是 9100。它本质上是“把字节倒进管道”,没有作业概念,没有状态回传。误发了一份一百页的文件,你没有任何办法从电脑上取消,只能跑到机器跟前按停止键。这就是为什么很多人宁可在配置上多花五分钟,也要用 IPP。

4.3 误区三:IPP 规定了打印出来的页面长什么样

易混点:既然叫“打印协议”,那打印出来的排版、字体、分页,总该归它管吧。

为什么混:名字里带“打印”两个字,很容易让人以为它包办一切。

怎么区分:IPP 只管控制层,页面长什么样归页面描述语言管。你在属性里写document-format: application/pdf,意思是“我送你一份 PDF,你自己想办法渲染”,至于 PDF 里的字体、边距、图片压缩,IPP 一个字都不管。真正决定输出效果的是 PDF 渲染器、打印机的 RIP 芯片,以及纸张和色彩设置。所以“支持 IPP”不等于“什么文件都能打”,一台只认 PCL 的设备,你给它发 PDF,它照样打不出来。

4.4 误区四:支持 IPP 就等于支持无驱动打印

易混点:看到设备说明书上写着“支持 IPP”,就以为手机可以直接打,结果发现还是得装东西。

为什么混:“无驱动打印”这个卖点背后其实是一整套标准组合,IPP 只是其中一块。

怎么区分:无驱动打印要求三件事同时成立。第一,设备支持 IPP,并且实现了一组必需的属性。第二,设备支持一组标准文档格式,通常是 PDF、JPEG 和 PWG Raster。第三,设备通过 mDNS 广播自己,让客户端能自动发现。三条缺一,就得回到装驱动那条老路。AirPrint 和 Mopria 这类方案,本质上就是把这三条打包成了一个可认证的清单,厂商过了认证,用户才能“打开就打印”。

4.5 误区五:IPP 只能在内网用,出不了公网

易混点:平时见到的 IPP 地址都是192.168.x.x这种内网地址,看起来跟公网无缘。

为什么混:因为绝大多数部署确实都在内网,而且 631 端口在企业防火墙里经常是关着的。

怎么区分:协议本身没有内网限制,它跑在 HTTP 上,能路由、能做反向代理、能配 TLS 证书。真正拦住它的是运维策略:端口要不要开、证书怎么发、认证走哪套。所谓云打印,干的就是在公网上搭一个 IPP 中转,让内网设备主动连出去,从而绕开入站端口的问题。

5. IPP 在实际场景里怎么用:办公室、手机和排障

5.1 办公室里的一台打印服务器

最常见的企业部署长这样:一台服务器上跑打印服务,把每台物理打印机映射成一个或多个队列,每个队列对应一套默认参数,比如“财务部-双面黑白”“前台-彩色 A3”。客户端只需要连到服务器上的队列,不直接连物理设备。

这样做的好处很直接:驱动只在服务器上装一次,客户端换系统、换电脑都不用管;配额、审计、水印这些策略可以集中做;物理打印机换了 IP,只改服务器配置,客户端无感。客户端连队列用的就是ipp://打印服务器:631/printers/队列名,跟连一台物理机没有区别。

坑在哪里?坑在队列名。有些管理员图省事,把队列名起成带空格和中文的形式,客户端配置时就得做 URL 编码,某些老旧客户端处理不好,直接连不上。还有人把同一个物理设备映射了五个队列,用户随便选一个,结果默认参数不一样,打出来一半单面一半双面,查半天以为是设备故障。血泪经验是:队列名只用小写字母、数字和连字符。

5.2 手机和访客:无驱动那条路怎么走通

手机打印能成立,靠的是前面说的三件套同时到位:mDNS 发现、IPP 控制、标准格式。iPhone 上的 AirPrint、安卓上的 Mopria,走的都是这条路,底层协议就是 IPP 加一组约定好的属性。

部署时最容易翻车的地方不是协议,是网络。访客网络为了安全,通常做了客户端隔离,同一网段里的设备互相看不见,mDNS 广播根本传不过去。表现就是:你能 ping 通打印机,浏览器也能打开它的管理页面,但手机打印菜单里就是不出现这台机器。这时候别怀疑协议,先确认发现流量有没有被拦。

另一个常见问题是跨子网。mDNS 默认不跨网段,办公室在二楼、打印机在三楼不同 VLAN 的话,得在网关侧做服务发现代理,把_ipp._tcp的广播转发过去。这件事没做,用户看到的就是“以前能打,搬了工位就不能打了”。

5.3 排障三件套:端口、队列、状态

真出问题时,按顺序做三步验证,比盲目重装驱动有效得多。

# 第一步:631 上的 HTTP 服务到底活没活 # 能返回响应头,说明 IPP 的传输层是通的 curl -sI http://192.168.1.50:631/ | head -5 # 第二步:队列名对不对 # 设备管理页通常会把所有队列列出来,先确认名字拼写 curl -s http://192.168.1.50:631/printers/ | grep -i office # 第三步:直接问一次能力,把前面拼好的请求发过去 # 响应里能看到 printer-state 和 printer-state-reasons curl -s --data-binary @req.bin \ -H "Content-Type: application/ipp" \ http://192.168.1.50:631/printers/office \ --output resp.bin && xxd resp.bin | head -20

三步各自的失败含义不一样。第一步连接被拒绝,说明服务没起、端口被防火墙拦了,或者地址根本不对。第二步返回 404,说明服务活着但队列名写错了,这种情况在手工填地址时特别常见。第三步返回 200 但响应里printer-state是 5,说明协议层一切正常,问题出在设备本身——缺纸、卡纸、盖板没关,去看printer-state-reasons就行。

这三步的价值在于把问题切开:是网络不通、是队列配错、还是设备自己有问题。很多人一上来就重装驱动,其实驱动跟这三层都没关系。

5.4 一个属性写错的教训

有个案例值得说。某开发者给一批设备配双面打印,图省事在配置模板里写了sides: duplex,看着挺合理。结果所有打印机都默默按单面打,日志里一条错误都没有。他查了两天,怀疑过纸张设置、怀疑过驱动版本,最后才在属性值表里发现合法值只有one-sided、two-sided-long-edge、two-sided-short-edge三个,duplex根本不在其中。

这件事暴露了 IPP 的一个设计特点:对不认识的属性,默认行为是忽略,而不是报错。这个设计有它的道理——不同厂商会加自己的扩展属性,客户端不该因为不认识某个扩展就整个请求失败——但对配置的人不友好。规避办法是提交前先跑一次Validate-Job,让它明确告诉你哪些参数不合法,别等打出来才发现不对。

6. 什么时候该用 IPP,什么时候别碰:一份判断清单

说了这么多,落到实处就一个问题:我这个场景,到底该不该走 IPP。

先看一张对照表,覆盖大多数实际会遇到的情况。

场景建议原因
一个办公室里 Windows、macOS、Linux、手机混用用 IPP一套协议通吃,不用给每个系统备一份驱动
需要看进度、取消误发的作业用 IPP9100 这类裸流协议没有任何回传能力
要按部门做配额、审计、水印用 IPP 加打印服务器控制点集中在队列上,策略好落地
设备是十年前的型号,只认私有指令装驱动,走厂商推荐通道协议层降级不丢人,能用最重要
单机 USB 直连,就自己一个人用系统自带驱动足够引入网络层只会增加变量
网络策略严格,631 明确在封禁列表里先谈端口,别急着换协议换成别的协议一样要开端口,问题没解决

判断的核心其实是三个问题,问完基本就有答案了。

第一,你需不需要“知道发生了什么”。需要状态回传、需要取消、需要查历史,那就得用有控制通道的协议,IPP 是当下最完整的选择。反过来,如果只是往一台固定设备上灌数据、出错了大不了重打一遍,那 9100 反而更省事。

第二,设备支不支持标准格式。这决定了你能不能走无驱动那条路。设备只认 PCL,你就老老实实装驱动,别指望发个 PDF 过去它能自己搞定。

第三,网络能不能通。这一条最容易被忽略。IPP 跑在 631 上,跨子网要过防火墙,自动发现要过 mDNS,访客网络还有隔离。协议再优雅,端口不通也是白搭。所以正确的顺序是先确认网络可达,再谈协议选型,反过来做只会浪费时间。

什么时候别用 IPP?三种情况值得注意。一是极简场景,一台机器一根 USB 线,引入网络协议纯属自找麻烦。二是设备太老,只实现了 IPP 的一个残缺子集,状态字段全是空的,那还不如用回厂商自己的通道。三是网络策略完全锁死、631 开不了、又不允许做中转,这时候硬上只会把排障成本推高,不如接受现实。

我自己踩过一次坑。有台设备走 9100 老是出问题,我图省事把队列从ipp://改成了socket://...:9100,想着少一层协议总该快一点。速度没见快,倒是从此以后再也取消不了误发的作业——有一次手滑把一份两百页的标书发到了前台那台机器上,只能跑过去站在机器前面一张一张抽纸。从那以后我每次配队列,都强制先跑一遍Get-Printer-Attributes,确认状态字段能正常读出来,再往下走。多花的这三十秒,比在机器前面站着抽纸划算太多。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询