Scala 处理带有一次性报头的Akka流

Scala 处理带有一次性报头的Akka流,scala,akka,akka-stream,Scala,Akka,Akka Stream,我有一个接收TCP套接字连接的应用程序,该连接将以以下形式发送数据: n{json}bbbbbbbbbb… 其中n是以下json的长度(以字节为单位),而json可能类似于{'splitEvery':5},这将指示我如何分解和处理后面可能无限的字节字符串 我想用Scala中的Akka处理这个流。我认为streams是实现这一点的正确工具,但我很难找到一个使用具有不同处理阶段的流的示例。大多数流似乎一遍又一遍地做着同样的事情,比如prefixAndTail示例。这非常接近于我希望处理流的n{jso

我有一个接收TCP套接字连接的应用程序,该连接将以以下形式发送数据:

n{json}bbbbbbbbbb…

其中
n
是以下
json
的长度(以字节为单位),而
json
可能类似于
{'splitEvery':5}
,这将指示我如何分解和处理后面可能无限的字节字符串

我想用Scala中的Akka处理这个流。我认为
streams
是实现这一点的正确工具,但我很难找到一个使用具有不同处理阶段的流的示例。大多数流似乎一遍又一遍地做着同样的事情,比如
prefixAndTail
示例。这非常接近于我希望处理流的
n{json}
部分的方式,但区别在于,我只需要在每个连接上执行一次,然后进入不同的处理“阶段”


有人能给我举一个使用Akka流的例子吗?

由于块大小取决于流的内容,但在处理流数据之前,所有处理阶段都必须具体化,因此您不能轻易使用像
Source.group(chunkSize)
这样的方便方法。我建议从流的开头剥离元数据(使用与Akka流不同的方法),并将流的其余部分馈送到
Source.group(chunkSize)

或者,您可以使用状态机折叠/扫描流,但这要麻烦得多:

implicit val system=ActorSystem(“测试”)
隐式val-materializer=actormatarializer()
val input=“”17{“splitEvery”:5}AAAAA BBBBB CCCCC”“”
def getChunkSize(json:String)=5//虚拟实现
封闭特征状态
案例类GetLength(编号:String)扩展状态
case类GetJson(n:Int,json:String)扩展了State
案例类ProcessData(chunkSize:Int,s:String)扩展了状态
键入输出=(状态,选项[字符串])
val future=Source.fromIterator(()=>input.iterator)。
扫描[输出]((GetLength(“”,无)){
如果e.isDigit=>(GetLength(s+e),则为大小写((GetLength),_u2;),e)(GetLength(s+e),无)
大小写((GetLength,_25;),e)=>(GetJson(s.toInt-1,e.toString),无)
大小写((GetJson(0,json),2;),e)=>(ProcessData(getChunkSize(json),e.toString),无)
大小写((GetJson(n,json),z),e)=>(GetJson(n-1,json+e),无)
如果s.length==chunkSize-1=>(ProcessData(chunkSize,“”),则为case((ProcessData(chunkSize,s),u),e)(ProcessData(chunkSize,“”),部分(s+e))
大小写((ProcessData(chunkSize,s),z),e)=>(ProcessData(chunkSize,s+e),无)
}.
收集{case(u,Some(s))=>s}。
runForeach(println)
println(等待结果(未来,1秒))
//AAAA
//bbbbb
//ccccc

作为记录,这里有一种方法是行不通的,因为
takeWhile
消耗迭代器的下一个元素(当
\uu.isDigit
失败时),这仍然是后续JSON解析阶段所需要的:

val it=input.iterator
def nextSource=Source.fromIterator(()=>it)
隐式类Stringify[+Out,+Mat](val-source:source[Out,Mat]){
def stringify=source.runFold(“”)(u+389;)
}
val future2=nextSource。
takeWhile(uu.isDigit)。
严格化。
映射(uu.toInt)。
映射{l=>
下一个来源。
以(l)为例。
严格化。
映射(getChunkSize)。
映射{chunkSize=>
下一个来源。
分组(块大小)。
映射(uu.mkString)。
runForeach(println)
}
}
println(等待结果(未来2,1秒))
//aaaab
//bbbbc
//中交

这里有一个
图形阶段
,它通过testring处理
s:

  • 从标题中提取块大小
  • 发出指定块大小的
    ByteString
    s
导入akka.stream.{属性,流形状,入口,出口}
导入akka.stream.stage.{GraphStage,GraphStageLogic,InHandler,OutHandler}
导入akka.util.ByteString
类预处理器扩展GraphStage[FlowShape[ByteString,ByteString]]{
val in:入口[ByteString]=入口(“ParseHeader.in”)
val out:Outlet[ByteString]=Outlet(“ParseHeader.out”)
覆盖val形状=FlowShape.of(in,out)
重写def createLogic(继承的属性:属性):GraphStageLogic=
新图形符号学(形状){
var buffer=ByteString.empty
变量chunkSize:Option[Int]=None
private var upstreamFinished=假
private val headerPattern=“”^\d+\{“splitEvery”:(\d+\}“。r
/**
*@param data要解析的数据。
*@返回区块大小和标头大小(如果标头
*可以解析。
*/
def parseHeader(数据:ByteString):选项[(Int,Int)]=
头模式。
findFirstMatchIn(data.decodeString(“UTF-8”))。
map{mtch=>(mtch.group(1.toInt,mtch.end)}
setHandler(输出,新输出处理程序{
覆盖def onPull():单位={
if(isClosed(in))emit()
否则拉(入)
}
})
setHandler(在中,新在Handler中{
覆盖def onPush():单位={
val elem=抓取(in)
缓冲区+++=elem
if(chunkSize.isEmpty){
parseHeader(缓冲区)foreach{case(chunk,headerSize)=>
chunkSize=Some(块)
buffer=buffer.drop(headerSize)
}
}
发射()
}
覆盖def onUpstreamFinish():单位={
上游完成=真
if(chunkSize.isEmpty | | buffer.isEmpty)completeStage()
否则{
如果(i可用(输出))发射()
}
}
})
专用def continue():单位=
如果(isClosed(in))完成测试
否则拉(入)
专用def emit():单位={
块大小匹配{
case None=>continue()
案例部分(大小)=>
如果(上游完成&&buffer.isEmpty)||
!upstreamFinished&&buffer.size[17{"] [splitE] [very"] [] [: 5}] [aaaaa] [bbb] [bbcccc] [] [cddd]

aaaaa
bbbbb
ccccc
ddd