云打印实现流程与细节

1、云打印组成结构

云打印机分为windows端的服务端和Linux下的客户端。服务端和客户端都是多进程架构,分别由两个和四个进程组成。客户端和服务器通信采用thrift的rpc框架,通过定义统一的IDL接口,实现接口统一。

1.1客户端组成

客户端由四个进程组成,分别是:

  • deepin-cloud-print-agent

  • deepin-cloud-print-configurator

  • dcp

  • deepin-cloud-print-daemon

    deepin-cloud-print-agent是一个开机自启dbus服务,用户权限启动。这个dbus服务提供三个接口,用来获取配置文件存储的加密认证码,输入框的加密认证码和打印完成的系统通知。deepin-cloud-print-daemon也是一个开机自启的dbus服务进程,但是是root权限启动,这个dbus服务就是封装deepin-cloud-print-agent里面的接口(个人感觉这两个进程功能有些重复)。dcp是最重要的一个部分,作为backend后端,在程序安装的时候拷贝到/usr/lib/cups/backend/目录。使用云打印的时候cups就会调用这个后端去处理打印。deepin-cloud-print-configurator是Qt界面交互程序,提供用户输入ip和授权码的入口,展示和添加云打印机。

1.2客户端工作流程

  • 安装

    安装的时候除了会执行一些常规的拷贝操作,postinst脚本会在安装完成之后,修改dcp的权限以及日志目录的权限、用户、组,保证cups可以正常访问。重启之后agent和daemon就会在后台常驻提供dbus服务。

  • 启动配置界面

    配置界面启动之后,用户输入远程ip和授权码,点击下一步,程序通过thrift调用windows上的verify接口验证,验证完成之后通过ListPrinter接口获取远程打印机列表,然后获取本地cups的打印机列表进行对比,对已经添加过的云打印机区分不同的图标进行展示。用户点击添加按钮,程序调用lpadmin进行打印机添加,添加打印机的ppd是提前固定写死的,后续可以扩展为实时获取windows打印机的支持属性,动态生成ppd文件。

  • 打印

    用户通过软件选择云打印机进行打印时,cups会调用dcp后端程序,并将打印参数option传递给它。cups的job模块会向stdin的标准输入里面写入输出文件的内容,dcp会读取stdin的内容在/var/spool/dcp/SPOOL目录生成中间文件。然后使用gs将ps文件翻译为pdf文件,临时目录在/var/cache/cups/dcp/,然后通过print接口调用远程服务器上的打印机功能。

  • 打印机通知

    dcp等待服务器端处理完打印任务之后获取打印机结果,然后调用daemon的dbus接口弹出打印结果通知。

2.1服务端组成

服务端由两个进程组成:

  • PrintAgent

  • CloudPrint

PrintAgent是一个后台常驻UI程序,由Qt和thrift cpp实现,主要是四个功能,界面部分展示当前机器的ip和授权码,数据通过rpc接口从CloudPrint获取,用户可以手动更新授权码。业务部分实现了一个PrinterAgentServer服务实例,里面提供了打印的相关操作的具体实现接口,比如DefaultPrinter获取默认打印机,StartJob调用第三方工具SumatraPDF进行打印等等。这些接口供CloudPrint调用。然后实现了一个PrintNotifyClient客户端接口,去调用CloudPrint提供的对应的接口。这些接口主要用来通知CloudPrint服务器打印任务状态。PrintNotify是一个在子线程死循环监听打印机任务状态的实例,是通过winSpool接口获取相关的通知信息,然后转发给PrintNotifyClient。

CloudPrint是go实现的后台服务,主要功能类似于一个中转站,连接Linux的客户端和windows端的PrintAgent。CloudPrint启动了三个服务实例,configServer是和PrintAgent之间传输配置信息的服务接口,默认启动会创建一个默认授权码,PrintAgent获取和更新授权码都是通过这个服务。cloudPrintServer提供打印相关的中间接口,接受客户端dcp传输过来的打印数据和打印指令,处理完成之后调用go里面的serverPrinter客户端实例去调用PrintAgent服务端接口进行真实打印任务。printerNotifyServer作为通知服务的实例,接受PrintAgent发来的打印机任务通知请求。CloudPrint还调用svc库实现了windows服务的注册、启动、删除等接口。

2.2服务端工作流程

  • 安装

    安装完成之后,PrintAgent常驻后台,成为一个托盘程序,CloudPrint注册为一个windows服务。

  • 启动界面程序

    PrintAgent启动之后会在界面上展示ip列表和默认的授权码,这些信息是rpc接口从CloudPrint获取,用户重新输入授权码,更新也是调用CloudPrint接口,CloudPrint的config实例会将新的配置信息存储到本地。

  • 打印流程

    Linux客户端通过rpc接口调用CloudPrint的服务接口,然后CloudPrint通过rpc接口调用PrintAgent的打印服务端接口,所以CloudPrint本质上就是个中转程序,理论上可以将它的功能合并到c++实现的PrintAgent程序中,这里估计是考虑到go的高并发性,提高打印效率(大量打印任务的情况下),所以采用go作为中转程序。

2、thrift的使用技巧

thrift作为rpc架构,实现远程进程通信,有tcp和http两种协议选项。优点在于,只要定义了一个IDL接口,可以自动编译成常见语言的通信框架代码,编译工具thrift.exe可以去官网下载指定版本,不同版本编译的代码会有很大不同,就拿cpp来说,毕竟cpp的标准库也在不停添加新特性。然后用户只需要继承虚基类实现对应的虚函数(就cpp而言)就行,想要启动一个thrift的服务,只需要将该类作为一个handle传递给server,server内部维护的map就会将接口和实现对应起来,客户端只需要使用自动生成的客户端实例就可以连接服务器,直接调用服务器的接口。

IDL示例

User.thrift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
namespace cpp Sample



struct User {
1:required i32 id;
2:required string name;
3:required string avatar;
4:required string address;
5:required string mobile;
}

struct UserList {
1:required list<User> userList;
2:required i32 page;
3:required i32 limit;
}

Service.thrift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
include "User.thrift"

namespace cpp Sample



typedef map<string, string> Data

struct Response {
1:required i32 errCode; //错误码
2:required string errMsg; //错误信息
3:required Data data;
}

//定义服务
service Greeter {
Response SayHello(
1:required User.User user
)

Response GetUser(
1:required i32 uid
)
}

User.thrift里面定义的是公用的数据结构,namespace表示命名空间,cpp表示对应的语言,Service.thrift定义的是提供的服务接口,编译完成会生成服务端的虚基类和客户端的直接调用接口。编译命令如下:

1
thrift -r --gen cpp thrift/Service.thrift

-r参数可以自动编译include进去的User.thrift文件,–gen 后面接你的目标语言。


云打印实现流程与细节
http://yoursite.com/2020/07/22/云打印实现流程与细节/
作者
还在输入
发布于
2020年7月22日
许可协议