[英]How to prevent Akka TCP Stream of incoming connections from connecting after a configured max number of connections have connected?
[編輯] :問題已解決,Artur 提供的解決方案已添加為最后的編輯。
我試圖實現的想法是,TCP 服務器允許 n 個連接,如果我獲得 n+1 個連接,則不允許該連接。
因此,我需要以某種方式取消連接,然后將該特定流連接到 Sink.cancelled()。
我擁有的是連接到自定義流的 IncomingConnection,該流根據連接計數對 IncomingConnection 進行分區。 一旦超出最大連接數,分區邏輯會將其定向到連接到 Sink.cancelled 的出口。
期望是立即取消連接,但它允許客戶端連接,然后在一段時間后斷開連接。
也許我遇到了與為什么當連接沒有流時 Akka TCP 流服務器斷開客戶端連接.handlewith的答案中提到的相同問題? 找不到要處理的流,它會徘徊並斷開連接。
我在尋找
package com.example;
import java.util.concurrent.CompletionStage;
import akka.Done;
import akka.NotUsed;
import akka.actor.typed.ActorSystem;
import akka.actor.typed.javadsl.Behaviors;
import akka.stream.FlowShape;
import akka.stream.SinkShape;
import akka.stream.UniformFanOutShape;
import akka.stream.javadsl.Flow;
import akka.stream.javadsl.GraphDSL;
import akka.stream.javadsl.Partition;
import akka.stream.javadsl.Sink;
import akka.stream.javadsl.Source;
import akka.stream.javadsl.Tcp;
import akka.stream.javadsl.Tcp.IncomingConnection;
import akka.stream.javadsl.Tcp.ServerBinding;
import akka.util.ByteString;
public class SimpleStream03 {
private static int connectionCount = 0;
private static int maxConnectioCount = 2;
public static void runServer() {
ActorSystem actorSystem = ActorSystem.create(Behaviors.empty(), "actorSystem");
Source<IncomingConnection, CompletionStage<ServerBinding>> source = Tcp.get(actorSystem).bind("127.0.0.1",
8888);
Sink<IncomingConnection, CompletionStage<Done>> handler = Sink.foreach(conn -> {
System.out.println("Handler Sink Connection Count " + connectionCount);
System.out.println("Handler Sink Client connected from: " + conn.remoteAddress());
conn.handleWith(Flow.of(ByteString.class), actorSystem);
});
Flow<IncomingConnection, IncomingConnection, NotUsed> connectioncountFlow = Flow
.fromGraph(GraphDSL.create(builder -> {
SinkShape<IncomingConnection> sinkCancelled = builder.add(Sink.cancelled());
FlowShape<IncomingConnection, IncomingConnection> inFlowShape = builder
.add(Flow.of(IncomingConnection.class).map(conn -> {
connectionCount++;
return conn;
}));
UniformFanOutShape<IncomingConnection, IncomingConnection> partition = builder
.add(Partition.create(IncomingConnection.class, 2, param -> {
if (connectionCount > maxConnectioCount) {
connectionCount = maxConnectioCount;
System.out.println("Outlet 0 -> Sink.cancelled");
return 0;
}
System.out.println("Outlet 1 -> forward to handler");
return 1;
}));
builder.from(inFlowShape).toFanOut(partition);
builder.from(partition.out(0)).to(sinkCancelled);
return new FlowShape<>(inFlowShape.in(), partition.out(1));
}));
CompletionStage<ServerBinding> bindingFuture = source.via(connectioncountFlow).to(handler).run(actorSystem);
bindingFuture.handle((binding, throwable) -> {
if (binding != null) {
System.out.println("Server started, listening on: " + binding.localAddress());
} else {
System.err.println("Server could not bind to : " + throwable.getMessage());
actorSystem.terminate();
}
return NotUsed.getInstance();
});
}
public static void main(String[] args) throws InterruptedException {
SimpleStream03.runServer();
}
}
輸出確認分區正在工作,並且 2 個連接正在訪問主接收器處理程序。
Server started, listening on: /127.0.0.1:8888
Outlet 1 -> forward to handler
Handler Sink Connection Count 1
Handler Sink Client connected from: /127.0.0.1:60327
Outlet 1 -> forward to handler
Handler Sink Connection Count 2
Handler Sink Client connected from: /127.0.0.1:60330
Outlet 0 -> Sink.cancelled
編輯:實施已接受的答案,以下更改可防止違反閾值后傳入連接。 客戶端看到對等方重置的連接
+---------------------------------------------------------+
| |
| Fail Flow |
| +-------------+ |
| +-->+Sink |Source| |
| | |cancel|fail | |
| | +-------------+ |
| +----------+ | |
| | | | |
| +----------+ | O0+--+ |
connections|FLOW | | | O0:count > threshold |
+-------+--> +------->+ Partition| |
| |count++ | | O1+----------------------------->
| +----------+ | | |
| | | O1:count <= threshold|
| +----------+ |
| |
+---------------------------------------------------------+
代替
SinkShape<IncomingConnection> sinkCancelled = builder.add(Sink.cancelled());
和
Sink<IncomingConnection, CompletionStage<Done>> connectionCancellingSink = Sink.foreach(ic -> ic
.handleWith(Flow.fromSinkAndSource(Sink.cancelled(), Source.failed(new Throwable("killed"))),
actorSystem));// Sink.ignore and Sink.cancel give me the same expected result
SinkShape<IncomingConnection> sinkCancelledShape = builder.add(connectionCancellingSink);
Sink.cancelled()
立即取消上游( https://doc.akka.io/docs/akka/current/stream/operators/Sink/cancelled.html )。
但是,您的Partition
是在eagerCancel 設置為false
情況下創建的
/**
* Create a new `Partition` operator with the specified input type, `eagerCancel` is `false`.
*
* @param clazz a type hint for this method
* @param outputCount number of output ports
* @param partitioner function deciding which output each element will be targeted
*/
def create[T](
@unused clazz: Class[T],
outputCount: Int,
partitioner: function.Function[T, Integer]): Graph[UniformFanOutShape[T, T], NotUsed] =
new scaladsl.Partition(outputCount, partitioner.apply, eagerCancel = false)
這意味着該Partition
只有在其所有下游連接都取消時才會取消。 這不是你想要的。 但是您也不希望eagerCancel=true
,因為這意味着超過限制的第一個連接將破壞整個Partition
,因此您的所有連接..基本上會破壞整個服務器。
也許從嵌套流的角度考慮這里的情況是有用的。 頂級Source<IncomingConnection>
表示接受的 TCP 連接流。 您不想取消該流。 如果你這樣做,你只是殺死了你的服務器。 每個IncomingConnection
代表一個單獨的 TCP 連接。 這種連接上發生的字節交換也表示為流。 您要為每個高於閾值的連接取消此流。
為此,您可以像這樣定義一個取消Sink
的連接:
Sink<IncomingConnection, CompletionStage<Done>>
connectionCancellingSink =
Sink.foreach(
ic ->
ic.handleWith(
Flow.fromSinkAndSource(Sink.cancelled(), Source.empty()),
actorSystem));
IncomingConnection
允許您使用handleWith
方法附加處理程序。 為此,您需要一個Flow
因為您既消耗來自客戶端的字節,也可能將字節發送到客戶端(傳入的字節進入Flow
以及您想要發送回客戶端的任何內容,您需要在Flow
的輸出上生成)。 在我們的例子中,我們只想立即取消該流。 您可以使用Flow.fromSinkAndSource
從... Sink
和Source
獲取Flow
。 你可以利用它來插件Sink.cancelled
和Source.empty
。 所以Source.empty
意味着我們不會向連接發送任何字節, Sink.cancelled
將立即取消流,並希望取消底層 TCP 連接。 讓我們試一試。
最后要做的是將我們新的取消Sink
插入到Partition
SinkShape<IncomingConnection> sinkCancelled =
builder.add(connectionCancellingSink);
//...the rest stays the same
builder.from(partition.out(0))
.to(sinkCancelled);
如果這樣做,在第三次連接時,您將看到以下消息:
Not aborting connection from 127.0.0.1:49874 because downstream cancelled stream without failure
所以Sink.cancelled()
並沒有真正觸發你想要的。 讓我們重新定義我們的取消Flow
:
Sink<IncomingConnection, CompletionStage<Done>>
connectionCancellingSink =
Sink.foreach(
ic ->
ic.handleWith(
Flow.fromSinkAndSource(Sink.ignore(),
Source.failed(new Throwable("killed"))),
actorSystem));
現在,這是使用Sink.ignore()
來忽略傳入的字節,但通過Source.failed(...)
使流失敗。 這將導致連接立即終止,並且 stracktrace 將打印在服務器輸出上。 如果你想保持安靜,你可以在沒有堆棧跟蹤的情況下創建異常:
public static class TerminatedException extends Exception
{
public TerminatedException(String message)
{
super(message, null, false, false);
}
}
然后使用它來使您的連接流失敗
Flow.fromSinkAndSource(Sink.ignore(),
Source.failed(
new TerminatedException(("killed"))))
這樣你會得到更干凈的日志。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.