added possibility to use multiple bytes as separator
This commit is contained in:
parent
1dcf9dec7a
commit
63df33457f
@ -5,7 +5,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
group 'me.bvn13'
|
group 'me.bvn13'
|
||||||
version '1.2.2'
|
version '1.2.4'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
@ -63,6 +63,7 @@ public abstract class AbstractClientListener implements Runnable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads line (separated with '\n') from socket
|
* Reads line (separated with '\n') from socket
|
||||||
|
*
|
||||||
* @return the line read from socket
|
* @return the line read from socket
|
||||||
*/
|
*/
|
||||||
public String readLine() {
|
public String readLine() {
|
||||||
@ -78,17 +79,32 @@ public abstract class AbstractClientListener implements Runnable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads data from socket until {@code separator} is encountered
|
* Reads data from socket until {@code separator} is encountered
|
||||||
|
*
|
||||||
* @param separator byte to separate data portions
|
* @param separator byte to separate data portions
|
||||||
* @return array of bytes read from socket
|
* @return array of bytes read from socket
|
||||||
*/
|
*/
|
||||||
public byte[] readBytes(byte separator) {
|
public byte[] readBytes(byte[] separator) {
|
||||||
final List<Byte> data = new ArrayList<>(256);
|
final List<Byte> data = new ArrayList<>(2048 * 2048);
|
||||||
|
List<Byte> buffer = new ArrayList<>(separator.length);
|
||||||
|
int separatorPosition = 0;
|
||||||
try {
|
try {
|
||||||
while (socket.isConnected()) {
|
while (socket.isConnected()) {
|
||||||
byte[] portion = in.readNBytes(1);
|
byte[] portion = in.readNBytes(1);
|
||||||
if (portion == null || portion.length == 0 || portion[0] == separator) {
|
if (portion == null || portion.length == 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (portion[0] == separator[separatorPosition]) {
|
||||||
|
if (separatorPosition == separator.length - 1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
separatorPosition++;
|
||||||
|
buffer.add(portion[0]);
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
separatorPosition = 0;
|
||||||
|
data.addAll(buffer);
|
||||||
|
buffer.clear();
|
||||||
|
}
|
||||||
data.add(portion[0]);
|
data.add(portion[0]);
|
||||||
}
|
}
|
||||||
final byte[] bytes = new byte[data.size()];
|
final byte[] bytes = new byte[data.size()];
|
||||||
@ -96,7 +112,7 @@ public abstract class AbstractClientListener implements Runnable {
|
|||||||
for (Byte aByte : data) {
|
for (Byte aByte : data) {
|
||||||
bytes[i++] = aByte;
|
bytes[i++] = aByte;
|
||||||
}
|
}
|
||||||
if (log.isTraceEnabled()) log.trace("Received: " + new String(bytes));
|
if (log.isTraceEnabled()) log.trace("Received {} bytes: {}", bytes.length, bytes);
|
||||||
return bytes;
|
return bytes;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
@ -106,11 +122,12 @@ public abstract class AbstractClientListener implements Runnable {
|
|||||||
/**
|
/**
|
||||||
* Writes line into socket ending with default separator '\n'.
|
* Writes line into socket ending with default separator '\n'.
|
||||||
* Flushes after writing.
|
* Flushes after writing.
|
||||||
|
*
|
||||||
* @param bytes bytes to be sent into socket
|
* @param bytes bytes to be sent into socket
|
||||||
* @param separator byte to separate data portions
|
* @param separator byte to separate data portions
|
||||||
*/
|
*/
|
||||||
public void writeBytes(byte[] bytes, byte separator) {
|
public void writeBytes(byte[] bytes, byte[] separator) {
|
||||||
if (log.isTraceEnabled()) log.trace("Sending: " + new String(bytes));
|
if (log.isTraceEnabled()) log.trace("Sending {} bytes: {}", bytes.length, bytes);
|
||||||
try {
|
try {
|
||||||
out.write(bytes);
|
out.write(bytes);
|
||||||
out.write(separator);
|
out.write(separator);
|
||||||
@ -123,6 +140,7 @@ public abstract class AbstractClientListener implements Runnable {
|
|||||||
/**
|
/**
|
||||||
* Writes line into socket ending with default separator '\n'.
|
* Writes line into socket ending with default separator '\n'.
|
||||||
* Flushes after writing.
|
* Flushes after writing.
|
||||||
|
*
|
||||||
* @param data data to be sent into socket
|
* @param data data to be sent into socket
|
||||||
*/
|
*/
|
||||||
public void writeLine(String data) {
|
public void writeLine(String data) {
|
||||||
|
@ -89,9 +89,13 @@ public class Client<T extends AbstractClientListener> {
|
|||||||
*/
|
*/
|
||||||
public void stop() {
|
public void stop() {
|
||||||
log.debug("Stopping client");
|
log.debug("Stopping client");
|
||||||
|
if (client != null) {
|
||||||
client.stop();
|
client.stop();
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
|
if (socket != null) {
|
||||||
socket.close();
|
socket.close();
|
||||||
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.error("Failed to close socket");
|
log.error("Failed to close socket");
|
||||||
}
|
}
|
||||||
@ -119,7 +123,7 @@ public class Client<T extends AbstractClientListener> {
|
|||||||
* @param separator
|
* @param separator
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public byte[] readBytes(byte separator) {
|
public byte[] readBytes(byte[] separator) {
|
||||||
return client.readBytes(separator);
|
return client.readBytes(separator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +59,7 @@ public class CommandClientListener extends AbstractClientListener implements Abs
|
|||||||
log.error("Unexpected command received");
|
log.error("Unexpected command received");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
log.debug("Command received: " + command);
|
log.debug("Command received: " + command.getClass());
|
||||||
if (!(command instanceof AbstractCommand)) {
|
if (!(command instanceof AbstractCommand)) {
|
||||||
log.warn("Incorrect command received: " + command);
|
log.warn("Incorrect command received: " + command);
|
||||||
continue;
|
continue;
|
||||||
|
@ -99,7 +99,9 @@ public class Server<T extends AbstractClientListener> {
|
|||||||
iterator.remove();
|
iterator.remove();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
if (socket != null) {
|
||||||
socket.close();
|
socket.close();
|
||||||
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
log.error("Failed to close socket");
|
log.error("Failed to close socket");
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,7 @@ public final class Sewy {
|
|||||||
private static final ReentrantLock LOCK = new ReentrantLock();
|
private static final ReentrantLock LOCK = new ReentrantLock();
|
||||||
|
|
||||||
private final List<Class<?>> registeredDataTypes = new CopyOnWriteArrayList<>();
|
private final List<Class<?>> registeredDataTypes = new CopyOnWriteArrayList<>();
|
||||||
private byte separator = SEPARATOR;
|
private byte[] separator = new byte[] { SEPARATOR };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers command in white list for further communications
|
* Registers command in white list for further communications
|
||||||
@ -55,11 +55,11 @@ public final class Sewy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static byte getSeparator() {
|
public static byte[] getSeparator() {
|
||||||
return getInstance().separator;
|
return getInstance().separator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setSeparator(byte separator) {
|
public static void setSeparator(byte[] separator) {
|
||||||
try {
|
try {
|
||||||
LOCK.lock();
|
LOCK.lock();
|
||||||
getInstance().separator = separator;
|
getInstance().separator = separator;
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package me.bvn13.sewy;
|
package me.bvn13.sewy;
|
||||||
|
|
||||||
import me.bvn13.sewy.command.AbstractCommand;
|
import me.bvn13.sewy.command.AbstractCommand;
|
||||||
|
import me.bvn13.sewy.command.ComplexCommand;
|
||||||
import me.bvn13.sewy.command.PingCommand;
|
import me.bvn13.sewy.command.PingCommand;
|
||||||
import me.bvn13.sewy.command.PongCommand;
|
import me.bvn13.sewy.command.PongCommand;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
@ -23,6 +24,7 @@ import org.junit.jupiter.params.ParameterizedTest;
|
|||||||
import org.junit.jupiter.params.provider.ValueSource;
|
import org.junit.jupiter.params.provider.ValueSource;
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
public class ServerTest {
|
public class ServerTest {
|
||||||
|
|
||||||
@ -112,4 +114,43 @@ public class ServerTest {
|
|||||||
Assertions.assertTrue(latency.get() > 0);
|
Assertions.assertTrue(latency.get() > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(ints = START_PORT + 7)
|
||||||
|
void wideSeparatorTest(int port) throws InterruptedException {
|
||||||
|
Sewy.register(ComplexCommand.class);
|
||||||
|
Sewy.setSeparator(new byte[] { '\n', 'e', 'n', 'd', '\n' });
|
||||||
|
|
||||||
|
AtomicReference<ComplexCommand> check = new AtomicReference<>();
|
||||||
|
|
||||||
|
CommandServer server = new CommandServer("localhost", port, (socket) -> new CommandClientListener(socket) {
|
||||||
|
@Override
|
||||||
|
public AbstractCommand onCommand(AbstractCommand command) {
|
||||||
|
if (command instanceof ComplexCommand) {
|
||||||
|
check.set((ComplexCommand) command);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException(command.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
CommandClient client = new CommandClient("localhost", port, (socket) -> new CommandClientListener(socket) {
|
||||||
|
@Override
|
||||||
|
public AbstractCommand onCommand(AbstractCommand command) {
|
||||||
|
throw new IllegalArgumentException(command.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ComplexCommand command = new ComplexCommand();
|
||||||
|
command.add(new ComplexCommand.SimpleData("a1"));
|
||||||
|
command.add(new ComplexCommand.SimpleData("b2"));
|
||||||
|
command.add(new ComplexCommand.SimpleData("finish"));
|
||||||
|
|
||||||
|
client.send(command);
|
||||||
|
Thread.sleep(1000);
|
||||||
|
Assertions.assertNotNull(check.get());
|
||||||
|
Assertions.assertEquals(3, check.get().getDatum().size());
|
||||||
|
Assertions.assertEquals("a1", check.get().getDatum().get(0).getString());
|
||||||
|
Assertions.assertEquals("b2", check.get().getDatum().get(1).getString());
|
||||||
|
Assertions.assertEquals("finish", check.get().getDatum().get(2).getString());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
44
src/test/java/me/bvn13/sewy/command/ComplexCommand.java
Normal file
44
src/test/java/me/bvn13/sewy/command/ComplexCommand.java
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package me.bvn13.sewy.command;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ComplexCommand extends AbstractCommand {
|
||||||
|
|
||||||
|
private List<SimpleData> datum;
|
||||||
|
|
||||||
|
public ComplexCommand() {
|
||||||
|
datum = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ComplexCommand(List<SimpleData> datum) {
|
||||||
|
this.datum = datum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(SimpleData data) {
|
||||||
|
datum.add(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ComplexCommand setDatum(List<SimpleData> datum) {
|
||||||
|
this.datum = datum;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<SimpleData> getDatum() {
|
||||||
|
return datum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class SimpleData implements Serializable {
|
||||||
|
private final String string;
|
||||||
|
|
||||||
|
public SimpleData(String string) {
|
||||||
|
this.string = string;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getString() {
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user