many fixes

master
bvn13 2022-02-21 02:07:35 +03:00
parent 63df33457f
commit 98fc7703f4
10 changed files with 189 additions and 120 deletions

View File

@ -5,7 +5,7 @@ plugins {
} }
group 'me.bvn13' group 'me.bvn13'
version '1.2.4' version '1.2.5'
repositories { repositories {
mavenCentral() mavenCentral()
@ -34,6 +34,10 @@ tasks.withType(Test).configureEach {
maxParallelForks = 1 maxParallelForks = 1
} }
task sourceJar(type: Jar) {
from sourceSets.main.allJava
}
publishing { publishing {
repositories { repositories {
maven { maven {
@ -49,5 +53,12 @@ publishing {
gpr(MavenPublication) { gpr(MavenPublication) {
from(components.java) from(components.java)
} }
mavenJava(MavenPublication) {
from components.java
artifact sourceJar {
classifier "sources"
}
}
} }
} }

View File

@ -47,7 +47,7 @@ public abstract class AbstractClientListener implements Runnable {
this.in = socket.getInputStream(); this.in = socket.getInputStream();
log.debug("BufferedReader successfully created"); log.debug("BufferedReader successfully created");
log.debug("PrintWriter successfully created"); log.debug("PrintWriter successfully created");
out = socket.getOutputStream(); this.out = socket.getOutputStream();
log.debug("OutputStream successfully created"); log.debug("OutputStream successfully created");
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
@ -66,7 +66,7 @@ public abstract class AbstractClientListener implements Runnable {
* *
* @return the line read from socket * @return the line read from socket
*/ */
public String readLine() { public String readLine() throws IOException {
final byte[] bytes = readBytes(getSeparator()); final byte[] bytes = readBytes(getSeparator());
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
for (byte aByte : bytes) { for (byte aByte : bytes) {
@ -83,40 +83,36 @@ public abstract class AbstractClientListener implements Runnable {
* @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) throws IOException {
final List<Byte> data = new ArrayList<>(2048 * 2048); final List<Byte> data = new ArrayList<>(2048 * 2048);
List<Byte> buffer = new ArrayList<>(separator.length); List<Byte> buffer = new ArrayList<>(separator.length);
int separatorPosition = 0; int separatorPosition = 0;
try { while (socket.isConnected() && !socket.isClosed()) {
while (socket.isConnected()) { byte[] portion = in.readNBytes(1);
byte[] portion = in.readNBytes(1); if (portion == null || portion.length == 0) {
if (portion == null || portion.length == 0) { break;
}
if (portion[0] == separator[separatorPosition]) {
if (separatorPosition == separator.length - 1) {
break; break;
} }
if (portion[0] == separator[separatorPosition]) { separatorPosition++;
if (separatorPosition == separator.length - 1) { buffer.add(portion[0]);
break; continue;
} } else {
separatorPosition++; separatorPosition = 0;
buffer.add(portion[0]); data.addAll(buffer);
continue; buffer.clear();
} else {
separatorPosition = 0;
data.addAll(buffer);
buffer.clear();
}
data.add(portion[0]);
} }
final byte[] bytes = new byte[data.size()]; data.add(portion[0]);
int i = 0;
for (Byte aByte : data) {
bytes[i++] = aByte;
}
if (log.isTraceEnabled()) log.trace("Received {} bytes: {}", bytes.length, bytes);
return bytes;
} catch (IOException e) {
throw new RuntimeException(e);
} }
final byte[] bytes = new byte[data.size()];
int i = 0;
for (Byte aByte : data) {
bytes[i++] = aByte;
}
if (log.isTraceEnabled()) log.trace("Received {} bytes: {}", bytes.length, bytes);
return bytes;
} }
/** /**
@ -126,15 +122,11 @@ public abstract class AbstractClientListener implements Runnable {
* @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) throws IOException {
if (log.isTraceEnabled()) log.trace("Sending {} bytes: {}", bytes.length, bytes); if (log.isTraceEnabled()) log.trace("Sending {} bytes: {}", bytes.length, bytes);
try { out.write(bytes);
out.write(bytes); out.write(separator);
out.write(separator); out.flush();
out.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
} }
/** /**
@ -145,7 +137,11 @@ public abstract class AbstractClientListener implements Runnable {
*/ */
public void writeLine(String data) { public void writeLine(String data) {
if (log.isTraceEnabled()) log.trace("Sending: " + data); if (log.isTraceEnabled()) log.trace("Sending: " + data);
writeBytes(data.getBytes(), getSeparator()); try {
writeBytes(data.getBytes(), getSeparator());
} catch (Exception e) {
log.error("", e);
}
} }
/** /**
@ -157,12 +153,12 @@ public abstract class AbstractClientListener implements Runnable {
out.close(); out.close();
in.close(); in.close();
} catch (IOException e) { } catch (IOException e) {
log.warn("Unable to close IN client buffer"); log.warn("Unable to close IN/OUT client buffer");
} }
try { try {
socket.close(); socket.close();
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); log.warn("Unable to close socket");
} }
} }
} }

View File

@ -115,7 +115,11 @@ public class Client<T extends AbstractClientListener> {
* @return the line read from socket * @return the line read from socket
*/ */
public String readLine() { public String readLine() {
return client.readLine(); try {
return client.readLine();
} catch (Exception e) {
throw new RuntimeException(e);
}
} }
/** /**
@ -123,7 +127,7 @@ public class Client<T extends AbstractClientListener> {
* @param separator * @param separator
* @return * @return
*/ */
public byte[] readBytes(byte[] separator) { public byte[] readBytes(byte[] separator) throws IOException {
return client.readBytes(separator); return client.readBytes(separator);
} }

View File

@ -26,16 +26,16 @@ class ClientListenerFactory {
/** /**
* Creates client listener constructor * Creates client listener constructor
*
* @param clientListenerClass class to be used as client listener * @param clientListenerClass class to be used as client listener
* @param <T> generic type * @param <T> generic type
* @return lambda method to create client listener * @return lambda method to create client listener
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
static <T extends AbstractClientListener> Function<Socket, T> createClientListenerConstructor(Class clientListenerClass) { static <T extends AbstractClientListener> Function<Socket, T> createClientListenerConstructor(Class clientListenerClass) {
if (clientListenerClass.getGenericSuperclass() == null if (clientListenerClass.getGenericSuperclass() == null) {
/*|| !clientListenerClass.getGenericSuperclass().equals(T.class)*/) { throw new IllegalArgumentException("Wrong client listener of type: " + clientListenerClass.getName());
throw new IllegalArgumentException("Wrong client listener of type: "+clientListenerClass.getName());
} }
return (client) -> { return (client) -> {

View File

@ -19,10 +19,10 @@ import me.bvn13.sewy.command.AbstractCommand;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.Socket; import java.net.Socket;
import java.util.function.Function; import java.util.function.Function;
import static java.lang.String.format;
import static me.bvn13.sewy.ClientListenerFactory.createClientListenerConstructor; import static me.bvn13.sewy.ClientListenerFactory.createClientListenerConstructor;
/** /**
@ -41,6 +41,7 @@ public class CommandClient extends Client<CommandClientListener> {
/** /**
* Starts to connect to server immediately * Starts to connect to server immediately
*
* @param host host to connect to * @param host host to connect to
* @param port port to be used while connecting * @param port port to be used while connecting
*/ */
@ -50,8 +51,9 @@ public class CommandClient extends Client<CommandClientListener> {
/** /**
* Starts to connect to server immediately * Starts to connect to server immediately
* @param host host to connect to *
* @param port port to be used while connecting * @param host host to connect to
* @param port port to be used while connecting
* @param clientListenerClass client listener class describing protocol of communications * @param clientListenerClass client listener class describing protocol of communications
*/ */
public CommandClient(String host, int port, Class clientListenerClass) { public CommandClient(String host, int port, Class clientListenerClass) {
@ -60,8 +62,9 @@ public class CommandClient extends Client<CommandClientListener> {
/** /**
* Connects to server immediately * Connects to server immediately
* @param host host to connect to *
* @param port port to be used while connecting * @param host host to connect to
* @param port port to be used while connecting
* @param clientListenerConstructor to provide constructor for client listener (see {@link me.bvn13.sewy.Client#Client(java.lang.String, int, java.lang.Class)}) * @param clientListenerConstructor to provide constructor for client listener (see {@link me.bvn13.sewy.Client#Client(java.lang.String, int, java.lang.Class)})
*/ */
public CommandClient(String host, int port, Function<Socket, CommandClientListener> clientListenerConstructor) { public CommandClient(String host, int port, Function<Socket, CommandClientListener> clientListenerConstructor) {
@ -71,10 +74,12 @@ public class CommandClient extends Client<CommandClientListener> {
/** /**
* Sends command to server * Sends command to server
*
* @param command command to be sent * @param command command to be sent
* @param <T> generic type * @param <T> generic type
* @throws IOException if any error occurred while sending
*/ */
public <T extends AbstractCommand> void send(T command) { public <T extends AbstractCommand> void send(T command) throws IOException {
log.debug("Start to send command: " + command); log.debug("Start to send command: " + command);
client.send(command); client.send(command);
} }

View File

@ -18,6 +18,7 @@ package me.bvn13.sewy;
import me.bvn13.sewy.command.AbstractCommand; import me.bvn13.sewy.command.AbstractCommand;
import org.apache.commons.lang3.SerializationUtils; import org.apache.commons.lang3.SerializationUtils;
import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
import java.net.Socket; import java.net.Socket;
@ -39,34 +40,38 @@ public class CommandClientListener extends AbstractClientListener implements Abs
public void run() { public void run() {
for (Thread.yield(); !socket.isConnected() && !socket.isClosed(); Thread.yield()) { for (Thread.yield(); !socket.isConnected() && !socket.isClosed(); Thread.yield()) {
} }
while (socket.isConnected()) { while (socket.isConnected() && !socket.isClosed()) {
Thread.yield();
byte[] line = readBytes(getSeparator());
if (line == null || line.length == 0) {
continue;
}
final Object command;
try { try {
command = SerializationUtils.deserialize(line); Thread.yield();
} catch (Throwable e) { byte[] line = readBytes(getSeparator());
log.warn("Deserialization exception occurred!", e); if (line == null || line.length == 0) {
continue; continue;
}
final Object command;
try {
command = SerializationUtils.deserialize(line);
} catch (Throwable e) {
log.warn("Deserialization exception occurred!", e);
continue;
}
if (command == null) {
continue;
}
if (!Sewy.getRegisteredDataTypes().contains(command.getClass())) {
log.error("Unexpected command received");
continue;
}
log.debug("Command received: " + command.getClass());
if (!(command instanceof AbstractCommand)) {
log.warn("Incorrect command received: " + command);
continue;
}
final Serializable response = onCommand((AbstractCommand) command);
log.debug(format("Response for %s is: %s", command, response));
writeBytes(SerializationUtils.serialize(response), getSeparator());
} catch (Exception e) {
log.error("Failed to communicate!", e);
} }
if (command == null) {
continue;
}
if (!Sewy.getRegisteredDataTypes().contains(command.getClass())) {
log.error("Unexpected command received");
continue;
}
log.debug("Command received: " + command.getClass());
if (!(command instanceof AbstractCommand)) {
log.warn("Incorrect command received: " + command);
continue;
}
final Serializable response = onCommand((AbstractCommand) command);
log.debug(format("Response for %s is: %s", command, response));
writeBytes(SerializationUtils.serialize(response), getSeparator());
} }
} }
@ -85,11 +90,12 @@ public class CommandClientListener extends AbstractClientListener implements Abs
/** /**
* Sends command to opposite side * Sends command to opposite side
*
* @param command command to be sent * @param command command to be sent
* @param <T> generic type * @param <T> generic type
*/ */
public <T extends AbstractCommand> void send(T command) { public <T extends AbstractCommand> void send(T command) throws IOException {
log.debug("Start to send command: " + command); log.debug("Start to send command: {}", command);
writeBytes(SerializationUtils.serialize(command), getSeparator()); writeBytes(SerializationUtils.serialize(command), getSeparator());
} }

View File

@ -16,19 +16,12 @@
package me.bvn13.sewy; package me.bvn13.sewy;
import me.bvn13.sewy.command.AbstractCommand; import me.bvn13.sewy.command.AbstractCommand;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.util.ArrayList; import java.util.function.Consumer;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Function; import java.util.function.Function;
import static java.lang.String.format; import static java.lang.String.format;
@ -50,8 +43,8 @@ public class CommandServer extends Server<CommandClientListener> {
} }
/** /**
* @param host host to bind in order to start listen to clients * @param host host to bind in order to start listen to clients
* @param port port to start listen to * @param port port to start listen to
* @param clientListenerClass client listen class to be used for communication * @param clientListenerClass client listen class to be used for communication
*/ */
public CommandServer(String host, int port, Class clientListenerClass) { public CommandServer(String host, int port, Class clientListenerClass) {
@ -59,9 +52,8 @@ public class CommandServer extends Server<CommandClientListener> {
} }
/** /**
* * @param host host to bind in order to start listen to clients
* @param host host to bind in order to start listen to clients * @param port port to start listen to
* @param port port to start listen to
* @param clientListenerConstructor to provide constructor for client listener (see {@link CommandServer#CommandServer(String, int, Class)}) * @param clientListenerConstructor to provide constructor for client listener (see {@link CommandServer#CommandServer(String, int, Class)})
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -73,10 +65,12 @@ public class CommandServer extends Server<CommandClientListener> {
socket = server; socket = server;
while (!server.isClosed()) { while (!server.isClosed()) {
final Socket client = server.accept(); if (!isMaximumClientsAchieved()) {
final CommandClientListener clientListener = clientListenerConstructor.apply(client); final Socket client = server.accept();
executor.execute(clientListener); final CommandClientListener clientListener = clientListenerConstructor.apply(client);
clients.add(clientListener); executor.execute(clientListener);
clients.add(clientListener);
}
} }
} catch (IOException e) { } catch (IOException e) {
@ -87,14 +81,31 @@ public class CommandServer extends Server<CommandClientListener> {
/** /**
* Sends command to every client * Sends command to every client
* @param command command to be sent *
* @param <T> generic type * @param command command to be sent
* @param <T> generic type
*/ */
public <T extends AbstractCommand> void send(T command) { public <T extends AbstractCommand> void send(T command) {
log.debug("Start to send command: " + command); send(command, client -> {});
for (CommandClientListener client : clients) {
client.send(command);
}
} }
/**
* Sends command to every client
*
* @param command command to be sent
* @param <T> generic type
* @param onException for catching errors while sending. Do not throw any Exception inside onException callback -
* it leads to stopping sending the command to remaining clients
*/
public <T extends AbstractCommand> void send(T command, Consumer<CommandClientListener> onException) {
log.debug("Start to send command: " + command);
for (CommandClientListener client : clients) {
try {
client.send(command);
} catch (IOException e) {
log.error("Failed to send command " + command, e);
onException.accept(client);
}
}
}
} }

View File

@ -46,12 +46,14 @@ public class Server<T extends AbstractClientListener> {
protected ServerSocket socket; protected ServerSocket socket;
private int maxClientsCount;
protected Server() { protected Server() {
} }
/** /**
* @param host host to bind in order to start listen to clients * @param host host to bind in order to start listen to clients
* @param port port to start listen to * @param port port to start listen to
* @param clientListenerClass client listen class to be used for communication * @param clientListenerClass client listen class to be used for communication
*/ */
public Server(String host, int port, Class clientListenerClass) { public Server(String host, int port, Class clientListenerClass) {
@ -59,9 +61,8 @@ public class Server<T extends AbstractClientListener> {
} }
/** /**
* * @param host host to bind in order to start listen to clients
* @param host host to bind in order to start listen to clients * @param port port to start listen to
* @param port port to start listen to
* @param clientListenerConstructor to provide constructor for client listener (see {@link me.bvn13.sewy.Server#Server(java.lang.String, int, java.lang.Class)}) * @param clientListenerConstructor to provide constructor for client listener (see {@link me.bvn13.sewy.Server#Server(java.lang.String, int, java.lang.Class)})
*/ */
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -73,17 +74,19 @@ public class Server<T extends AbstractClientListener> {
socket = server; socket = server;
while (!server.isClosed()) { while (!server.isClosed()) {
final Socket client = server.accept(); if (!isMaximumClientsAchieved()) {
final T clientListener = clientListenerConstructor.apply(client); final Socket client = server.accept();
executor.execute(clientListener); final T clientListener = clientListenerConstructor.apply(client);
clients.add(clientListener); executor.execute(clientListener);
clients.add(clientListener);
}
Thread.yield();
} }
} catch (IOException e) { } catch (IOException e) {
log.error(format("Error while conversation with %s:%d", host, port), e); log.error(format("Error while conversation with %s:%d", host, port), e);
} }
}); });
} }
/** /**
@ -110,10 +113,33 @@ public class Server<T extends AbstractClientListener> {
/** /**
* To check whether the server is ready for new connections * To check whether the server is ready for new connections
*
* @return * @return
*/ */
public boolean isListening() { public boolean isListening() {
return socket != null && socket.isBound(); return socket != null && socket.isBound();
} }
/**
* Returns count of connected clients
*
* @return count of connected clients
*/
public int getClientsCount() {
return clients.size();
}
/**
* Sets maximum clients to be connected to server
*
* @param count maximum clients count
*/
public void setMaxClientsCount(int count) {
maxClientsCount = count;
}
protected boolean isMaximumClientsAchieved() {
return maxClientsCount == 0
|| clients.size() >= maxClientsCount;
}
} }

View File

@ -15,6 +15,9 @@
*/ */
package me.bvn13.sewy; package me.bvn13.sewy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.Socket; import java.net.Socket;
/** /**
@ -22,6 +25,9 @@ import java.net.Socket;
* Writes into socket all the data received before * Writes into socket all the data received before
*/ */
public class EchoClientListener extends AbstractClientListener { public class EchoClientListener extends AbstractClientListener {
private final Logger log = LoggerFactory.getLogger(EchoClientListener.class);
public EchoClientListener(Socket socket) { public EchoClientListener(Socket socket) {
super(socket); super(socket);
} }
@ -33,8 +39,12 @@ public class EchoClientListener extends AbstractClientListener {
public void run() { public void run() {
while (socket.isConnected()) { while (socket.isConnected()) {
Thread.yield(); Thread.yield();
final String data = readLine(); try {
writeLine(data); final String data = readLine();
writeLine(data);
} catch (Exception e) {
log.error("", e);
}
} }
} }
} }

View File

@ -83,7 +83,7 @@ public class ServerTest {
@ParameterizedTest @ParameterizedTest
@ValueSource(ints = START_PORT + 6) @ValueSource(ints = START_PORT + 6)
void serverIsAbleToPingPong(int port) throws InterruptedException { void serverIsAbleToPingPong(int port) throws Exception {
Sewy.register(PingCommand.class); Sewy.register(PingCommand.class);
Sewy.register(PongCommand.class); Sewy.register(PongCommand.class);
@ -116,7 +116,7 @@ public class ServerTest {
@ParameterizedTest @ParameterizedTest
@ValueSource(ints = START_PORT + 7) @ValueSource(ints = START_PORT + 7)
void wideSeparatorTest(int port) throws InterruptedException { void wideSeparatorTest(int port) throws Exception {
Sewy.register(ComplexCommand.class); Sewy.register(ComplexCommand.class);
Sewy.setSeparator(new byte[] { '\n', 'e', 'n', 'd', '\n' }); Sewy.setSeparator(new byte[] { '\n', 'e', 'n', 'd', '\n' });