简体   繁体   English

如何断开与 java akka 流服务器的 tcp 连接?

[英]How to disconnect tcp connections to a java akka stream server?

I have simple TCP server that echoes back data received from the client connection.我有简单的 TCP 服务器,可以回显从客户端连接接收到的数据。

I want to do the following我想做以下事情

  1. When a client connect happens I want to respond back immediately with some data.当客户端连接发生时,我想立即回复一些数据。 ByteBuffer for simplicity. ByteBuffer 为简单起见。
  2. I want to allow n number of connections.我想允许 n 个连接。 Currently I am doing it through a connection count variable and just don't call handle.目前我是通过连接计数变量来完成的,只是不调用句柄。 I don't think that's the right way to do it.我认为这不是正确的做法。
  3. I want the ability to send messages from the server at any time on specific connections.我希望能够在特定连接上随时从服务器发送消息。 So if I have 3 connections, I want to pick and choose a connection I want to send a message to.因此,如果我有 3 个连接,我想选择一个我想向其发送消息的连接。
  4. Ability to shut down the server after closing the connections.能够在关闭连接后关闭服务器。 Any pointers / snippets of codes would be greatly appreciated.任何指针/代码片段将不胜感激。

Code snippet that I am working with我正在使用的代码片段

// handles individual connections
final Sink<IncomingConnection, CompletionStage<Done>> handler = Sink.foreach(conn -> {
    connectionCount++;
    if (connectionCount < 2) {
        System.out.println("Client connected from: " + conn.remoteAddress());
        conn.handleWith(Flow.<ByteString>create(), actorSystem);
    }
    // Is not handling a good away to avoid the incoming connection?
    // When I use  netcat to connect to this server I see it saying 
    //connected and then the connection goes away when a second connection is attempted.
     

});

//Create the TCP server
Source<IncomingConnection, CompletionStage<ServerBinding>> source = Tcp.get(actorSystem)
        .bind("127.0.0.1", 8888);
// connect the server source to the sink handler and run/materialize it
final CompletionStage<ServerBinding> bindingFuture = source.to(handler)
        .run(actorSystem);

bindingFuture.handle((ServerBinding binding, Throwable exception) -> {
    if (binding != null) {
        System.out.println("Server started, listening on: " + binding.localAddress());
    }
    else {
        System.err.println("Server could not bind to  : " + exception.getMessage());
        actorSystem.terminate();
    }
    return NotUsed.getInstance();
});

} }

Edit 1编辑 1

I kind of got problem 1 to work using a keepalive processing stage and echoes back the received data.我使用keepalive处理阶段解决了问题1并回显了接收到的数据。 So insert a flow with a keepalive that adds a bytestring to be sent back at regular intervals.因此,插入一个带有 keepalive 的流,该流添加要定期发回的字节串。 This is not perfect and I will try a fan in with the timer source that injects data in. Then there was this Flow.fromSinkAndSource that I tried, looked promising but could not get it to work.这并不完美,我将尝试使用注入数据的计时器源。然后我尝试了这个 Flow.fromSinkAndSource,看起来很有希望,但无法让它工作。 https://doc.akka.io/docs/akka/current/stream/operators/Flow/fromSinkAndSource.html https://doc.akka.io/docs/akka/current/stream/operators/Flow/fromSinkAndSource.html

What currently works is this change目前有效的是这种变化

ActorSystem actorSystem = ActorSystem.create(Behaviors.empty(), "actorSystem");
final ByteString keepAliveMessage = ByteString.fromString("KEEP ALIVE");
Flow<ByteString, ByteString, NotUsed> keepAliveInject = Flow.of(ByteString.class)
        .keepAlive(Duration.ofSeconds(1), () -> keepAliveMessage);

final Sink<IncomingConnection, CompletionStage<Done>> handler = Sink.foreach(conn -> {
    System.out.println("Client connected from: " + conn.remoteAddress());
    conn.handleWith(keepAliveInject.via(Flow.<ByteString>create()), actorSystem);
    
});

This is how I have solved the some of my problems.这就是我如何解决我的一些问题。

Allowing N number of connections and sending a message on a client connection.允许 N 个连接并在客户端连接上发送消息。

The code can be run use multiple netcat clients to connect to it and see the behavior.可以使用多个 netcat 客户端连接到它并查看行为来运行代码。

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.SourceShape;
import akka.stream.UniformFanInShape;
import akka.stream.UniformFanOutShape;
import akka.stream.javadsl.Concat;
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;

/**
 * We want to creat a TCP server listening on port 8888. Will allow N=2
 * connections. Any more than 2 will be rejected. On client connect the server
 * will send a "OnConnect" string. Server will maintain count of connections, if
 * client connects connection count will be increments and if client disconnects
 * connection count will be decremented. Server will behave as a echo server,
 * responding back with data sent by the client.
 */
public class SimpleStream05 {
    private static int connectionCount = 0;
    private static int maxConnectioCount = 2;

    private static void run() {
        ActorSystem actorSystem = ActorSystem.create(Behaviors.empty(), "actorSystem");

        Flow<ByteString, ByteString, NotUsed> serverLogic = Flow.fromGraph(GraphDSL.create(builder -> {

            // We have our source that will send the onConnect message to the tcp client.

            SourceShape<ByteString> onConnectSourceShape = builder
                    .add(Source.single(ByteString.fromString("OnConnect")));

            // Incoming Connection that just echoes back what we received.

            UniformFanInShape<ByteString, ByteString> concatShape = builder.add(Concat.create());
            FlowShape<ByteString, ByteString> echoFlowShape = builder.add(Flow.of(ByteString.class).map(y -> y));
            /*
             * Connect the single source to concat and connect the echo flows output to
             * concat. When a tcp connection comes in, this handler (flow) is called, concat
             * will first take the input from the onConnectSourceShape which is a single and
             * will emit just once and complete. Once onConnectSourceShape completes concat
             * will take data from the second input echoFlowShape which is nothing but data
             * from the tcp connection.
             */
            builder.from(onConnectSourceShape).toFanIn(concatShape); // onConnectSourceShape -> concatShape
            builder.from(echoFlowShape).toFanIn(concatShape); // echoFlowShape -> concatShape
            return FlowShape.of(echoFlowShape.in(), concatShape.out());

        }));
        final Sink<IncomingConnection, CompletionStage<Done>> handler = Sink.foreach(conn -> {
            System.out.println("Client connected from: " + conn.remoteAddress());
            // conn.handleWith(serverLogic.via(Flow.<ByteString>create()), actorSystem);
            conn.handleWith(
                    serverLogic.via(Flow.<ByteString>create()).watchTermination((prevMatValue, completionStage) -> {
                        completionStage.whenComplete((done, exc) -> {
                            connectionCount--;
                            System.out.println("Watch Termination called. Connection count:" + connectionCount);
                            if (done != null) {

                                System.out.println("The stream materialized " + prevMatValue.toString());
                            } else

                                System.out.println(exc.getMessage());
                        });
                        return prevMatValue;
                    }), 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));

                }));

        Source<IncomingConnection, CompletionStage<ServerBinding>> source = Tcp.get(actorSystem).bind("127.0.0.1",
                8888);

        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 {
        SimpleStream05.run();

    }

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM