rename akka-docs dir to docs (#62)

This commit is contained in:
PJ Fanning 2022-12-02 10:49:40 +01:00 committed by GitHub
parent 13dce0ec69
commit 708da8caec
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
1029 changed files with 2033 additions and 2039 deletions

View file

@ -0,0 +1,98 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.io;
import org.apache.pekko.actor.ActorSystem;
import org.apache.pekko.actor.AbstractActor;
// #imports
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.io.Inet;
import org.apache.pekko.io.Tcp;
import org.apache.pekko.io.TcpMessage;
import org.apache.pekko.io.TcpSO;
import org.apache.pekko.util.ByteString;
import java.time.Duration;
// #imports
public class IODocTest {
public static class Demo extends AbstractActor {
ActorRef connectionActor = null;
ActorRef listener = getSelf();
@Override
public Receive createReceive() {
return receiveBuilder()
.matchEquals(
"connect",
msg -> {
// #manager
final ActorRef tcp = Tcp.get(system).manager();
// #manager
// #connect
final InetSocketAddress remoteAddr = new InetSocketAddress("127.0.0.1", 12345);
tcp.tell(TcpMessage.connect(remoteAddr), getSelf());
// #connect
// #connect-with-options
final InetSocketAddress localAddr = new InetSocketAddress("127.0.0.1", 1234);
final List<Inet.SocketOption> options = new ArrayList<Inet.SocketOption>();
options.add(TcpSO.keepAlive(true));
Duration timeout = null;
tcp.tell(
TcpMessage.connect(remoteAddr, localAddr, options, timeout, false), getSelf());
// #connect-with-options
})
// #connected
.match(
Tcp.Connected.class,
conn -> {
connectionActor = getSender();
connectionActor.tell(TcpMessage.register(listener), getSelf());
})
// #connected
// #received
.match(
Tcp.Received.class,
recv -> {
final ByteString data = recv.data();
// and do something with the received data ...
})
.match(
Tcp.CommandFailed.class,
failed -> {
final Tcp.Command command = failed.cmd();
// react to failed connect, bind, write, etc.
})
.match(
Tcp.ConnectionClosed.class,
closed -> {
if (closed.isAborted()) {
// handle close reasons like this
}
})
// #received
.matchEquals(
"bind",
msg -> {
final ActorRef handler = getSelf();
// #bind
final ActorRef tcp = Tcp.get(system).manager();
final InetSocketAddress localAddr = new InetSocketAddress("127.0.0.1", 1234);
final List<Inet.SocketOption> options = new ArrayList<Inet.SocketOption>();
options.add(TcpSO.reuseAddress(true));
tcp.tell(TcpMessage.bind(handler, localAddr, 10, options, false), getSelf());
// #bind
})
.build();
}
}
static ActorSystem system;
// This is currently only a compilation test, nothing is run
}

View file

@ -0,0 +1,106 @@
/*
* Copyright (C) 2018-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.io;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.Props;
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.io.Inet;
import org.apache.pekko.io.Tcp;
import org.apache.pekko.io.TcpMessage;
import org.apache.pekko.util.ByteString;
import java.time.Duration;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
/** Copyright (C) 2009-2018 Lightbend Inc. <https://www.lightbend.com> */
public class JavaReadBackPressure {
public static class Listener extends AbstractActor {
ActorRef tcp;
ActorRef listener;
@Override
// #pull-accepting
public Receive createReceive() {
return receiveBuilder()
.match(
Tcp.Bound.class,
x -> {
listener = getSender();
// Accept connections one by one
listener.tell(TcpMessage.resumeAccepting(1), getSelf());
})
.match(
Tcp.Connected.class,
x -> {
ActorRef handler = getContext().actorOf(Props.create(PullEcho.class, getSender()));
getSender().tell(TcpMessage.register(handler), getSelf());
// Resume accepting connections
listener.tell(TcpMessage.resumeAccepting(1), getSelf());
})
.build();
}
// #pull-accepting
@Override
public void preStart() throws Exception {
// #pull-mode-bind
tcp = Tcp.get(getContext().getSystem()).manager();
final List<Inet.SocketOption> options = new ArrayList<Inet.SocketOption>();
tcp.tell(
TcpMessage.bind(getSelf(), new InetSocketAddress("localhost", 0), 100, options, true),
getSelf());
// #pull-mode-bind
}
private void demonstrateConnect() {
// #pull-mode-connect
final List<Inet.SocketOption> options = new ArrayList<Inet.SocketOption>();
Duration timeout = null;
tcp.tell(
TcpMessage.connect(
new InetSocketAddress("localhost", 3000), null, options, timeout, true),
getSelf());
// #pull-mode-connect
}
}
public static class Ack implements Tcp.Event {}
public static class PullEcho extends AbstractActor {
final ActorRef connection;
public PullEcho(ActorRef connection) {
this.connection = connection;
}
// #pull-reading-echo
@Override
public void preStart() throws Exception {
connection.tell(TcpMessage.resumeReading(), getSelf());
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(
Tcp.Received.class,
message -> {
ByteString data = message.data();
connection.tell(TcpMessage.write(data, new Ack()), getSelf());
})
.match(
Ack.class,
message -> {
connection.tell(TcpMessage.resumeReading(), getSelf());
})
.build();
}
// #pull-reading-echo
}
}

View file

@ -0,0 +1,135 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.io;
// #imports
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.event.Logging;
import org.apache.pekko.event.LoggingAdapter;
import org.apache.pekko.io.Inet;
import org.apache.pekko.io.Udp;
import org.apache.pekko.io.UdpMessage;
import org.apache.pekko.util.ByteString;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.StandardProtocolFamily;
import java.net.DatagramSocket;
import java.nio.channels.DatagramChannel;
import java.util.ArrayList;
import java.util.List;
// #imports
public class JavaUdpMulticast {
// #inet6-protocol-family
public static class Inet6ProtocolFamily extends Inet.DatagramChannelCreator {
@Override
public DatagramChannel create() throws Exception {
return DatagramChannel.open(StandardProtocolFamily.INET6);
}
}
// #inet6-protocol-family
// #multicast-group
public static class MulticastGroup extends Inet.AbstractSocketOptionV2 {
private String address;
private String interf;
public MulticastGroup(String address, String interf) {
this.address = address;
this.interf = interf;
}
@Override
public void afterBind(DatagramSocket s) {
try {
InetAddress group = InetAddress.getByName(address);
NetworkInterface networkInterface = NetworkInterface.getByName(interf);
s.getChannel().join(group, networkInterface);
} catch (Exception ex) {
System.out.println("Unable to join multicast group.");
}
}
}
// #multicast-group
public static class Listener extends AbstractActor {
LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);
ActorRef sink;
public Listener(String iface, String group, Integer port, ActorRef sink) {
this.sink = sink;
// #bind
List<Inet.SocketOption> options = new ArrayList<>();
options.add(new Inet6ProtocolFamily());
options.add(new MulticastGroup(group, iface));
final ActorRef mgr = Udp.get(getContext().getSystem()).getManager();
// listen for datagrams on this address
InetSocketAddress endpoint = new InetSocketAddress(port);
mgr.tell(UdpMessage.bind(getSelf(), endpoint, options), getSelf());
// #bind
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(
Udp.Bound.class,
bound -> {
log.info("Bound to {}", bound.localAddress());
sink.tell(bound, getSelf());
})
.match(
Udp.Received.class,
received -> {
final String txt = received.data().decodeString("utf-8");
log.info("Received '{}' from {}", txt, received.sender());
sink.tell(txt, getSelf());
})
.build();
}
}
public static class Sender extends AbstractActor {
LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);
String iface;
String group;
Integer port;
String message;
public Sender(String iface, String group, Integer port, String msg) {
this.iface = iface;
this.group = group;
this.port = port;
this.message = msg;
List<Inet.SocketOption> options = new ArrayList<>();
options.add(new Inet6ProtocolFamily());
final ActorRef mgr = Udp.get(getContext().getSystem()).getManager();
mgr.tell(UdpMessage.simpleSender(options), getSelf());
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(
Udp.SimpleSenderReady.class,
x -> {
InetSocketAddress remote = new InetSocketAddress(group + "%" + iface, port);
log.info("Sending message to " + remote);
getSender()
.tell(UdpMessage.send(ByteString.fromString(message), remote), getSelf());
})
.build();
}
}
}

View file

@ -0,0 +1,116 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.io;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.ActorSystem;
import org.apache.pekko.actor.Props;
import org.apache.pekko.io.Udp;
import org.apache.pekko.testkit.SocketUtil;
import jdocs.AbstractJavaTest;
import org.apache.pekko.testkit.javadsl.TestKit;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.*;
public class JavaUdpMulticastTest extends AbstractJavaTest {
static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create("JavaUdpMulticastTest");
}
@Test
public void testUdpMulticast() throws Exception {
new TestKit(system) {
{
List<NetworkInterface> ipv6Ifaces = new ArrayList<>();
for (Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
interfaces.hasMoreElements(); ) {
NetworkInterface interf = interfaces.nextElement();
if (interf.isUp() && interf.supportsMulticast()) {
for (Enumeration<InetAddress> addresses = interf.getInetAddresses();
addresses.hasMoreElements(); ) {
InetAddress address = addresses.nextElement();
if (address instanceof Inet6Address) {
ipv6Ifaces.add(interf);
}
}
}
}
if (ipv6Ifaces.isEmpty()) {
system
.log()
.info(
"JavaUdpMulticastTest skipped since no ipv6 interface supporting multicast could be found");
} else {
// lots of problems with choosing the wrong interface for this test depending
// on the platform (awsdl0 can't be used on OSX, docker[0-9] can't be used in a docker
// machine etc.)
// therefore: try hard to find an interface that _does_ work, and only fail if there was
// any potentially
// working interfaces but all failed
for (Iterator<NetworkInterface> interfaceIterator = ipv6Ifaces.iterator();
interfaceIterator.hasNext(); ) {
NetworkInterface ipv6Iface = interfaceIterator.next();
// host assigned link local multicast address
// https://www.rfc-editor.org/rfc/rfc3307#section-4.3.2
// generate a random 32 bit multicast address with the high order bit set
final String randomAddress =
Long.toHexString(((long) Math.abs(new Random().nextInt())) | (1L << 31))
.toUpperCase();
final StringBuilder groupBuilder = new StringBuilder("FF02:");
for (int i = 0; i < 2; i += 1) {
groupBuilder.append(":");
groupBuilder.append(randomAddress.subSequence(i * 4, i * 4 + 4));
}
final String group = groupBuilder.toString();
final Integer port = SocketUtil.temporaryUdpIpv6Port(ipv6Iface);
final String msg = "ohi";
final ActorRef sink = getRef();
final String iface = ipv6Iface.getName();
final ActorRef listener =
system.actorOf(
Props.create(JavaUdpMulticast.Listener.class, iface, group, port, sink));
try {
expectMsgClass(Udp.Bound.class);
final ActorRef sender =
system.actorOf(
Props.create(JavaUdpMulticast.Sender.class, iface, group, port, msg));
expectMsgEquals(msg);
// success with one interface is enough
break;
} catch (AssertionError ex) {
if (!interfaceIterator.hasNext()) throw ex;
else {
system.log().info("Failed to run test on interface {}", ipv6Iface.getDisplayName());
}
} finally {
// unbind
system.stop(listener);
}
}
}
}
};
}
@AfterClass
public static void tearDown() {
TestKit.shutdownActorSystem(system);
system = null;
}
}

View file

@ -0,0 +1,96 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.io;
import org.apache.pekko.japi.pf.ReceiveBuilder;
import org.junit.Test;
import org.apache.pekko.actor.ActorSystem;
import org.apache.pekko.actor.AbstractActor;
// #imports
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.io.Inet;
import org.apache.pekko.io.UdpConnected;
import org.apache.pekko.io.UdpConnectedMessage;
import org.apache.pekko.io.UdpSO;
import org.apache.pekko.util.ByteString;
import static org.apache.pekko.util.ByteString.emptyByteString;
// #imports
public class UdpConnectedDocTest {
public static class Demo extends AbstractActor {
ActorRef connectionActor = null;
ActorRef handler = getSelf();
ActorSystem system = getContext().getSystem();
@Override
public Receive createReceive() {
ReceiveBuilder builder = receiveBuilder();
builder.matchEquals(
"connect",
message -> {
// #manager
final ActorRef udp = UdpConnected.get(system).manager();
// #manager
// #connect
final InetSocketAddress remoteAddr = new InetSocketAddress("127.0.0.1", 12345);
udp.tell(UdpConnectedMessage.connect(handler, remoteAddr), getSelf());
// #connect
// #connect-with-options
final InetSocketAddress localAddr = new InetSocketAddress("127.0.0.1", 1234);
final List<Inet.SocketOption> options = new ArrayList<Inet.SocketOption>();
options.add(UdpSO.broadcast(true));
udp.tell(
UdpConnectedMessage.connect(handler, remoteAddr, localAddr, options), getSelf());
// #connect-with-options
});
// #connected
builder.match(
UdpConnected.Connected.class,
conn -> {
connectionActor = getSender(); // Save the worker ref for later use
});
// #connected
// #received
builder
.match(
UdpConnected.Received.class,
recv -> {
final ByteString data = recv.data();
// and do something with the received data ...
})
.match(
UdpConnected.CommandFailed.class,
failed -> {
final UdpConnected.Command command = failed.cmd();
// react to failed connect, etc.
})
.match(
UdpConnected.Disconnected.class,
x -> {
// do something on disconnect
});
// #received
builder.matchEquals(
"send",
x -> {
ByteString data = emptyByteString();
// #send
connectionActor.tell(UdpConnectedMessage.send(data), getSelf());
// #send
});
return builder.build();
}
}
@Test
public void demonstrateConnect() {}
}

View file

@ -0,0 +1,181 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.io;
// #imports
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.PoisonPill;
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.io.Udp;
import org.apache.pekko.io.UdpConnected;
import org.apache.pekko.io.UdpConnectedMessage;
import org.apache.pekko.io.UdpMessage;
import org.apache.pekko.util.ByteString;
import java.net.InetSocketAddress;
// #imports
public class UdpDocTest {
// #sender
public static class SimpleSender extends AbstractActor {
final InetSocketAddress remote;
public SimpleSender(InetSocketAddress remote) {
this.remote = remote;
// request creation of a SimpleSender
final ActorRef mgr = Udp.get(getContext().getSystem()).getManager();
mgr.tell(UdpMessage.simpleSender(), getSelf());
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(
Udp.SimpleSenderReady.class,
message -> {
getContext().become(ready(getSender()));
// #sender
getSender()
.tell(UdpMessage.send(ByteString.fromString("hello"), remote), getSelf());
// #sender
})
.build();
}
private Receive ready(final ActorRef send) {
return receiveBuilder()
.match(
String.class,
message -> {
send.tell(UdpMessage.send(ByteString.fromString(message), remote), getSelf());
// #sender
if (message.equals("world")) {
send.tell(PoisonPill.getInstance(), getSelf());
}
// #sender
})
.build();
}
}
// #sender
// #listener
public static class Listener extends AbstractActor {
final ActorRef nextActor;
public Listener(ActorRef nextActor) {
this.nextActor = nextActor;
// request creation of a bound listen socket
final ActorRef mgr = Udp.get(getContext().getSystem()).getManager();
mgr.tell(UdpMessage.bind(getSelf(), new InetSocketAddress("localhost", 0)), getSelf());
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(
Udp.Bound.class,
bound -> {
// #listener
nextActor.tell(bound.localAddress(), getSender());
// #listener
getContext().become(ready(getSender()));
})
.build();
}
private Receive ready(final ActorRef socket) {
return receiveBuilder()
.match(
Udp.Received.class,
r -> {
// echo server example: send back the data
socket.tell(UdpMessage.send(r.data(), r.sender()), getSelf());
// or do some processing and forward it on
final Object processed = // parse data etc., e.g. using PipelineStage
// #listener
r.data().utf8String();
// #listener
nextActor.tell(processed, getSelf());
})
.matchEquals(
UdpMessage.unbind(),
message -> {
socket.tell(message, getSelf());
})
.match(
Udp.Unbound.class,
message -> {
getContext().stop(getSelf());
})
.build();
}
}
// #listener
// #connected
public static class Connected extends AbstractActor {
final InetSocketAddress remote;
public Connected(InetSocketAddress remote) {
this.remote = remote;
// create a restricted a.k.a. connected socket
final ActorRef mgr = UdpConnected.get(getContext().getSystem()).getManager();
mgr.tell(UdpConnectedMessage.connect(getSelf(), remote), getSelf());
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(
UdpConnected.Connected.class,
message -> {
getContext().become(ready(getSender()));
// #connected
getSender()
.tell(UdpConnectedMessage.send(ByteString.fromString("hello")), getSelf());
// #connected
})
.build();
}
private Receive ready(final ActorRef connection) {
return receiveBuilder()
.match(
UdpConnected.Received.class,
r -> {
// process data, send it on, etc.
// #connected
if (r.data().utf8String().equals("hello")) {
connection.tell(
UdpConnectedMessage.send(ByteString.fromString("world")), getSelf());
}
// #connected
})
.match(
String.class,
str -> {
connection.tell(UdpConnectedMessage.send(ByteString.fromString(str)), getSelf());
})
.matchEquals(
UdpConnectedMessage.disconnect(),
message -> {
connection.tell(message, getSelf());
})
.match(
UdpConnected.Disconnected.class,
x -> {
getContext().stop(getSelf());
})
.build();
}
}
// #connected
}

View file

@ -0,0 +1,259 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.io.japi;
import java.net.InetSocketAddress;
import java.util.LinkedList;
import java.util.Queue;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.event.Logging;
import org.apache.pekko.event.LoggingAdapter;
import org.apache.pekko.io.Tcp.CommandFailed;
import org.apache.pekko.io.Tcp.ConnectionClosed;
import org.apache.pekko.io.Tcp.Event;
import org.apache.pekko.io.Tcp.Received;
import org.apache.pekko.io.Tcp.Write;
import org.apache.pekko.io.Tcp.WritingResumed;
import org.apache.pekko.io.TcpMessage;
import org.apache.pekko.util.ByteString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
// #echo-handler
public class EchoHandler extends AbstractActor {
final LoggingAdapter log = Logging.getLogger(getContext().getSystem(), getSelf());
final ActorRef connection;
final InetSocketAddress remote;
public static final long MAX_STORED = 100000000;
public static final long HIGH_WATERMARK = MAX_STORED * 5 / 10;
public static final long LOW_WATERMARK = MAX_STORED * 2 / 10;
private long transferred;
private int storageOffset = 0;
private long stored = 0;
private Queue<ByteString> storage = new LinkedList<>();
private boolean suspended = false;
private static class Ack implements Event {
public final int ack;
public Ack(int ack) {
this.ack = ack;
}
}
public EchoHandler(ActorRef connection, InetSocketAddress remote) {
this.connection = connection;
this.remote = remote;
writing = writing();
// sign death pact: this actor stops when the connection is closed
getContext().watch(connection);
// start out in optimistic write-through mode
getContext().become(writing);
}
@Override
public Receive createReceive() {
return writing;
}
private final Receive writing;
private Receive writing() {
return receiveBuilder()
.match(
Received.class,
msg -> {
final ByteString data = msg.data();
connection.tell(TcpMessage.write(data, new Ack(currentOffset())), getSelf());
buffer(data);
})
.match(
Integer.class,
msg -> {
acknowledge(msg);
})
.match(
CommandFailed.class,
msg -> {
final Write w = (Write) msg.cmd();
connection.tell(TcpMessage.resumeWriting(), getSelf());
getContext().become(buffering((Ack) w.ack()));
})
.match(
ConnectionClosed.class,
msg -> {
if (msg.isPeerClosed()) {
if (storage.isEmpty()) {
getContext().stop(getSelf());
} else {
getContext().become(closing());
}
}
})
.build();
}
// #buffering
static final class BufferingState {
int toAck = 10;
boolean peerClosed = false;
}
protected Receive buffering(final Ack nack) {
final BufferingState state = new BufferingState();
return receiveBuilder()
.match(
Received.class,
msg -> {
buffer(msg.data());
})
.match(
WritingResumed.class,
msg -> {
writeFirst();
})
.match(
ConnectionClosed.class,
msg -> {
if (msg.isPeerClosed()) state.peerClosed = true;
else getContext().stop(getSelf());
})
.match(
Integer.class,
ack -> {
acknowledge(ack);
if (ack >= nack.ack) {
// otherwise it was the ack of the last successful write
if (storage.isEmpty()) {
if (state.peerClosed) getContext().stop(getSelf());
else getContext().become(writing);
} else {
if (state.toAck > 0) {
// stay in ACK-based mode for a short while
writeFirst();
--state.toAck;
} else {
// then return to NACK-based again
writeAll();
if (state.peerClosed) getContext().become(closing());
else getContext().become(writing);
}
}
}
})
.build();
}
// #buffering
// #closing
protected Receive closing() {
return receiveBuilder()
.match(
CommandFailed.class,
msg -> {
// the command can only have been a Write
connection.tell(TcpMessage.resumeWriting(), getSelf());
getContext().become(closeResend(), false);
})
.match(
Integer.class,
msg -> {
acknowledge(msg);
if (storage.isEmpty()) getContext().stop(getSelf());
})
.build();
}
protected Receive closeResend() {
return receiveBuilder()
.match(
WritingResumed.class,
msg -> {
writeAll();
getContext().unbecome();
})
.match(
Integer.class,
msg -> {
acknowledge(msg);
})
.build();
}
// #closing
// #storage-omitted
@Override
public void postStop() {
log.info("transferred {} bytes from/to [{}]", transferred, remote);
}
// #helpers
protected void buffer(ByteString data) {
storage.add(data);
stored += data.size();
if (stored > MAX_STORED) {
log.warning("drop connection to [{}] (buffer overrun)", remote);
getContext().stop(getSelf());
} else if (stored > HIGH_WATERMARK) {
log.debug("suspending reading at {}", currentOffset());
connection.tell(TcpMessage.suspendReading(), getSelf());
suspended = true;
}
}
protected void acknowledge(int ack) {
assertEquals(storageOffset, ack);
assertFalse(storage.isEmpty());
final ByteString acked = storage.remove();
stored -= acked.size();
transferred += acked.size();
storageOffset += 1;
if (suspended && stored < LOW_WATERMARK) {
log.debug("resuming reading");
connection.tell(TcpMessage.resumeReading(), getSelf());
suspended = false;
}
}
// #helpers
protected int currentOffset() {
return storageOffset + storage.size();
}
protected void writeAll() {
int i = 0;
for (ByteString data : storage) {
connection.tell(TcpMessage.write(data, new Ack(storageOffset + i++)), getSelf());
}
}
protected void writeFirst() {
connection.tell(TcpMessage.write(storage.peek(), new Ack(storageOffset)), getSelf());
}
// #storage-omitted
}
// #echo-handler

View file

@ -0,0 +1,87 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.io.japi;
import java.net.InetSocketAddress;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.Props;
import org.apache.pekko.actor.SupervisorStrategy;
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.event.Logging;
import org.apache.pekko.event.LoggingAdapter;
import org.apache.pekko.io.Tcp;
import org.apache.pekko.io.Tcp.Bind;
import org.apache.pekko.io.Tcp.Bound;
import org.apache.pekko.io.Tcp.Connected;
import org.apache.pekko.io.TcpMessage;
public class EchoManager extends AbstractActor {
final LoggingAdapter log = Logging.getLogger(getContext().getSystem(), getSelf());
final Class<?> handlerClass;
public EchoManager(Class<?> handlerClass) {
this.handlerClass = handlerClass;
}
@Override
public SupervisorStrategy supervisorStrategy() {
return SupervisorStrategy.stoppingStrategy();
}
@Override
public void preStart() throws Exception {
// #manager
final ActorRef tcpManager = Tcp.get(getContext().getSystem()).manager();
// #manager
tcpManager.tell(
TcpMessage.bind(getSelf(), new InetSocketAddress("localhost", 0), 100), getSelf());
}
@Override
public void postRestart(Throwable arg0) throws Exception {
// do not restart
getContext().stop(getSelf());
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(
Bound.class,
msg -> {
log.info("listening on [{}]", msg.localAddress());
})
.match(
Tcp.CommandFailed.class,
failed -> {
if (failed.cmd() instanceof Bind) {
log.warning("cannot bind to [{}]", ((Bind) failed.cmd()).localAddress());
getContext().stop(getSelf());
} else {
log.warning("unknown command failed [{}]", failed.cmd());
}
})
.match(
Connected.class,
conn -> {
log.info("received connection from [{}]", conn.remoteAddress());
final ActorRef connection = getSender();
final ActorRef handler =
getContext()
.actorOf(Props.create(handlerClass, connection, conn.remoteAddress()));
// #echo-manager
connection.tell(
TcpMessage.register(
handler, true, // <-- keepOpenOnPeerClosed flag
true),
getSelf());
// #echo-manager
})
.build();
}
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.io.japi;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.ActorSystem;
import org.apache.pekko.actor.Props;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
public class EchoServer {
public static void main(String[] args) throws InterruptedException {
final Config config = ConfigFactory.parseString("akka.loglevel=DEBUG");
final ActorSystem system = ActorSystem.create("EchoServer", config);
try {
final CountDownLatch latch = new CountDownLatch(1);
final ActorRef watcher = system.actorOf(Props.create(Watcher.class, latch), "watcher");
final ActorRef nackServer =
system.actorOf(Props.create(EchoManager.class, EchoHandler.class), "nack");
final ActorRef ackServer =
system.actorOf(Props.create(EchoManager.class, SimpleEchoHandler.class), "ack");
watcher.tell(nackServer, ActorRef.noSender());
watcher.tell(ackServer, ActorRef.noSender());
latch.await(10, TimeUnit.MINUTES);
} finally {
system.terminate();
}
}
}

View file

@ -0,0 +1,205 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.io.japi;
import org.apache.pekko.testkit.AkkaJUnitActorSystemResource;
import jdocs.AbstractJavaTest;
import org.apache.pekko.testkit.javadsl.TestKit;
import org.junit.ClassRule;
import org.junit.Test;
// #imports
import java.net.InetSocketAddress;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.ActorSystem;
import org.apache.pekko.actor.Props;
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.io.Tcp;
import org.apache.pekko.io.Tcp.Bound;
import org.apache.pekko.io.Tcp.CommandFailed;
import org.apache.pekko.io.Tcp.Connected;
import org.apache.pekko.io.Tcp.ConnectionClosed;
import org.apache.pekko.io.Tcp.Received;
import org.apache.pekko.io.TcpMessage;
import org.apache.pekko.util.ByteString;
// #imports
import org.apache.pekko.testkit.AkkaSpec;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class IODocTest extends AbstractJavaTest {
public
// #server
static class Server extends AbstractActor {
final ActorRef manager;
public Server(ActorRef manager) {
this.manager = manager;
}
public static Props props(ActorRef manager) {
return Props.create(Server.class, manager);
}
@Override
public void preStart() throws Exception {
final ActorRef tcp = Tcp.get(getContext().getSystem()).manager();
tcp.tell(TcpMessage.bind(getSelf(), new InetSocketAddress("localhost", 0), 100), getSelf());
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(
Bound.class,
msg -> {
manager.tell(msg, getSelf());
})
.match(
CommandFailed.class,
msg -> {
getContext().stop(getSelf());
})
.match(
Connected.class,
conn -> {
manager.tell(conn, getSelf());
final ActorRef handler =
getContext().actorOf(Props.create(SimplisticHandler.class));
getSender().tell(TcpMessage.register(handler), getSelf());
})
.build();
}
}
// #server
public
// #simplistic-handler
static class SimplisticHandler extends AbstractActor {
@Override
public Receive createReceive() {
return receiveBuilder()
.match(
Received.class,
msg -> {
final ByteString data = msg.data();
System.out.println(data);
getSender().tell(TcpMessage.write(data), getSelf());
})
.match(
ConnectionClosed.class,
msg -> {
getContext().stop(getSelf());
})
.build();
}
}
// #simplistic-handler
public
// #client
static class Client extends AbstractActor {
final InetSocketAddress remote;
final ActorRef listener;
public static Props props(InetSocketAddress remote, ActorRef listener) {
return Props.create(Client.class, remote, listener);
}
public Client(InetSocketAddress remote, ActorRef listener) {
this.remote = remote;
this.listener = listener;
final ActorRef tcp = Tcp.get(getContext().getSystem()).manager();
tcp.tell(TcpMessage.connect(remote), getSelf());
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(
CommandFailed.class,
msg -> {
listener.tell("failed", getSelf());
getContext().stop(getSelf());
})
.match(
Connected.class,
msg -> {
listener.tell(msg, getSelf());
getSender().tell(TcpMessage.register(getSelf()), getSelf());
getContext().become(connected(getSender()));
})
.build();
}
private Receive connected(final ActorRef connection) {
return receiveBuilder()
.match(
ByteString.class,
msg -> {
connection.tell(TcpMessage.write((ByteString) msg), getSelf());
})
.match(
CommandFailed.class,
msg -> {
// OS kernel socket buffer was full
})
.match(
Received.class,
msg -> {
listener.tell(msg.data(), getSelf());
})
.matchEquals(
"close",
msg -> {
connection.tell(TcpMessage.close(), getSelf());
})
.match(
ConnectionClosed.class,
msg -> {
getContext().stop(getSelf());
})
.build();
}
}
// #client
@ClassRule
public static AkkaJUnitActorSystemResource actorSystemResource =
new AkkaJUnitActorSystemResource("IODocTest", AkkaSpec.testConf());
private final ActorSystem system = actorSystemResource.getSystem();
@Test
public void testConnection() {
new TestKit(system) {
{
@SuppressWarnings("unused")
final ActorRef server = system.actorOf(Server.props(getRef()), "server1");
final InetSocketAddress listen = expectMsgClass(Bound.class).localAddress();
final ActorRef client = system.actorOf(Client.props(listen, getRef()), "client1");
final Connected c1 = expectMsgClass(Connected.class);
final Connected c2 = expectMsgClass(Connected.class);
assertTrue(c1.localAddress().equals(c2.remoteAddress()));
assertTrue(c2.localAddress().equals(c1.remoteAddress()));
client.tell(ByteString.fromString("hello"), getRef());
final ByteString reply = expectMsgClass(ByteString.class);
assertEquals("hello", reply.utf8String());
watch(client);
client.tell("close", getRef());
expectTerminated(client);
}
};
}
}

View file

@ -0,0 +1,44 @@
/*
* Copyright (C) 2013-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.io.japi;
// #message
public class Message {
public static class Person {
private final String first;
private final String last;
public Person(String first, String last) {
this.first = first;
this.last = last;
}
public String getFirst() {
return first;
}
public String getLast() {
return last;
}
}
private final Person[] persons;
private final double[] happinessCurve;
public Message(Person[] persons, double[] happinessCurve) {
this.persons = persons;
this.happinessCurve = happinessCurve;
}
public Person[] getPersons() {
return persons;
}
public double[] getHappinessCurve() {
return happinessCurve;
}
}
// #message

View file

@ -0,0 +1,141 @@
/*
* Copyright (C) 2009-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.io.japi;
import java.net.InetSocketAddress;
import java.util.LinkedList;
import java.util.Queue;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.AbstractActor;
import org.apache.pekko.event.Logging;
import org.apache.pekko.event.LoggingAdapter;
import org.apache.pekko.io.Tcp.ConnectionClosed;
import org.apache.pekko.io.Tcp.Event;
import org.apache.pekko.io.Tcp.Received;
import org.apache.pekko.io.TcpMessage;
import org.apache.pekko.util.ByteString;
// #simple-echo-handler
public class SimpleEchoHandler extends AbstractActor {
final LoggingAdapter log = Logging.getLogger(getContext().getSystem(), getSelf());
final ActorRef connection;
final InetSocketAddress remote;
public static final long maxStored = 100000000;
public static final long highWatermark = maxStored * 5 / 10;
public static final long lowWatermark = maxStored * 2 / 10;
public SimpleEchoHandler(ActorRef connection, InetSocketAddress remote) {
this.connection = connection;
this.remote = remote;
// sign death pact: this actor stops when the connection is closed
getContext().watch(connection);
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(
Received.class,
msg -> {
final ByteString data = msg.data();
buffer(data);
connection.tell(TcpMessage.write(data, ACK), getSelf());
// now switch behavior to waiting for acknowledgement
getContext().become(buffering(), false);
})
.match(
ConnectionClosed.class,
msg -> {
getContext().stop(getSelf());
})
.build();
}
private Receive buffering() {
return receiveBuilder()
.match(
Received.class,
msg -> {
buffer(msg.data());
})
.match(
Event.class,
msg -> msg == ACK,
msg -> {
acknowledge();
})
.match(
ConnectionClosed.class,
msg -> {
if (msg.isPeerClosed()) {
closing = true;
} else {
// could also be ErrorClosed, in which case we just give up
getContext().stop(getSelf());
}
})
.build();
}
// #storage-omitted
public void postStop() {
log.info("transferred {} bytes from/to [{}]", transferred, remote);
}
private long transferred;
private long stored = 0;
private Queue<ByteString> storage = new LinkedList<>();
private boolean suspended = false;
private boolean closing = false;
private final Event ACK = new Event() {};
// #simple-helpers
protected void buffer(ByteString data) {
storage.add(data);
stored += data.size();
if (stored > maxStored) {
log.warning("drop connection to [{}] (buffer overrun)", remote);
getContext().stop(getSelf());
} else if (stored > highWatermark) {
log.debug("suspending reading");
connection.tell(TcpMessage.suspendReading(), getSelf());
suspended = true;
}
}
protected void acknowledge() {
final ByteString acked = storage.remove();
stored -= acked.size();
transferred += acked.size();
if (suspended && stored < lowWatermark) {
log.debug("resuming reading");
connection.tell(TcpMessage.resumeReading(), getSelf());
suspended = false;
}
if (storage.isEmpty()) {
if (closing) {
getContext().stop(getSelf());
} else {
getContext().unbecome();
}
} else {
connection.tell(TcpMessage.write(storage.peek(), ACK), getSelf());
}
}
// #simple-helpers
// #storage-omitted
}
// #simple-echo-handler

View file

@ -0,0 +1,45 @@
/*
* Copyright (C) 2018-2022 Lightbend Inc. <https://www.lightbend.com>
*/
package jdocs.io.japi;
import java.util.concurrent.CountDownLatch;
import org.apache.pekko.actor.ActorRef;
import org.apache.pekko.actor.Terminated;
import org.apache.pekko.actor.AbstractActor;
public class Watcher extends AbstractActor {
public static class Watch {
final ActorRef target;
public Watch(ActorRef target) {
this.target = target;
}
}
final CountDownLatch latch;
public Watcher(CountDownLatch latch) {
this.latch = latch;
}
@Override
public Receive createReceive() {
return receiveBuilder()
.match(
Watch.class,
msg -> {
getContext().watch(msg.target);
})
.match(
Terminated.class,
msg -> {
latch.countDown();
if (latch.getCount() == 0) getContext().stop(getSelf());
})
.build();
}
}