[英]akka streams over tcp
Here is the setup: I want to be able to stream messages (jsons converted to bytestrings) from a publisher to a remote server subscriber over a tcp connection. 以下是设置:我希望能够通过tcp连接将消息(jsons转换为bytestrings)从发布者流式传输到远程服务器订阅者。
Ideally, the publisher would be an actor that would receive internal messages, queue them and then stream them to the subscriber server if there is outstanding demand of course. 理想情况下,发布者将是一个接收内部消息,排队然后然后将它们流式传输到订阅者服务器的参与者,如果当然有出色的需求。 I understood that what is necessary for this is to extend ActorPublisher
class in order to onNext()
the messages when needed. 据我ActorPublisher
,这需要扩展ActorPublisher
类,以便在需要时将消息扩展到onNext()
。
My problem is that so far I am able just to send (receive and decode properly) one shot messages to the server opening a new connection each time. 我的问题是,到目前为止,我只能向服务器发送(接收和解码) 一次性消息,每次都打开一个新的连接。 I did not manage to get my head around the akka doc and be able to set the proper tcp Flow
with the ActorPublisher
. 我没有设法绕过akka doc并能够使用ActorPublisher
设置正确的tcp Flow
。
Here is the code from the publisher: 以下是发布商的代码:
def send(message: Message): Unit = {
val system = Akka.system()
implicit val sys = system
import system.dispatcher
implicit val materializer = ActorMaterializer()
val address = Play.current.configuration.getString("eventservice.location").getOrElse("localhost")
val port = Play.current.configuration.getInt("eventservice.port").getOrElse(9000)
/*** Try with actorPublisher ***/
//val result = Source.actorPublisher[Message] (Props[EventActor]).via(Flow[Message].map(Json.toJson(_).toString.map(ByteString(_))))
/*** Try with actorRef ***/
/*val source = Source.actorRef[Message](0, OverflowStrategy.fail).map(
m => {
Logger.info(s"Sending message: ${m.toString}")
ByteString(Json.toJson(m).toString)
}
)
val ref = Flow[ByteString].via(Tcp().outgoingConnection(address, port)).to(Sink.ignore).runWith(source)*/
val result = Source(Json.toJson(message).toString.map(ByteString(_))).
via(Tcp().outgoingConnection(address, port)).
runFold(ByteString.empty) { (acc, in) ⇒ acc ++ in }//Handle the future
}
and the code from the actor which is quite standard in the end: 以及最终非常标准的演员代码:
import akka.actor.Actor
import akka.stream.actor.ActorSubscriberMessage.{OnComplete, OnError}
import akka.stream.actor.{ActorPublisherMessage, ActorPublisher}
import models.events.Message
import play.api.Logger
import scala.collection.mutable
class EventActor extends Actor with ActorPublisher[Message] {
import ActorPublisherMessage._
var queue: mutable.Queue[Message] = mutable.Queue.empty
def receive = {
case m: Message =>
Logger.info(s"EventActor - message received and queued: ${m.toString}")
queue.enqueue(m)
publish()
case Request => publish()
case Cancel =>
Logger.info("EventActor - cancel message received")
context.stop(self)
case OnError(err: Exception) =>
Logger.info("EventActor - error message received")
onError(err)
context.stop(self)
case OnComplete =>
Logger.info("EventActor - onComplete message received")
onComplete()
context.stop(self)
}
def publish() = {
while (queue.nonEmpty && isActive && totalDemand > 0) {
Logger.info("EventActor - message published")
onNext(queue.dequeue())
}
}
I can provide the code from the subscriber if necessary: 如有必要,我可以提供订阅者的代码:
def connect(system: ActorSystem, address: String, port: Int): Unit = {
implicit val sys = system
import system.dispatcher
implicit val materializer = ActorMaterializer()
val handler = Sink.foreach[Tcp.IncomingConnection] { conn =>
Logger.info("Event server connected to: " + conn.remoteAddress)
// Get the ByteString flow and reconstruct the msg for handling and then output it back
// that is how handleWith work apparently
conn.handleWith(
Flow[ByteString].fold(ByteString.empty)((acc, b) => acc ++ b).
map(b => handleIncomingMessages(system, b.utf8String)).
map(ByteString(_))
)
}
val connections = Tcp().bind(address, port)
val binding = connections.to(handler).run()
binding.onComplete {
case Success(b) =>
Logger.info("Event server started, listening on: " + b.localAddress)
case Failure(e) =>
Logger.info(s"Event server could not bind to $address:$port: ${e.getMessage}")
system.terminate()
}
}
thanks in advance for the hints. 提前感谢提示。
My first recommendation is to not write your own queue logic. 我的第一个建议是不要编写自己的队列逻辑。 Akka provides this out-of-the-box. Akka提供了这种开箱即用的功能。 You also don't need to write your own Actor, Akka Streams can provide it as well. 您也不需要编写自己的Actor,Akka Streams也可以提供它。
First we can create the Flow that will connect your publisher to your subscriber via Tcp. 首先,我们可以创建Flow,通过Tcp将您的发布者连接到您的订阅者。 In your publisher code you only need to create the ActorSystem
once and connect to the outside server once: 在您的发布商代码中,您只需创建一次ActorSystem
并连接到外部服务器一次:
//this code is at top level of your application
implicit val actorSystem = ActorSystem()
implicit val actorMaterializer = ActorMaterializer()
import actorSystem.dispatcher
val host = Play.current.configuration.getString("eventservice.location").getOrElse("localhost")
val port = Play.current.configuration.getInt("eventservice.port").getOrElse(9000)
val publishFlow = Tcp().outgoingConnection(host, port)
publishFlow
is a Flow
that will input ByteString
data that you want to send to the external subscriber and outputs ByteString data that comes from subscriber: publishFlow
是一个Flow
,将输入ByteString
要发送给外部用户输出字节字符串是来自用户的数据资料:
// data to subscriber ----> publishFlow ----> data returned from subscriber
The next step is the publisher Source. 下一步是发布者来源。 Instead of writing your own Actor you can use Source.actorRef
to "materialize" the Stream into an ActorRef
. 而不是写你自己的演员,你可以用Source.actorRef
以“兑现”的流注入ActorRef
。 Essentially the Stream will become an ActorRef for us to use later: 基本上,Stream将成为我们稍后使用的ActorRef:
//these values control the buffer
val bufferSize = 1024
val overflowStrategy = akka.stream.OverflowStrategy.dropHead
val messageSource = Source.actorRef[Message](bufferSize, overflowStrategy)
We also need a Flow to convert Messages into ByteString 我们还需要一个Flow将Messages转换为ByteString
val marshalFlow =
Flow[Message].map(message => ByteString(Json.toJson(message).toString))
Finally we can connect all of the pieces. 最后,我们可以连接所有部分。 Since you aren't receiving any data back from the external subscriber we'll ignore any data coming in from the connection: 由于您没有从外部订户接收任何数据,我们将忽略来自连接的任何数据:
val subscriberRef : ActorRef = messageSource.via(marshalFlow)
.via(publishFlow)
.runWith(Sink.ignore)
We can now treat this stream as if it were an Actor: 我们现在可以将此流视为一个Actor:
val message1 : Message = ???
subscriberRef ! message1
val message2 : Message = ???
subscriberRef ! message2
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.