附: DBus并不是我们真正需要的,因为我们只需要客户端 – 服务器架构而不是客户端总线守护进程.
P.P.S.关于偏离主题 – 在这个问题中我什么也看不到需要自以为是的答案.答案应包含事实但不包含意见.
信号可以通过几种不同的方式在Cap’n Proto之上实现.对象链
Cap’n Proto RPC调用没有问题需要很长时间才能完成.在同一连接上的其他呼叫可以正常继续,并且您可以一次有许多未完成的呼叫.因此,接收信号的一种策略是在返回之前进行等待信号的呼叫.
许多RPC系统支持挂起呼叫,但还有一个额外的挑战:如果你有一个信号流,并且客户端观察流中的每个信号都很重要,那么如果生成新信号的速度比客户端调用的信号快,那么事情会变得复杂RPC来读取它们.您需要为每个客户端保留一个缓冲区.但是,如果客户端死亡并停止发出请求怎么办?现在你需要某种超时,然后清除它.
与大多数其他RPC系统不同,Cap’n Proto支持即时生成新对象.因此,您可以将信号流表示为对象链.例如:
struct MyPayload { ... } interface MyInterface { subscribe @0 () -> (firstSignal :Signal(MyPayload)); # Subscribe to signals from this interface. } interface Signal(Type) { # One signal in a stream of signals. Has a payload, and lets you # wait for the next signal. get @0 () -> (value :Type); # Gets the payload value of this signal. (Returns immediately.) waitForNext @1 () -> (nextSignal :Signal(Type)); # Waits for the next signal in the sequence, returning a new # `Signal` object representing it. }
这大大简化了服务器端的状态管理,因为Cap’n Proto会在所有客户端都表明它们已经完成之后自动调用每个对象的析构函数(通过销毁客户端引用,也就是“丢弃”它).如果客户端断开连接,则会隐式删除其所有引用.
回调
因为Cap’n Proto允许双向RPC调用(客户端 – >服务器和服务器 – >客户端),所以您可以使用回调实现“信号”或发布/订阅机制:
struct MyPayload { ... } interface MyInterface { subscribe @0 (cb :Callback(MyPayload)) -> (handle :Handle); } interface Callback(Type) { call @0 (value :Type); } interface Handle {}
客户端调用subscribe()并传递回调对象cb.然后,只要有信号,服务器就可以回叫客户端.
请注意,subscribe()返回一个Handle,它是一个没有方法的对象.这样做的目的是检测客户端何时取消订阅.如果客户端丢弃句柄,将通知服务器(服务器端对象的析构函数将运行),然后服务器可以取消注册回调.这还处理客户端断开连接的情况 – 所有对象引用都隐式丢弃在断开连接上.
乍一看,由于其简单性,该解决方案可能看起来比对象链解决方案更好.但是,它存在的问题是,您现在具有指向两个方向的对象引用,这可能导致循环.在您的客户端代码中,您必须小心确保回调实现不“拥有”保持其注册的句柄,否则它将永远不会被清除(除非连接关闭).在等待服务器取消注册回调时,您还必须确保在删除句柄后仍可以短时间内调用回调.这些问题在对象链解决方案中不存在,这可能使该解决方案更加清晰.
其他RPC系统
我上面讨论了Cap’n Proto,因为我是作者,因为它提供了比大多数RPC系统更多的选项.
如果您使用gRPC,您可以使用其“流”功能来支持信号等内容.流式RPC可以随时间返回多个响应.
我不太确定Thrift.我最后一次尝试它时,请求必须是FIFO,这意味着长时间运行的RPC是禁止的.然而,那是很久以前的事了,也许从那时起它就发生了变化.