/******************************************************************************* * Copyright 2015 Klaus Pfeiffer <klaus@allpiper.com> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ package com.jfastnet; import com.jfastnet.idprovider.ClientIdReliableModeIdProvider; import com.jfastnet.idprovider.ReliableModeIdProvider; import com.jfastnet.messages.Message; import com.jfastnet.messages.MessagePart; import com.jfastnet.processors.MessageLogProcessor; import com.jfastnet.processors.ReliableModeSequenceProcessor; import lombok.ToString; import lombok.extern.slf4j.Slf4j; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.Ignore; import org.junit.Test; import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.concurrent.atomic.AtomicLong; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; /** @author Klaus Pfeiffer - klaus@allpiper.com */ @Slf4j public class ServerTest extends AbstractTest { public static final int TEST_GLOBAL_TIMEOUT = 30; public static int received = 0; private static BigMessage receivedBigMessage; static class DefaultUnreliableMessage extends Message { @Override public ReliableMode getReliableMode() { return ReliableMode.UNRELIABLE; } @Override public void process(Object context) { received++; } } static class DefaultUnreliableMessageSpecific extends Message { public int value; public DefaultUnreliableMessageSpecific() {} public DefaultUnreliableMessageSpecific(int value) {this.value = value;} @Override public ReliableMode getReliableMode() {return ReliableMode.UNRELIABLE;} @Override public void process(Object context) {received++;} } static class DefaultReliableSeqMessage extends Message { @Override public ReliableMode getReliableMode() { return ReliableMode.SEQUENCE_NUMBER; } @Override public void process(Object context) { received++; } } @ToString(callSuper = true) static class DefaultReliableSeqMessageSpecific extends Message { public int value; public DefaultReliableSeqMessageSpecific() {} public DefaultReliableSeqMessageSpecific(int value) {this.value = value;} @Override public ReliableMode getReliableMode() {return ReliableMode.SEQUENCE_NUMBER;} @Override public void process(Object context) {received++;} } static class BigMessage extends Message { String s; public BigMessage() { this(50); } public BigMessage(int size) { Random r = new Random(System.currentTimeMillis()); StringBuilder sb = new StringBuilder(); sb.append("BEGIN__"); for (int i = 0; i < size; i++) { for (int j = 0; j < 15; j++) { sb.append((char) r.nextInt()); } } // 1 char UTF-8 encoded in ASCII space consumes 1 byte of memory // payload size = 50 * 30 byte = 1500 byte sb.append("__END"); // + 3 = 1503 byte s = sb.toString(); } @Override public void process(Object context) { log.info("Big message received."); receivedBigMessage = this; received++; } } public void reset() { received = 0; receivedBigMessage = null; } @Test public void testSequence() { reset(); Config newServerConfig = newServerConfig(); newServerConfig.setExternalReceiver(message -> message.process(null)); Server server = new Server(newServerConfig) { @Override public boolean send(Message message) { return super.send(message); } @Override public boolean send(int clientId, Message message) { return super.send(clientId, message); } }; Client client1 = new Client(newClientConfig().setExternalReceiver(message -> { // is called after all the processor magic message.process(null); })); this.server = server; server.start(); clients = new ArrayList<>(); clients.add(client1); client1.setClientId(987); client1.start(); client1.blockingWaitUntilConnected(); assertThat(server.state.getClientStates().size(), is(1)); DefaultUnreliableMessage msg1 = new DefaultUnreliableMessage(); client1.send(msg1); log.info("Last message id: {}", msg1.getMsgId()); assertThat(msg1.getMsgId(), is(2L)); checkReceived(); log.info("Clear id and create new."); msg1.clearId(); client1.send(msg1); log.info("Last message id: {}", msg1.getMsgId()); assertThat(msg1.getMsgId(), is(3L)); checkReceived(); log.info("Send big message."); BigMessage bigMessage = new BigMessage(); String forLaterCheck = bigMessage.s; List<MessagePart> messageParts = MessagePart.createFromMessage(client1.state, 0, bigMessage, 1024); // 41 log.info("Parts: {}", messageParts.size()); // 15 + 1 messageParts.forEach(client1::send); waitFor(1500); checkReceived(); messageParts.forEach(client1::queue); assertThat("Received message not the same.", receivedBigMessage.s, is(equalTo(forLaterCheck))); } @Test public void testBigMessageQueuing() { reset(); start(1); log.info("Send big message."); BigMessage bigMessage = new BigMessage(); String forLaterCheck = bigMessage.s; List<MessagePart> messageParts = MessagePart.createFromMessage(client1.state, 0, bigMessage, 1024); // 41 log.info("Parts: {}", messageParts.size()); // 15 + 1 log.info("Test queued sending of big message"); messageParts.forEach(client1::queue); waitForCondition("Big message after queuing not received.", 3, () -> received == 1); checkReceived(); assertThat("Received message not the same.", receivedBigMessage.s, is(equalTo(forLaterCheck))); } @Test public void testAutoSplit() { reset(); start(1); log.info("Send big message."); BigMessage bigMessage = new BigMessage(); String forLaterCheck = bigMessage.s; client1.send(bigMessage); waitForCondition("Big message after sending not received.", 3, () -> received == 1); checkReceived(); assertThat("Received message not the same.", receivedBigMessage.s, is(equalTo(forLaterCheck))); } @Test public void testConnect() { reset(); start(8); tearDown(); start(8, newServerConfig(), () -> newClientDebugConfig(15)); tearDown(); start(8, newServerConfig(), () -> newClientDebugConfig(15)); } @Test public void testBigMessageAckQueuing() { reset(); start(1, newServerConfig(), () -> newClientDebugConfig(15)); BigMessage bigMessage; String forLaterCheck; List<MessagePart> messageParts; logBig("Send big message."); receivedBigMessage = null; bigMessage = new BigMessage(150); forLaterCheck = bigMessage.s; messageParts = MessagePart.createFromMessage(client1.state, 0, bigMessage, 1024, Message.ReliableMode.ACK_PACKET); log.info("Parts: {}", messageParts.size()); // 15 + 1 log.info("Test queued sending of big message"); messageParts.forEach(client1::queue); waitForCondition("Big message after queuing not received.", 3, () -> received == 1); checkReceived(); assertThat("Received message not the same.", receivedBigMessage.s, is(equalTo(forLaterCheck))); logBig("Send a smaller message"); receivedBigMessage = null; bigMessage = new BigMessage(50); forLaterCheck = bigMessage.s; messageParts = MessagePart.createFromMessage(client1.state, 0, bigMessage, 1024, Message.ReliableMode.ACK_PACKET); log.info("Parts: {}", messageParts.size()); // 15 + 1 log.info("Test queued sending of big message"); messageParts.forEach(client1::queue); waitForCondition("Big message after queuing not received.", 3, () -> received == 1); checkReceived(); assertThat("Received message not the same.", receivedBigMessage.s, is(equalTo(forLaterCheck))); logBig("Send not all packets of big message"); receivedBigMessage = null; bigMessage = new BigMessage(150); forLaterCheck = bigMessage.s; messageParts = MessagePart.createFromMessage(client1.state, 0, bigMessage, 1024, Message.ReliableMode.ACK_PACKET); log.info("Parts: {}", messageParts.size()); // 15 + 1 log.info("Test queued sending of big message"); for (int i = 0; i < messageParts.size(); i++) { MessagePart messagePart = messageParts.get(i); if (i % 2 == 0 || i == messageParts.size() - 1) { client1.queue(messagePart); } } waitFor(300); // logBig("Clear byte buffer."); // server.config.byteArrayBufferMap.clear(); logBig("Send big message after unsuccessful delivery on different id."); receivedBigMessage = null; received = 0; bigMessage = new BigMessage(150); forLaterCheck = bigMessage.s; messageParts = MessagePart.createFromMessage(client1.state, 1, bigMessage, 1024, Message.ReliableMode.ACK_PACKET); log.info("Parts: {}", messageParts.size()); // 15 + 1 log.info("Test queued sending of big message"); messageParts.forEach(client1::queue); waitForCondition("Big message after queuing not received.", 6, () -> received == 1, () -> "recv.: "+ received); checkReceived(); assertThat("Received message not the same.", receivedBigMessage.s, is(equalTo(forLaterCheck))); } @Test public void sendToMultipleClientsUnreliableTest() { reset(); start(4, newServerConfig(), () -> { Config config = newClientConfig(); config.getAdditionalConfig(MessageLogProcessor.ProcessorConfig.class).messageLogReceiveFilter = message -> true; // don't filter messages config.getAdditionalConfig(MessageLogProcessor.ProcessorConfig.class).messageLogSendFilter = message -> true; // don't filter messages return config; }); server.send(new DefaultUnreliableMessage()); waitForCondition("Not all messages received.", 1, () -> allClientsReceivedMessageTypeOf(DefaultUnreliableMessage.class)); server.send(new DefaultUnreliableMessageSpecific(1).setReceiverId(1)); server.send(new DefaultUnreliableMessageSpecific(2).setReceiverId(2)); server.send(new DefaultUnreliableMessageSpecific(3).setReceiverId(3)); server.send(new DefaultUnreliableMessageSpecific(4).setReceiverId(4)); waitForCondition("Not all messages received.", 1, () -> allClientsReceivedMessageTypeOf(DefaultUnreliableMessageSpecific.class)); assertThat(((DefaultUnreliableMessageSpecific) getLastReceivedMessage(0)).value, is(1)); assertThat(((DefaultUnreliableMessageSpecific) getLastReceivedMessage(1)).value, is(2)); assertThat(((DefaultUnreliableMessageSpecific) getLastReceivedMessage(2)).value, is(3)); assertThat(((DefaultUnreliableMessageSpecific) getLastReceivedMessage(3)).value, is(4)); } /** 50 % packet loss single client. */ @Test public void sendToClientReliableSeqTest() { reset(); start(1, () -> { Config config = newClientConfig(); config.idProviderClass = ClientIdReliableModeIdProvider.class; config.debug.enabled = true; config.debug.lostPacketsPercentage = 50; return config; }); logBig("Send broadcast messages to clients"); server.send(new DefaultReliableSeqMessage()); int timeoutInSeconds = TEST_GLOBAL_TIMEOUT; waitForCondition("Not all messages received.", timeoutInSeconds, () -> allClientsReceivedMessageTypeOf(DefaultReliableSeqMessage.class)); logBig("Send specific messages to all clients"); int value = 1; server.send(new DefaultReliableSeqMessageSpecific(value).setReceiverId(1)); waitForCondition("Not all messages received.", timeoutInSeconds, () -> allClientsReceivedMessageTypeOf(DefaultReliableSeqMessageSpecific.class)); assertThat(((DefaultReliableSeqMessageSpecific) getLastReceivedMessage(0)).value, is(value)); server.send(new DefaultReliableSeqMessageSpecific(2).setReceiverId(1)); waitForCondition("Not all messages received.", timeoutInSeconds, () -> getLastReceivedMessage(0) instanceof DefaultReliableSeqMessageSpecific && ((DefaultReliableSeqMessageSpecific) getLastReceivedMessage(0)).value == 2, () -> getLastReceivedMessage(0).toString()); server.send(new DefaultReliableSeqMessageSpecific(3).setReceiverId(1)); waitForCondition("Not all messages received.", timeoutInSeconds, () -> getLastReceivedMessage(0) instanceof DefaultReliableSeqMessageSpecific && ((DefaultReliableSeqMessageSpecific) getLastReceivedMessage(0)).value == 3, () -> getLastReceivedMessage(0).toString()); logBig("Send broadcast messages to clients"); server.send(new DefaultReliableSeqMessage()); waitForCondition("Not all messages received.", timeoutInSeconds, () -> allClientsReceivedMessageTypeOf(DefaultReliableSeqMessage.class)); } /** Sending to multiple clients with specific ids enabled. */ @Ignore("Sending with specific ids is not working reliably yet.") @Test public void sendToMultipleClientsSpecificReliableSeqTest() { reset(); start(8, () -> { Config config = newClientConfig(); config.idProviderClass = ClientIdReliableModeIdProvider.class; config.debug.enabled = true; config.debug.lostPacketsPercentage = 10; return config; }); logBig("Send broadcast messages to clients"); server.send(new DefaultReliableSeqMessage()); int timeoutInSeconds = TEST_GLOBAL_TIMEOUT; waitForCondition("Not all messages received.", timeoutInSeconds, () -> allClientsReceivedMessageTypeOf(DefaultReliableSeqMessage.class)); logBig("Send specific messages to all clients"); for (int i = 0; i < clients.size(); i++) { server.send(new DefaultReliableSeqMessageSpecific(i + 1).setReceiverId(i + 1)); } waitForCondition("Not all messages received.", timeoutInSeconds, () -> allClientsReceivedMessageTypeOf(DefaultReliableSeqMessageSpecific.class)); for (int i = 0; i < clients.size(); i++) { assertThat(((DefaultReliableSeqMessageSpecific) getLastReceivedMessage(i)).value, is(i + 1)); } logBig("Send broadcast messages to clients"); server.send(new DefaultReliableSeqMessage()); waitForCondition("Not all messages received.", timeoutInSeconds, () -> allClientsReceivedMessageTypeOf(DefaultReliableSeqMessage.class)); logBig("Send from clients to server"); for (int i = 0; i < clients.size(); i++) { Client client = clients.get(i); client.send(new DefaultReliableSeqMessageSpecific(10 + i)); } waitForCondition("Client message on server missing.", timeoutInSeconds, () -> { List<Message> tmpMsgs = getLastReceivedMessagesFromLog(DefaultReliableSeqMessageSpecific.class); for (int i = 0; i < clients.size(); i++) { final int finalI = i; if (tmpMsgs.stream().filter(message -> ((DefaultReliableSeqMessageSpecific) message).value == finalI + 10).count() == 0) { return false; } } return true; }); } /** Sending to multiple clients with specific ids disabled. */ @Test public void sendToMultipleClientsReliableSeqTest() { reset(); start(8, () -> { Config config = newClientConfig(); config.idProviderClass = ReliableModeIdProvider.class; config.debug.enabled = true; config.debug.lostPacketsPercentage = 10; return config; }); int timeoutInSeconds = TEST_GLOBAL_TIMEOUT; logBig("Send broadcast messages to clients"); server.send(new DefaultReliableSeqMessage()); waitForCondition("Not all messages received.", timeoutInSeconds, () -> allClientsReceivedMessageTypeOf(DefaultReliableSeqMessage.class)); logBig("Send from clients to server"); log.info("Wait for every client to send its message and be received from the server."); for (int i = 0; i < clients.size(); i++) { Client client = clients.get(i); client.send(new DefaultReliableSeqMessageSpecific(10 + i)); } waitForCondition("Client message on server missing.", timeoutInSeconds, () -> { List<Message> tmpMsgs = getLastReceivedMessagesFromLog(DefaultReliableSeqMessageSpecific.class); for (int i = 0; i < clients.size(); i++) { final int finalI = i; if (tmpMsgs.stream().filter(message -> ((DefaultReliableSeqMessageSpecific) message).value == finalI + 10).count() == 0) { return false; } } return true; }); logBig("Check re-connect of client"); AtomicLong atomicLong = client1.getState().getProcessorOf(ReliableModeSequenceProcessor.class).getLastMessageIdMap().get(0); assertThat(atomicLong, is(notNullValue())); long lastSeqIdFromServer = (long) atomicLong.get(); log.info("Last Seq-Id from server: {}", lastSeqIdFromServer); client1.stop(); clients.remove(client1); final int client1Id = client1.config.senderId; waitFor(100); Config config = newClientConfig(); config.setSenderId(client1Id); client1 = new Client(config); clients.add(client1); client1.start(); client1.blockingWaitUntilConnected(); AtomicLong atomicLong2 = client1.getState().getProcessorOf(ReliableModeSequenceProcessor.class).getLastMessageIdMap().get(0); assertThat(atomicLong2, is(notNullValue())); long lastSeqIdFromServer2 = atomicLong2.get(); log.info("Last Seq-Id from server: {}", lastSeqIdFromServer2); assertThat("Didn't retrieve current reliable sequence id from server.", lastSeqIdFromServer2, is(greaterThanOrEqualTo(lastSeqIdFromServer))); logBig("Send broadcast messages to clients"); server.send(new DefaultReliableSeqMessage()); waitForCondition("Not all messages received.", timeoutInSeconds, () -> allClientsReceivedMessageTypeOf(DefaultReliableSeqMessage.class)); } private void checkReceived() { waitFor(100); MatcherAssert.assertThat(received, Matchers.is(1)); received = 0; } private void checkReceived(int count) { waitFor(100); MatcherAssert.assertThat(received, Matchers.is(count)); received = 0; } private void checkNotReceived() { waitFor(100); MatcherAssert.assertThat(received, Matchers.is(0)); received = 0; } public void waitFor(int ms) { try { Thread.sleep(ms); } catch (InterruptedException e) { e.printStackTrace(); } } }