IPP互联网打印协议:631端口、任务状态与免驱打印解析 简介围绕 IPPInternet Printing Protocol网络打印协议整理的实现源码包面向希望理解跨平台远程打印通信机制、并尝试自定义打印服务或客户端的开发者。内容以 C 语言实现为主体包内共 32 个文件包含 8 个 .c 源文件、5 个 .h 头文件及 Makefile、README、format、entries 等构建与说明文件另有 15 个 svn-base 版本留存文件压缩包约 35KB规模精简但结构完整便于按模块阅读协议实现。源码围绕 HTTP/1.1 承载的 IPP 请求与响应展开可对照学习报文解析、协议状态机、数据编码、错误处理及 HTTPS 安全相关思路并覆盖提交打印作业、查询打印机状态、控制作业与查询属性等功能环节。已有 3876 人学习适合作为网络协议与网络编程的实例参考也可为打印服务集成和功能扩展提供基础读者可据此理解 IETF 标准化报文结构如何支撑不同厂商设备互通并积累协议解析与网络编程的调试经验。1. IPP 是什么打印机界那句人人都该会说的普通话新打印机就摆在桌上无线指示灯亮着绿色可你在电脑里翻遍“添加打印机”要么什么也搜不到要么只看得到一串完全看不懂的型号名字。多数时候你和这台机器之间缺少的并不是驱动而是一句双方都能听懂的普通话。IPP全称 Internet Printing Protocol中文叫“互联网打印协议”。通俗地说它是电脑、手机和打印机之间商量好的一套对话规则谁先开口报文用什么编码说“三份、双面、A4”时怎么表达打完了怎么回话。它运行在 HTTP 之上默认使用 631 端口。一台打印机在这套协议里更像一个能查询、能排队、能撤销任务的小服务而不是一根只会往外吐纸的管子。搞懂 IPP打印这件事里一大半让人摸不着头脑的地方就清楚了。2. 一份文件怎么变成纸上那几行字IPP 的任务模型2.1 打印不是“传文件”而是“下订单”很多人对网络打印的直觉是把文件“发”给打印机。这个直觉只在最原始的打印方式里成立——那边开一个端口这边把一堆字节倒进去倒完就结束打印机认不认、打得对不对发送方一概不知。IPP 换了个思路它把打印当成一次业务办理。你可以想象去窗口办事先递材料文件再填一张表说明要求几份、单面还是双面、什么纸张、要不要装订然后拿到一个受理编号之后随时可以凭编号查进度不满意还能撤销。文件是“菜”属性是“不要香菜、微辣、打包带走”两者分开管理这才是 IPP 的核心结构。任务因此有了身份。每一次提交都会生成一个 job-id 和一个 job-uri打印机自己记着这个任务现在处于什么状态。这一点看起来平平无奇实际是后面所有“免驱打印”“手机上取消任务”“查看还有几页”的能力来源。没有任务概念客户端就只能在发完之后凭猜测。2.2 一次 Print-Job 的完整往返IPP 的报文长得很有规律开头 8 个字节是固定头接着是若干个属性组属性组用单个字节的分隔标记隔开最后用0x03表示“属性说完了”后面紧跟文档本身的字节。整个请求通过 HTTP 的 POST 发出去Content-Type固定为application/ipp请求地址就是打印机的 printer-uri。import struct # IPP 请求体的骨架8 字节定长头 若干属性组 0x03(属性结束) 文档数据 head struct.pack( BBHI, 0x02, # 主版本号 0x00, # 次版本号 0x0002, # 操作码 0x0002 Print-Job提交一份打印任务 0x00000001, # 请求号客户端自增用来把响应和请求对上 ) def attr(tag: int, name: str, value: bytes) - bytes: 一个属性 值标签(1B) 名字长度(2B) 名字 值长度(2B) 值 n name.encode() return bytes([tag]) struct.pack(H, len(n)) n struct.pack(H, len(value)) value body head body b\x01 # 操作属性组的分隔标记 body attr(0x47, attributes-charset, butf-8) # 0x47 表示字符集 body attr(0x48, attributes-natural-language, bzh-cn) # 0x48 表示自然语言 body attr(0x45, printer-uri, bipp://print.local/ipp/print) # 0x45 表示 URI body b\x02 # 任务属性组的分隔标记 body attr(0x42, job-name, 季度报表.encode()) # 0x42 表示名字 body b\x03 # 属性结束后面直接跟文件字节这段代码只负责拼出“信封”真正的文件字节追加在0x03之后。几个参数值得记住版本号两个字节决定双方讲的是哪一套语法操作码决定这次是“提交任务”还是“查询任务”请求号是客户端自己维护的计数器打印机在响应里原样带回靠它把一来一回对上号。属性里的名字长度是 2 字节意味着属性名可以很长但值的长度同样只有 2 字节——所以文档数据不能塞进属性里必须放在最后单独跟。打印机的响应结构几乎对称状态码替代了操作码后面跟属性组通常会带回 job-uri、job-id、job-state 这几项。状态码是个 16 位整数0x0000表示一切正常0x04xx段是客户端的问题比如文件格式不支持、属性写错0x05xx段是打印机自己的问题。看到 0x04 开头就该检查自己发了什么看到 0x05 开头就别再折腾客户端了。2.3 任务状态机为什么它老是卡在“正在打印”IPP 给任务定义了七个状态pending排队中、pending-held被扣住、processing处理中、processing-stopped处理中断、canceled已取消、aborted异常终止、completed已完成。这些状态不是装饰打印机必须如实维护客户端才能问出真相。于是就有了一个特别常见的困扰进度条死活停在“正在打印”。原因十有八九不在协议而在轮询。绝大部分客户端不是长连接监听而是每隔一段时间发一次 Get-Job-Attributes 问一句“现在怎么样了”在两次询问之间界面上显示的还是上一次的答案。如果打印机正卡在缺纸、卡纸、或者等待用户按面板按钮状态会停在 processing-stopped而客户端的界面往往不会把这个区别翻译成人话。撤销也是同理。Cancel-Job 不是一声令下就生效的遥控器它更像一个“申请撤销”的请求任务已经进入 processing 或者已经打完了打印机完全可以拒绝。真正可靠的做法是先查状态再决定是撤销还是物理上按停止键。2.4 属性协议里最像“表单”的部分IPP 的属性分成三类分清楚了就不会乱操作属性描述这次请求本身比如字符集、自然语言、目标打印机地址、发起人是谁任务属性描述这份活怎么干比如 job-name、copies、sides、media、print-quality、orientation-requested打印机属性则是对设备能力的描述比如支持哪些纸张、能不能双面、支持哪些文档格式。其中attributes-charset和attributes-natural-language是必须带的且必须排在最前面。这条规则看起来吹毛求疵其实是必要的如果连“名字是用什么编码写的”都要先猜后面的解析就全乱了。这也解释了一类经典故障——某些自己拼接请求的工具把这两个属性写错或者干脆漏掉打印机回报的是一句语焉不详的客户端错误排查起来相当费劲。值得强调的是属性是有类型标签的。整数、布尔、字符串、关键字、URI、分辨率各有各的标签字节同名属性的值类型不对打印机可以直接拒收。这跟 HTTP 表单那种“全都当字符串”的松散做法完全不同也是很多人第一次手写 IPP 请求时容易出错的地方。3. 为什么需要 IPP把“每家一种方言”换成“一套通用说法”3.1 驱动这个中间层越堆越容易出问题要理解 IPP 存在的理由得先理解没有它的年代是什么样。计算机里的画面是由操作系统的图形子系统画出来的而打印机只认识自己那套页面描述语言。中间必须有人翻译这个翻译就是驱动。问题在于每家厂商有自己的方言甚至同一厂商的不同系列也各说各话。驱动要跟着操作系统版本走要分 32 位和 64 位要适配不同的处理器架构还要在系统大版本更新后重新签名。任何一个环节对不上结果就是装了驱动打不出来或者打出来是乱码。有过在几台机器上为同一台打印机反复安装驱动经历的人大概都记得那种反复失败的挫败感。更麻烦的是驱动是运行在你系统里的原生代码。它拿到的是相当高的权限却往往由设备厂商独立维护、更新缓慢。从系统角度看这是一个既难管又难替换的部件。3.2 IPP 的选择先谈能力再发标准格式IPP 的解法不是“做一个更好的驱动”而是把翻译这一步挪走。它的做法分两半先用 Get-Printer-Attributes 问清楚这台机器会什么——支持哪些文档格式、有哪些纸盒、能不能双面、能不能装订然后客户端把文件转成双方都认的标准格式发过去让打印机自己渲染。这就把“每台设备一套驱动”变成了“一套标准格式 一组能力协商”。PDF、JPEG、PWG Raster 这几类格式之所以在免驱打印里反复出现就是因为它们足够通用且渲染结果可预期。文档格式写在document-format属性里打印机如果不会就返回一个客户端错误类的状态码客户端换一种格式重试即可而不是让用户去下载一个几百兆的安装包。打个比方这就像国际会议不再给每位参会者配一个随身翻译而是约定大家都说同一种工作语言并且提前交换一份“我能听懂哪些口音”的清单。3.3 631 端口与 HTTP借一套现成的传输设施IPP 最聪明的设计决策之一是它没有另起炉灶造传输层而是直接跑在 HTTP 上。默认端口 631URL 用ipp://和ipps://两种 scheme请求方法是 POST响应就是普通的 HTTP 响应。好处很实际认证、加密、连接管理、企业网络里对 HTTP 的通行策略这些基础设施全都现成。设备厂商不需要自己实现一套可靠传输客户端也不需要额外开洞。加密版本就是在 HTTP 外面套一层 TLS端口通常还是 631因为这一层对上层协议是透明的。代价也有。HTTP 是个“一问一答”的协议天生不适合长连接推送所以状态更新只能靠轮询这就是前面说的“进度条不动”的根源之一。另外IPP 的请求体是紧凑的二进制编码人眼不可读用普通工具抓下来是一堆十六进制调试体验远不如 JSON 友好。3.4 代价清单IPP 不是万能钥匙把话说满是不负责的。IPP 解决的是“跨设备、跨系统、可协商”的打印问题它并不解决所有打印问题。第一它依赖设备端实现完整。协议规定了哪些属性必须支持但现实里总有实现残缺的设备表现出来的就是“能被发现、能连上、一打就错”。第二它对私有扩展不友好。像弹出钱箱、切纸、走特定定位这类厂商专属动作标准属性覆盖不到还是得走厂商自己的通道。第三它对高频小任务的吞吐不如裸字节流因为每个任务都有一整套属性协商和状态维护的开销。认清这三条后面判断“什么时候别用 IPP”就有依据了。4. 免驱打印的三种叫法同一套基础的不同包装4.1 IPP Everywhere一份“最低能力清单”IPP 是协议协议允许实现得很简陋。于是一个现实问题出现了都声称支持 IPPA 品牌能免驱打B 品牌连上就报错。为了把这个模糊地带收紧行业组织推出了一份认证规范名字叫 IPP Everywhere。它做的事情本质上是画一条及格线必须支持较新版本的 IPP必须支持若干种标准文档格式必须能被局域网自动发现必须提供一组规定的属性必须支持基本的任务管理操作。达标的设备可以打上认证标记客户端看到这个标记就敢直接发文件不用再试探。这个思路的价值在于它把“支持 IPP”从一句营销话术变成了可验证的清单。对用户来说最直观的体验就是包装上带这个标插上电、连上同一个网络系统自己就认出来了不用装任何东西。4.2 AirPrint 与 Mopria同一套基础的两套包装移动端的免驱打印绕不开这两个名字。它们经常被当成彼此竞争的技术实际上都是站在 IPP 这副肩膀上各自加了一层认证和约定。面向苹果设备的那套方案要求设备支持 IPP、能被局域网服务发现广播出来、并且能直接处理 PDF。移动端的另一套行业认证则面向更广泛的安卓阵营思路高度相似以 IPP 为底座规定一组必须支持的格式和能力做兼容性认证。所以真实世界里经常出现这样一幕一台只印着某个移动打印标志的打印机在另一套系统上也能免驱使用。这不是巧合而是因为底座相同差异主要在于认证范围、格式优先级和发现方式的细节。4.3 发现打印机局域网里那几个服务名打印机不会主动喊“我在这里”它是靠服务发现被找到的。这套机制用组播在局域网里广播服务类型客户端监听之后拿到主机名、端口和一段键值对形式的附加信息。与 IPP 相关的服务类型主要是_ipp._tcp和加密版的_ipps._tcp。传统 LPD 对应_printer._tcp裸字节流对应_pdl-datastream._tcp。附加信息里有几个键很关键rp是资源路径通常就是/ipp/printpdl列出支持的文档格式ty是给人看的型号描述note是位置备注。# 在局域网里看看谁在广播 IPP 服务需要 avahi 工具Linux 常见 avahi-browse -rt _ipp._tcp # -r 解析出主机名、地址和附加信息-t 表示本次查询完就退出这条命令的输出基本能回答“为什么系统找不到打印机”。如果这里空空如也问题多半在网络上设备和客户端不在同一个子网、组播被拦截、或者打印机压根没开这个服务。这时候再怎么折腾驱动都是白费力气方向错了。4.4 IPPS 与 IPP over USB同一套语言换条路走IPP 本身只规定了“说什么”没规定“怎么送”。除了最常见的 TCP 承载它还有几种变体。加密版就是把 HTTP 换成 HTTPS报文一字不改。用在跨网段、公共网络或者对内容敏感的场景因为打印文件里常常带着不该随便被看到的东西。还有一种是把 IPP 报文直接跑在 USB 上。移动设备通过数据线连打印机时用不了 TCP但可以让 IPP 报文走 USB 通道协议层完全复用。这样一套打印逻辑可以同时服务有线和无线两种连接厂商不用维护两套实现。理解这一点的意义在于以后看到某个场景说“支持 IPP”先别急着认为它一定能通过网络用。要看清楚它说的是协议本身还是某一种特定的传输方式。5. 容易搞混的几组概念IPP、LPD、9100 端口与 WSD 的区别5.1 易混点一IPP 不等于“网络打印”易混在哪里两个词经常被当作同义词尤其是在设备说明书里。为什么会混只要打印机插上网线大家就管它叫网络打印机至于用的是哪种协议说明书往往一笔带过。怎么区分网络打印描述的是“打印机接在网络上”这个事实IPP 描述的是“用什么语言对话”这个约定。一台网络打印机可能只会说裸字节流那它在 IPP 的世界里就是“不会说话”的。反过来通过 USB 连着的设备也可以跑 IPP。判断依据永远是协议和端口不是那根线。5.2 易混点二IPP 与 LPD/LPR易混在哪里两者都能把任务送到远端打印机都带“打印”二字配置界面里还常常并排出现。为什么会混LPD 出现得更早在很长一段时间里就是 Unix 世界的事实标准很多老系统至今默认用它。习惯延续下来就容易被当成同一类东西。怎么区分看有没有“任务”这个概念以及能不能问状态。LPD 的模型非常朴素指定一个队列名把数据流倒进去结束。它几乎没有查询任务状态的能力也没有标准的属性协商机制纸张、份数这些要求全靠事先在服务端配置好。IPP 则是完整的请求-响应模型属性和状态都是协议的一部分。端口上也很直观LPD 用 515IPP 用 631。5.3 易混点三IPP 与 RAW 9100易混在哪里Windows 里添加打印机时那个“标准 TCP/IP 端口”默认就是 9100很多人以为这就是网络打印的正统做法。为什么会混它太常见了几乎每台网络打印机都开着这个端口配置起来又快又简单于是被默认为“标准”。怎么区分9100 端口传的是原始字节流通常是某种页面描述语言的数据。发送方把字节倒进去就完事打印机打没打、打成什么样一概不知。如果发过去的格式打印机不认识结果就是吐出一堆乱码纸而且没有任何错误提示——这就是典型的只发生在使用 9100 端口时的乱码问题。IPP 则会明确告诉你格式不支持。选哪个取决于你要不要反馈要状态、要免驱、要跨设备选 IPP只是想把一堆已经渲染好的字节赶紧送过去9100 更直接。5.4 易混点四IPP 与 WSD易混在哪里两者都能让 Windows 自动发现网络打印机用户看到的结果几乎一样。为什么会混都是“点一下就能加上”的体验普通用户没有理由去区分底层。怎么区分WSD 是一套基于 Web 服务的设备发现与控制机制用 XML 消息通信发现走多播控制走 5357 端口。它更像是 Windows 生态内的一套设备接入方案覆盖面比打印更广。IPP 则是专门为打印设计的协议属性体系更细跨平台支持更普遍。实践中的经验是如果设备同时支持两者优先用 IPP属性和状态信息更完整只有在 IPP 不可用时才回退到 WSD。5.5 易混点五IPP 与驱动、与 PDF/PCL 的关系易混在哪里经常听到“支持 IPP 就不用装驱动”于是有人推断“IPP 是一种文件格式”或者“IPP 就是 PDF”。为什么会混免驱打印的体验里用户既没装驱动也没手动转格式很容易把两件事合并成一个东西。怎么区分IPP 是对话规则PDF、JPEG、PWG Raster 是被对话传递的内容驱动是原本负责翻译的那个部件。免驱的真正含义是翻译工作从“客户端装驱动”变成了“打印机自己渲染标准格式”IPP 负责把能力和内容协商清楚。至于 PCL 这类页面描述语言它也可以作为document-format的一种被传过去只不过它不像 PDF 那样通用通常还是得靠驱动生成。方式默认端口传什么能查任务状态常见场景IPP631属性 标准文档格式能覆盖任务全生命周期免驱打印、移动端打印IPPS631外层加密同上内容加密能跨网段、对内容敏感LPD/LPR515队列名 原始数据流基本不能老式类 Unix 系统RAW9100 端口9100原始页面描述数据不能快速配置、已渲染数据WSD5357发现走多播XML 消息有限同生态内的设备接入6. 什么时候该用 IPP、什么时候别碰几个边界判断判断标准其实不复杂就看三件事你要不要反馈、设备是不是杂、内容要不要保密。要反馈的场景IPP 几乎是唯一选择。想让用户在手机上看到“还有 3 页”“纸张不足”“任务已取消”就得有任务状态这个概念而 9100 端口和 LPD 都给不了。设备杂的场景也一样办公室里同时有笔记本、平板、几台不同年代的机器靠装驱动是维护不完的走标准格式传输反而省事。内容敏感的场景加密版本直接解决问题报文不用改一个字。反过来说有三种情况我会主动避开 IPP。一是老设备只认裸字节流硬上 IPP 只会得到一串看不懂的错误码不如老老实实用 9100。二是需要驱动打印机专属动作比如切纸、开钱箱、精确定位这些私有指令不在标准属性里绕不过厂商自己的接口。三是高频小票据量打印每个任务都带一整套属性协商和状态维护开销占比不低裸字节流在这种场景下反而更合适。有个具体例子挺能说明问题。某次给一个小型办公环境配打印两台设备一台是较新的机型一台是服役多年的旧机器。新机器走 IPP 一路顺畅手机、笔记本、平板全部免驱旧机器只认 9100于是单独给它配了标准 TCP/IP 端口并且提前在客户端把文件渲染成它认识的格式。整个方案里没有一处“全都用 IPP”的教条只有“这台设备能听懂什么就用什么跟它说”。翻车的版本当然也有过——一开始图省事把旧机器也按 IPP 配结果添加成功、打印乱码查了半天才发现它广播了服务却处理不了标准格式典型的“能连上不等于能用”。从那以后我每次接一台新打印机都先确认两件事631 端口通不通局域网里有没有_ipp._tcp广播确认完再决定要不要装驱动、要不要回退到裸字节流。这两步花不了两分钟却能省掉后面一晚上的排查。如果你也想把 IPP 的操作码、属性类型和状态码放在手边随时对照我把常用部分整理成了一份速查资料需要的时候取来查就行。希望帮到你。本文还有配套的精品资源点击获取