簡體   English   中英

在配置的最大連接數連接后,如何防止傳入連接的 Akka TCP 流連接?

[英]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的答案中提到的相同問題 找不到要處理的流,它會徘徊並斷開連接。

我在尋找

  1. 一個干凈的解決方案,當超出最大值時不允許傳入連接。
  2. Sink.cancelled() 在做什么(如果它正在做某事)。
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從... SinkSource獲取Flow 你可以利用它來插件Sink.cancelledSource.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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM