
简介IPP网络打印协议源码包面向网络编程学习者、打印服务开发者与系统集成人员帮助理解跨平台远程打印的标准化实现。资源围绕IPP基于HTTP/1.1的请求响应模型涵盖打印作业提交、打印机状态查询、作业控制、属性查询及协议扩展等核心功能适合作为学习网络协议与自定义打印方案的实践参考。压缩包共32个文件约35KB以8个c源文件与5个h头文件为主体承担协议解析、状态机、数据编码与错误处理等实现另含Makefile、README及svn版本管理文件便于编译与阅读。已有3852人学习下载说明其在协议入门与源码研读方面具备一定参考价值。通过梳理源码结构读者可掌握IPP报文解析、连接管理与打印流程控制思路并在此基础上开发打印服务或客户端应用是理解网络协议与打印集成的实用素材。1. IPP网络打印协议为什么你的打印队列总在“玄学”掉线IPP网络打印协议全称 Internet Printing Protocol是跑在 HTTP 之上的打印通信标准。它把“打印”这件事从 USB 线缆里解放出来变成一次标准的网络请求客户端把文档、打印参数、任务属性打包发给打印机或打印服务器对方返回任务状态。你日常在手机、笔记本上点“添加打印机”时看到的那些网络设备背后大概率就是 IPP 在干活。它解决的核心问题是跨平台、跨网段的打印任务下发与状态回传。适合谁适合手里有网络打印机、打印服务器、CUPS 服务或者正在做打印中间件、云打印、自助打印终端的工程师。很多人第一次接触 IPP 是因为打印队列莫名其妙卡住、任务状态永远停在“正在打印”而日志里只有一行 HTTP 500。这篇笔记就按“协议怎么立住 → 环境怎么搭 → 请求怎么发 → 坑怎么排”的顺序把 IPP 从概念到可复现操作讲透。2. IPP 协议栈拆解从 HTTP 报文到打印属性组2.1 为什么 IPP 不是“另一个 socket 协议”IPP 最容易被误解的一点是把它当成类似 RAW 9100 的裸 TCP 打印。RAW 打印只负责把字节流推到打印机端口没有任务语义、没有状态回传、没有属性协商。IPP 则完全不同它复用 HTTP/1.1 作为传输层请求体是二进制编码的 IPP 消息响应体同样是二进制结构。这意味着你可以用 curl、Python requests、甚至浏览器开发者工具去观察一次打印请求的往返。常见做法是客户端向打印机 IPP 端点通常是http://打印机IP:631/ipp/print或ipps://发 POST 请求Content-Type 为application/ipp。请求体里包含操作码如 Print-Job、Get-Printer-Attributes、请求 ID、属性组操作属性组、作业属性组、文档属性组最后跟文档数据。打印机解析后返回状态码和属性组。整个交互是同步的一次请求对应一次响应。选型理由很直接如果你需要知道“任务到底有没有打成功”“纸张够不够”“彩色还是黑白”RAW 协议给不了你这些IPP 可以。如果你的场景只是往老式针式打印机推一段 ESC/POS 指令那 IPP 反而过重。判断标准是要不要任务状态和属性协商。要就上 IPP不要RAW 更省事。2.2 IPP 请求的二进制结构长什么样IPP 消息不是纯文本但结构非常规整。一个 Print-Job 请求大致分五段字段长度说明版本号2 字节主流为 0x02 0x00即 IPP/2.0操作码2 字节0x0002 表示 Print-Job请求 ID4 字节客户端自增用于匹配响应属性组变长每组以 0x01~0x05 分隔组内是属性名和值文档数据变长真正的 PDF/PS/文本内容属性组里最常打交道的几个attributes-charset固定为utf-8attributes-natural-language通常en或zh-cnprinter-uri是打印机地址job-name是任务名document-format告诉打印机文档类型application/pdf、image/jpeg、text/plain等。这些属性不是可选的缺一个就可能被打印机直接拒绝。我一般会先用ipptool或 Python 的pyipp库发一个 Get-Printer-Attributes确认打印机支持哪些文档格式和操作。这一步能省掉后面大量“为什么 PDF 发过去没反应”的排查时间。2.3 用 Python 发一次最小 IPP Print-Job下面这段代码不依赖打印机厂商 SDK只用标准库和requests构造一个最小 IPP 请求。实际生产中更推荐用pyipp但手写一次能帮你理解二进制布局。import struct import requests def build_ipp_print_job(printer_uri, job_name, document_data, document_formatapplication/pdf): # 版本 2.0操作码 Print-Job 0x0002请求 ID 1 header struct.pack(BBHI, 0x02, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01) # 操作属性组分隔符 0x01 op_group b\x01 # 属性编码tag 名称长度 名称 值长度 值 def attr(tag, name, value): name_b name.encode() value_b value.encode() if isinstance(value, str) else value return struct.pack(BH, tag, len(name_b)) name_b struct.pack(H, len(value_b)) value_b op_group attr(0x47, attributes-charset, utf-8) op_group attr(0x48, attributes-natural-language, en) op_group attr(0x45, printer-uri, printer_uri) op_group attr(0x42, job-name, job_name) op_group attr(0x49, document-format, document_format) # 文档属性组分隔符 0x02这里留空 doc_group b\x02 # 结束属性组 0x03 end_group b\x03 body header op_group doc_group end_group document_data resp requests.post( printer_uri, databody, headers{Content-Type: application/ipp}, timeout30 ) return resp.status_code, resp.content # 调用示例 with open(test.pdf, rb) as f: pdf_data f.read() code, raw build_ipp_print_job(http://192.168.1.50:631/ipp/print, test-job, pdf_data) print(HTTP 状态:, code) print(响应前 32 字节:, raw[:32].hex())逻辑说明struct.pack按大端序拼出 IPP 头部属性 tag 里 0x47 是 charset0x48 是 natural-language0x45 是 uri0x42 是 name0x49 是 mimeMediaType。每个属性都是“tag 名称长度 名称 值长度 值”的 TLV 结构。文档数据直接追加在结束符之后。参数说明printer_uri必须和打印机实际 IPP 端点完全一致末尾不要多斜杠document_format要和文件真实类型匹配PDF 就写application/pdfJPEG 写image/jpegtimeout建议不低于 30 秒因为大文件传输和打印机解析都可能慢。如果返回 HTTP 200 但响应体里状态码不是 0x0000说明 IPP 层拒绝了需要解析响应属性组里的status-message。3. 把 IPP 打印服务跑起来CUPS 配置与跨网段访问3.1 CUPS 为什么是 IPP 落地最稳的中间层直接让客户端连打印机 IPP 端点在同一个二层网络里通常没问题。但一旦跨网段、跨 VLAN或者打印机本身不支持 IPP over TLS就需要一个中间层。CUPS 是目前 Linux/Unix 生态里最成熟的 IPP 服务实现它既能把本地 USB 打印机共享成 IPP 网络打印机也能作为客户端去连上游 IPP 打印机还能做队列管理、权限控制、日志审计。常见做法是在一台常开的 Linux 机器上装 CUPS把打印机接上去或通过网络发现然后在 CUPS 里创建队列开启cupsctl --share-printers和cupsctl --remote-any客户端就能通过ipp://CUPS服务器:631/printers/队列名来打印。这样做的额外好处是所有打印任务都经过 CUPS日志统一出问题有error_log和access_log可查不用去猜打印机黑匣子里发生了什么。选型理由如果你只有一台打印机、一个网段、不需要审计直连 IPP 更简单。但只要有跨网段、多客户端、权限区分、任务重试这些需求CUPS 中间层几乎是最省心的方案。它的代价是多了一跳网络转发大文件打印时延迟会略微增加但换来的是可观测性和可控性。3.2 在 Ubuntu 上装 CUPS 并共享 IPP 队列以下步骤在 Ubuntu 22.04 上验证过其他发行版包名略有差异。# 安装 CUPS 和 IPP 工具 sudo apt update sudo apt install -y cups cups-client cups-ipp-utils # 启动并设为开机自启 sudo systemctl enable --now cups # 允许共享打印机和远程访问 sudo cupsctl --share-printers --remote-any # 查看当前队列 lpstat -v # 添加一台网络 IPP 打印机队列名 office lpadmin -p office -E -v ipp://192.168.1.50:631/ipp/print -m everywhere # 设置默认打印机 lpadmin -d office # 打印测试页 lp -d office /usr/share/cups/data/testprint逻辑说明cupsctl --share-printers让 CUPS 把本地队列通过 IPP 广播出去--remote-any允许非本机客户端访问。lpadmin -m everywhere表示使用 IPP Everywhere 驱动让 CUPS 自动向打印机查询能力不需要手动选 PPD 文件。lpstat -v用来确认队列绑定的 URI 是否正确。参数说明-p office是队列名客户端访问路径就是/printers/office-v ipp://...是上游打印机地址如果打印机支持加密就写ipps://-E表示启用队列并接受任务。如果lpadmin报 “Unable to connect to server”检查 CUPS 服务是否在跑以及当前用户是否在lpadmin组里。3.3 客户端如何验证 IPP 队列真的可用服务端配好后不要急着写业务代码先用命令行验证一遍完整链路。# 从另一台机器查询远程 CUPS 队列属性 ipptool -tv ipp://192.168.1.60:631/printers/office get-printer-attributes.test # 直接向远程队列提交一个文本文件 lp -h 192.168.1.60:631 -d office /etc/hostname # 查看远程队列中的任务状态 lpstat -h 192.168.1.60:631 -o # 取消指定任务 cancel -h 192.168.1.60:631 office-1逻辑说明ipptool是 CUPS 自带的 IPP 测试工具get-printer-attributes.test是内置测试脚本会列出打印机支持的所有属性。lp -h指定远程 CUPS 主机-d指定队列。lpstat -o看排队中的任务cancel按任务 ID 取消。参数说明-tv表示详细输出并显示传输过程排查网络问题时很有用。-h后面跟主机:端口不要带ipp://前缀。如果ipptool返回 “Unable to connect”先用telnet 192.168.1.60 631确认端口通不通再检查 CUPS 的Listen配置是否绑定了0.0.0.0:631而不是仅localhost。提示CUPS 默认只监听 localhost:631cupsctl --remote-any会修改监听配置。如果系统有防火墙还需要放行 631 端口否则客户端能解析到队列但连不上。4. IPP 打印避坑从任务卡死到中文乱码的 5 个血泪记录4.1 任务状态永远停在“正在打印”日志只有 HTTP 500现象客户端提交 PDF 后lpstat -o显示任务一直在队列里打印机面板无反应CUPSerror_log里反复出现HTTP 500或Unable to write。原因最常见的是document-format和实际文件类型不匹配。比如把 JPEG 文件命名为.pdf发过去打印机按 PDF 解析失败直接返回内部错误。另一个原因是打印机不支持该格式但客户端没有先查询能力就硬发。解决先用ipptool get-printer-attributes.test查document-format-supported列表确认目标格式在列。然后在提交前用file命令核对文件真实类型。如果格式确实不支持在 CUPS 队列里加一个cupsFilter或让 CUPS 自动转换而不是硬发原始文件。4.2 中文任务名变成问号或乱码现象打印任务在队列里显示为????或纸å¼但文档内容本身正常。原因IPP 属性编码时attributes-charset写了utf-8但job-name的值没有按 UTF-8 编码或者客户端库默认用了 Latin-1。部分打印机对非 ASCII 任务名处理也不一致。解决构造请求时确保job-name用 UTF-8 编码并且attributes-charset和实际编码一致。如果打印机本身不支持 UTF-8 任务名就把任务名限制为 ASCII中文信息放到文档内容里。CUPS 侧可以在printers.conf里检查Option job-name相关配置。4.3 跨网段后 IPP 队列能发现但打印失败现象客户端能通过 mDNS 或手动添加看到远程 CUPS 队列但一提交任务就报Connection refused或超时。原因CUPS 的Listen配置只绑定了localhost:631或者防火墙只放行了发现协议UDP 5353没放行 TCP 631。另一个常见原因是客户端拿到的printer-uri是 CUPS 服务器内网地址跨网段后不可达。解决在 CUPS 服务器上执行sudo cupsctl --remote-any然后检查/etc/cups/cupsd.conf里是否有Listen 0.0.0.0:631和Location /下的Allow all。防火墙放行 TCP 631。如果 CUPS 服务器有多个网卡确保客户端访问的地址是可达的那个必要时在cupsd.conf里显式指定ServerAlias。4.4 大文件打印到一半中断任务被标记为 aborted现象几十 MB 的 PDF 提交后打印机开始走纸但中途停下CUPS 把任务标记为abortederror_log里有Timeout或Broken pipe。原因IPP 请求默认没有分块传输大文件一次性 POST 过去打印机或中间网络设备超时。CUPS 的Timeout默认 300 秒但某些打印机内部缓冲小处理慢。解决在 CUPS 队列配置里调大Timeout或者让 CUPS 先落盘再后台发送。更稳的做法是客户端不要直接发原始大文件而是先提交到 CUPS由 CUPS 做队列和重试。如果必须直连打印机考虑把大文件拆分成多个小任务或者改用支持流式传输的 IPP 实现。4.5 同一队列在 Windows 和 Linux 上行为不一致现象同一个 CUPS IPP 队列Linux 客户端打印正常Windows 添加后打印出空白页或报驱动错误。原因Windows 的 IPP 客户端实现和 CUPS 有差异尤其是对document-format的协商和 PPD 驱动的依赖。Windows 可能默认发 XPS 或 RAW 数据而 CUPS 队列期望 PDF。解决在 Windows 上添加打印机时选择“通过 IPP 添加”并手动指定驱动为通用 PostScript 或 PDF。如果还是不行在 CUPS 队列上启用raw队列专门接收 Windows 发来的 RAW 数据或者用 CUPS 的cupsFilter做格式转换。更彻底的办法是让 Windows 也走ipp://而不是http://并确认attributes-charset协商一致。5. 用 ipptool 做 IPP 兼容性回归一个可复用的验证习惯5.1 为什么我每次上线前都跑一遍 ipptoolIPP 的坑大多不在协议本身而在打印机厂商对属性的支持程度参差不齐。同一份代码A 品牌打印机正常B 品牌就报client-error-bad-request。与其每次靠猜不如把ipptool做成一个回归脚本每次换打印机、换固件、换 CUPS 版本都跑一遍。这个习惯帮我省掉了至少三次“上线当天才发现打印机不认 PDF”的翻车。ipptool支持自定义测试文件你可以把关心的操作和属性写成.test脚本然后批量执行。下面是一个最小回归脚本覆盖查询属性、提交任务、查询任务状态三个动作。# 文件ipp-regression.test { OPERATION Get-Printer-Attributes GROUP operation-attributes-tag ATTR charset attributes-charset utf-8 ATTR language attributes-natural-language en ATTR uri printer-uri $uri STATUS successful-ok DISPLAY printer-state DISPLAY document-format-supported } { OPERATION Print-Job GROUP operation-attributes-tag ATTR charset attributes-charset utf-8 ATTR language attributes-natural-language en ATTR uri printer-uri $uri ATTR name job-name regression-job ATTR mimeMediaType document-format text/plain FILE /etc/hostname STATUS successful-ok DISPLAY job-id } { OPERATION Get-Job-Attributes GROUP operation-attributes-tag ATTR charset attributes-charset utf-8 ATTR language attributes-natural-language en ATTR uri printer-uri $uri ATTR integer job-id $job-id STATUS successful-ok DISPLAY job-state }执行方式ipptool -tv -f ipp-regression.test ipp://192.168.1.60:631/printers/office逻辑说明每个{ }块是一个独立请求。OPERATION指定操作码GROUP开始属性组ATTR定义属性STATUS断言期望状态DISPLAY打印关心的返回值。$uri和$job-id是变量ipptool会自动把上一个请求的job-id传给下一个。参数说明-t表示测试模式遇到失败会返回非零退出码方便接 CI-v显示详细报文-f指定测试文件。如果只想快速看打印机支持什么直接用内置的get-printer-attributes.test就够了。5.2 把回归脚本接进 CI 的一个轻量做法如果你在做打印中间件可以把ipptool回归脚本放进 CI 的集成测试阶段。用一个 Docker 容器跑 CUPS挂载一个虚拟 IPP 打印机比如cups-pdf或ippserver然后对容器执行上面的测试文件。这样每次代码变更都能验证 IPP 请求构造是否正确而不需要真实打印机。我一般会保留两份测试一份针对虚拟打印机跑得快验证协议层一份针对真实打印机手动触发验证厂商兼容性。虚拟打印机用ippserver就能起它实现了 IPP Everywhere 的基本属性足够覆盖大部分请求构造错误。注意ippserver和真实打印机在属性支持上仍有差异虚拟环境通过不代表真机通过。真机回归不能省但可以降低频率。5.3 一个让我少加班的习惯先查属性再发任务最后分享一个具体技巧任何新的 IPP 集成第一步永远不是写 Print-Job而是发 Get-Printer-Attributes把document-format-supported、printer-state、ipp-versions-supported、operations-supported四个属性打印出来。这四个属性基本决定了你的代码能不能跑、该怎么跑。document-format-supported告诉你发什么格式printer-state告诉你打印机是不是 idleipp-versions-supported告诉你版本号该写 0x0200 还是 0x0101operations-supported告诉你打印机支不支持 Cancel-Job、Get-Job-Attributes 这些操作。这个习惯看起来多了一步但能挡掉后面百分之八十的“为什么没反应”。我现在的做法是把它写成一个函数每次连接打印机先调用把结果缓存起来后续请求根据能力动态调整。希望帮到你。本文还有配套的精品资源点击获取