我过去成功使用过Akka Streams,但是,我现在很难理解为什么定义Akka-HTTP中的客户端Websocket Streams并按照{{3}中显示的那样工作}。
由于WebSocket连接允许全双工通信,我希望这种连接由Akka HTTP中的两个独立流表示,一个用于传入流量,一个用于传出流量。实际上,文档说明了以下内容:
WebSocket包含两个消息流[...]
它进一步指出传入的消息由Sink
表示,而传出的消息由Source
表示。这是我的第一个困惑点 - 当使用两个独立的流时,你会期望总共需要处理两个源和两个接收器而不是每种接收器中的一个。目前,我的猜测是传入流的来源以及传出流的接收器对开发人员来说并没有多大用处,因此只是“隐藏”。
但是,将所有内容连接在一起时确实会让人感到困惑(请参阅上面链接的文档)。
使用singleWebSocketRequest
时有问题的部分:
val flow: Flow[Message, Message, Future[Done]] =
Flow.fromSinkAndSourceMat(printSink, helloSource)(Keep.left)
使用webSocketClientFlow
时的相同部分:
val (upgradeResponse, closed) =
outgoing
.viaMat(webSocketFlow)(Keep.right)
.toMat(incoming)(Keep.both)
.run()
这与我目前对流的工作流程的理解相矛盾。
Source
和传入邮件的Sink
组合在一起?上面的代码看起来像是在向我自己而不是服务器发送消息。Flow[Message, Message, ...]
的语义是什么?将传出消息转换为传入消息似乎没有意义。感谢您提供理解的任何帮助。
编辑:
使用Source
和Sink
并通过WebSocket发送数据没有问题,我只想了解为什么这些阶段的连接是这样完成的。
答案 0 :(得分:4)
WebSocket包含两个独立的流,只是这些流(可能)不在同一个JVM上。
您有两个对等通信,一个是服务器,另一个是客户端,但从已建立的WebSocket连接的角度来看,差异无关紧要。一个数据流是对等体1向对等体2发送消息,另一个流体是对等体2向对等体1发送消息,然后在这两个对等体之间存在网络边界。如果您一次查看一个对等体,则对等体1从对等体2接收消息,而第二流体对等体1正在向对等体2发送消息。
每个对等体都有一个接收部分的接收器和一个发送部分的源。实际上你确实有两个Source和两个Sinks,但不是两个都在同一个ActorSystem上(假设为了解释这两个对等体都在Akka HTTP中实现)。来自对等体1的源连接到对等体2的接收器,对等体2的源连接到对等体1的接收器。
因此,您编写了一个描述如何处理第一个流上的传入消息的Sink和一个描述如何通过第二个流发送消息的Source。通常,您希望根据您收到的消息生成消息,因此您可以将这两者连接在一起,并通过描述如何响应和传入消息并生成任意数量的传出消息的不同流来路由消息。 Flow[Message, Message, _]
并不意味着您正在将传出邮件转换为传入邮件,而是将传入邮件转换为传出邮件。
webSocketFlow
是典型的异步边界,代表另一个对等体的流。它通过将传出消息发送到另一个对等体并发送其他对等体发送的任何消息,将其“转换”为传入消息。
val flow: Flow[Message, Message, Future[Done]] =
Flow.fromSinkAndSourceMat(printSink, helloSource)(Keep.left)
这个流程是你的同伴中两个流的一半:
[message from other peer]
已与printSink
helloSource
已与[message to the other peer]
传入消息和外发消息之间没有关系,您只需打印收到的所有内容并发送一个“hello world!”。实际上,由于源在一条消息之后完成,因此WebSocket连接也会关闭,但是如果用例如Source.repeat
替换Source,那么你会经常发送(洪水,真的)“你好,世界!”通过电线,无论传入消息的速率如何。
val (upgradeResponse, closed) =
outgoing
.viaMat(webSocketFlow)(Keep.right)
.toMat(incoming)(Keep.both)
.run()
在这里,您可以获取来自outgoing
的所有内容,这是您要发送的消息,通过webSocketFlow
路由它,通过与其他对等方通信来“转换”消息并生成每个接收到的消息邮件进入incoming
。通常,您有一个有线协议,您可以使用有线格式对案例类/ pojo / dto消息进行编码和解码。
val encode: Flow[T, Message, _] = ???
val decode: Flow[Message, T, _] = ???
val upgradeResponse = outgoing
.via(encode)
.viaMat(webSocketFlow)(Keep.right)
.via(decode)
.to(incoming)
.run()
或者你可以想象一些聊天服务器(啊,websockets和聊天),它们向许多客户广播和合并消息。这应该从任何客户端接收任何消息并将其发送到每个客户端(仅用于演示,未经测试,可能不是您想要的实际聊天服务器):
val chatClientReceivers: Seq[Sink[Message, NotUsed]] = ???
val chatClientSenders: Seq[Source[Message, NotUsed]] = ???
// each of those receivers/senders could be paired in their own websocket compatible flow
val chatSockets: Seq[Flow[Message, Message, NotUsed]] =
(chatClientReceivers, chatClientSenders).zipped.map(
(outgoingSendToClient, incomingFromClient) =>
Flow.fromSinkAndSource(outgoingSendToClient, incomingFromClient))
val toClients: Graph[SinkShape[Message], NotUsed] =
GraphDSL.create() {implicit b =>
import GraphDSL.Implicits._
val broadcast = b.add(Broadcast[Message](chatClientReceivers.size))
(broadcast.outArray, chatClientReceivers).zipped
.foreach((bcOut, client) => bcOut ~> b.add(client).in)
SinkShape(broadcast.in)
}
val fromClients: Graph[SourceShape[Message], NotUsed] =
GraphDSL.create() {implicit b =>
import GraphDSL.Implicits._
val merge = b.add(Merge[Message](chatClientSenders.size))
(merge.inSeq, chatClientSenders).zipped
.foreach((mIn, client) => b.add(client).out ~> mIn)
SourceShape(merge.out)
}
val upgradeResponse: Future[WebSocketUpgradeResponse] =
Source.fromGraph(fromClients)
.viaMat(webSocketFlow)(Keep.right)
.to(toClients)
.run()
希望这有点帮助。