refactoring, describing with JavaDocs
This commit is contained in:
parent
ac945faef8
commit
380dd5f8b0
@ -15,6 +15,12 @@ dependencies {
|
|||||||
// https://mvnrepository.com/artifact/org.slf4j/slf4j-api
|
// https://mvnrepository.com/artifact/org.slf4j/slf4j-api
|
||||||
implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.35'
|
implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.35'
|
||||||
|
|
||||||
|
// https://mvnrepository.com/artifact/commons-io/commons-io
|
||||||
|
implementation group: 'commons-io', name: 'commons-io', version: '2.11.0'
|
||||||
|
|
||||||
|
// https://mvnrepository.com/artifact/org.apache.commons/commons-lang3
|
||||||
|
implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.12.0'
|
||||||
|
|
||||||
testImplementation 'ch.qos.logback:logback-classic:1.2.10'
|
testImplementation 'ch.qos.logback:logback-classic:1.2.10'
|
||||||
|
|
||||||
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
|
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2020 Vyacheslav Boyko
|
Copyright 2022 Vyacheslav Boyko
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@ -18,49 +18,114 @@ package me.bvn13.sewy;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStream;
|
||||||
import java.io.PrintWriter;
|
import java.io.OutputStream;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static me.bvn13.sewy.Sewy.SEPARATOR;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TCP Client listener.
|
||||||
|
* This class provides methods to manage communications between {@link Server} and {@link Client}.
|
||||||
|
* Inherit this class to implement your own communication type.
|
||||||
|
*/
|
||||||
public abstract class AbstractClientListener implements Runnable {
|
public abstract class AbstractClientListener implements Runnable {
|
||||||
|
|
||||||
protected static final Logger log = LoggerFactory.getLogger(AbstractClientListener.class);
|
protected final Logger log = LoggerFactory.getLogger(this.getClass());
|
||||||
|
|
||||||
protected final Socket socket;
|
protected final Socket socket;
|
||||||
protected PrintWriter out;
|
protected OutputStream out;
|
||||||
protected BufferedReader in;
|
protected InputStream in;
|
||||||
|
|
||||||
protected AbstractClientListener(Socket socket) {
|
protected AbstractClientListener(Socket socket) {
|
||||||
|
log.debug("Initializing client listener");
|
||||||
this.socket = socket;
|
this.socket = socket;
|
||||||
try {
|
try {
|
||||||
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
this.in = socket.getInputStream();
|
||||||
} catch (IOException e) {
|
log.debug("BufferedReader successfully created");
|
||||||
throw new RuntimeException(e);
|
log.debug("PrintWriter successfully created");
|
||||||
}
|
out = socket.getOutputStream();
|
||||||
try {
|
log.debug("OutputStream successfully created");
|
||||||
out = new PrintWriter(socket.getOutputStream(), true);
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread runner.
|
||||||
|
* Override it according the needs
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public abstract void run();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads line (separated with '\n') from socket
|
||||||
|
* @return the line read from socket
|
||||||
|
*/
|
||||||
public String readLine() {
|
public String readLine() {
|
||||||
|
final byte[] bytes = readBytes(SEPARATOR);
|
||||||
|
final StringBuilder sb = new StringBuilder();
|
||||||
|
for (byte aByte : bytes) {
|
||||||
|
sb.append((char) aByte);
|
||||||
|
}
|
||||||
|
final String string = sb.toString();
|
||||||
|
if (log.isTraceEnabled()) log.trace("Received: " + string);
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads data from socket until {@code separator} is encountered
|
||||||
|
* @param separator byte to separate data portions
|
||||||
|
* @return array of bytes read from socket
|
||||||
|
*/
|
||||||
|
public byte[] readBytes(byte separator) {
|
||||||
|
final List<Byte> data = new ArrayList<>(256);
|
||||||
try {
|
try {
|
||||||
return in.readLine();
|
while (socket.isConnected()) {
|
||||||
|
byte[] portion = in.readNBytes(1);
|
||||||
|
if (portion == null || portion.length == 0 || portion[0] == separator) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
data.add(portion[0]);
|
||||||
|
}
|
||||||
|
final byte[] bytes = new byte[data.size()];
|
||||||
|
int i = 0;
|
||||||
|
for (Byte aByte : data) {
|
||||||
|
bytes[i++] = aByte;
|
||||||
|
}
|
||||||
|
if (log.isTraceEnabled()) log.trace("Received: " + new String(bytes));
|
||||||
|
return bytes;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes line into socket ending with default separator '\n'.
|
||||||
|
* Flushes after writing.
|
||||||
|
* @param data data to be sent into socket
|
||||||
|
*/
|
||||||
public void writeLine(String data) {
|
public void writeLine(String data) {
|
||||||
out.println(data);
|
if (log.isTraceEnabled()) log.trace("Sending: " + data);
|
||||||
|
try {
|
||||||
|
out.write(data.getBytes());
|
||||||
|
out.write(SEPARATOR);
|
||||||
|
out.flush();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() {
|
/**
|
||||||
out.close();
|
* Stops client listener gracefully
|
||||||
|
*/
|
||||||
|
public void stop() {
|
||||||
|
log.debug("Stopping");
|
||||||
try {
|
try {
|
||||||
|
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 client buffer");
|
||||||
|
22
src/main/java/me/bvn13/sewy/AbstractCommandExecutor.java
Normal file
22
src/main/java/me/bvn13/sewy/AbstractCommandExecutor.java
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package me.bvn13.sewy;
|
||||||
|
|
||||||
|
import me.bvn13.sewy.command.AbstractCommand;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface to describe command executor.
|
||||||
|
* Every command encountered by {@link CommandClientListener}
|
||||||
|
* will be sent into such executor provided
|
||||||
|
* in {@link Client#Client(java.lang.String, int, java.lang.Class)}
|
||||||
|
* or {@link Server#Server(java.lang.String, int, java.lang.Class)}
|
||||||
|
* while instantiating Client and Server
|
||||||
|
* @param <T>
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface AbstractCommandExecutor<T extends AbstractCommand> {
|
||||||
|
/**
|
||||||
|
* Command handler
|
||||||
|
* @param command incoming command
|
||||||
|
* @return response on incoming command is another command for corresponding side
|
||||||
|
*/
|
||||||
|
AbstractCommand onCommand(T command);
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2020 Vyacheslav Boyko
|
Copyright 2022 Vyacheslav Boyko
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@ -18,81 +18,111 @@ package me.bvn13.sewy;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.PrintWriter;
|
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
import static java.lang.String.format;
|
import static java.lang.String.format;
|
||||||
|
import static me.bvn13.sewy.ClientListenerFactory.createClientListenerConstructor;
|
||||||
|
|
||||||
public class Client {
|
/**
|
||||||
|
* TCP Client.
|
||||||
private static final Logger log = LoggerFactory.getLogger(Client.class);
|
* Create the instance of this class to connect to {@link Server}
|
||||||
|
*/
|
||||||
|
public class Client<T extends AbstractClientListener> {
|
||||||
|
protected final Logger log = LoggerFactory.getLogger(this.getClass());
|
||||||
|
|
||||||
private final ExecutorService executor = Executors.newCachedThreadPool();
|
private final ExecutorService executor = Executors.newCachedThreadPool();
|
||||||
|
protected T client;
|
||||||
|
|
||||||
private Socket socket;
|
protected Socket socket;
|
||||||
private PrintWriter out;
|
|
||||||
private BufferedReader in;
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default constructor is to delay connecting to server
|
||||||
|
*/
|
||||||
public Client() {
|
public Client() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Client(String host, int port) {
|
/**
|
||||||
connect(host, port);
|
* Connects to server immediately
|
||||||
|
* @param host host to connect to
|
||||||
|
* @param port port to be used while connecting
|
||||||
|
* @param clientListenerClass client listener class describing protocol of communications
|
||||||
|
*/
|
||||||
|
public Client(String host, int port, Class clientListenerClass) {
|
||||||
|
this(host, port, createClientListenerConstructor(clientListenerClass));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stop() {
|
/**
|
||||||
out.close();
|
* Connects to server immediately
|
||||||
try {
|
* @param host host to connect to
|
||||||
in.close();
|
* @param port port to be used while connecting
|
||||||
} catch (IOException e) {
|
* @param clientListenerConstructor to provide constructor for client listener (see {@link me.bvn13.sewy.Client#Client(java.lang.String, int, java.lang.Class)})
|
||||||
log.warn("Unable to close IN client buffer");
|
*/
|
||||||
}
|
public Client(String host, int port, Function<Socket, T> clientListenerConstructor) {
|
||||||
try {
|
log.debug("Creating client");
|
||||||
socket.close();
|
connect(host, port, clientListenerConstructor);
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void connect(String host, int port) {
|
/**
|
||||||
|
* Connects to {@link Server}
|
||||||
|
* @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)})
|
||||||
|
*/
|
||||||
|
public void connect(String host, int port, Function<Socket, T> clientListenerConstructor) {
|
||||||
try {
|
try {
|
||||||
|
log.debug(format("Connecting to %s:%d", host, port));
|
||||||
socket = new Socket(host, port);
|
socket = new Socket(host, port);
|
||||||
|
client = clientListenerConstructor.apply(socket);
|
||||||
|
executor.execute(client);
|
||||||
} 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);
|
||||||
stop();
|
stop();
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
out = new PrintWriter(socket.getOutputStream(), true);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops client gracefully
|
||||||
|
*/
|
||||||
|
public void stop() {
|
||||||
|
log.debug("Stopping client");
|
||||||
|
client.stop();
|
||||||
|
executor.shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To check whether socket is online
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isConnected() {
|
||||||
|
return socket != null && socket.isConnected();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads one line from socket
|
||||||
|
* @return the line read from socket
|
||||||
|
*/
|
||||||
public String readLine() {
|
public String readLine() {
|
||||||
try {
|
return client.readLine();
|
||||||
return in.readLine();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads data from socket until {@code separator} is encountered
|
||||||
|
* @param separator
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public byte[] readBytes(byte separator) {
|
||||||
|
return client.readBytes(separator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes line into socket ending with default separator '\n'.
|
||||||
|
* @param data data to be sent into socket
|
||||||
|
*/
|
||||||
public void writeLine(String data) {
|
public void writeLine(String data) {
|
||||||
out.println(data);
|
client.writeLine(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isConnected() {
|
|
||||||
return socket.isConnected();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
52
src/main/java/me/bvn13/sewy/ClientListenerFactory.java
Normal file
52
src/main/java/me/bvn13/sewy/ClientListenerFactory.java
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 Vyacheslav Boyko
|
||||||
|
|
||||||
|
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 me.bvn13.sewy;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory constructing client listeners
|
||||||
|
*/
|
||||||
|
class ClientListenerFactory {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates client listener constructor
|
||||||
|
* @param clientListenerClass class to be used as client listener
|
||||||
|
* @param <T> generic type
|
||||||
|
* @return lambda method to create client listener
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
static <T extends AbstractClientListener> Function<Socket, T> createClientListenerConstructor(Class clientListenerClass) {
|
||||||
|
|
||||||
|
if (clientListenerClass.getGenericSuperclass() == null
|
||||||
|
/*|| !clientListenerClass.getGenericSuperclass().equals(T.class)*/) {
|
||||||
|
throw new IllegalArgumentException("Wrong client listener of type: "+clientListenerClass.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
return (client) -> {
|
||||||
|
try {
|
||||||
|
final Constructor<CommandClientListener> constructor = clientListenerClass.getDeclaredConstructor(Socket.class);
|
||||||
|
constructor.setAccessible(true);
|
||||||
|
return (T) constructor.newInstance(client);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
81
src/main/java/me/bvn13/sewy/CommandClient.java
Normal file
81
src/main/java/me/bvn13/sewy/CommandClient.java
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 Vyacheslav Boyko
|
||||||
|
|
||||||
|
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 me.bvn13.sewy;
|
||||||
|
|
||||||
|
import me.bvn13.sewy.command.AbstractCommand;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import static java.lang.String.format;
|
||||||
|
import static me.bvn13.sewy.ClientListenerFactory.createClientListenerConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TCP Client.
|
||||||
|
* Works with command protocol.
|
||||||
|
* Create the instance of this class to connect to {@link Server}
|
||||||
|
*/
|
||||||
|
public class CommandClient extends Client<CommandClientListener> {
|
||||||
|
private final Logger log = LoggerFactory.getLogger(this.getClass());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default constructor is to delay connecting to Server
|
||||||
|
*/
|
||||||
|
public CommandClient() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts to connect to server immediately
|
||||||
|
* @param host host to connect to
|
||||||
|
* @param port port to be used while connecting
|
||||||
|
*/
|
||||||
|
public CommandClient(String host, int port) {
|
||||||
|
this(host, port, CommandClientListener.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts to connect to server immediately
|
||||||
|
* @param host host to connect to
|
||||||
|
* @param port port to be used while connecting
|
||||||
|
* @param clientListenerClass client listener class describing protocol of communications
|
||||||
|
*/
|
||||||
|
public CommandClient(String host, int port, Class clientListenerClass) {
|
||||||
|
this(host, port, createClientListenerConstructor(clientListenerClass));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connects to server immediately
|
||||||
|
* @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)})
|
||||||
|
*/
|
||||||
|
public CommandClient(String host, int port, Function<Socket, CommandClientListener> clientListenerConstructor) {
|
||||||
|
log.debug("Creating client");
|
||||||
|
connect(host, port, clientListenerConstructor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends command to server
|
||||||
|
* @param command command to be sent
|
||||||
|
* @param <T> generic type
|
||||||
|
*/
|
||||||
|
public <T extends AbstractCommand> void send(T command) {
|
||||||
|
log.debug("Start to send command: " + command);
|
||||||
|
client.send(command);
|
||||||
|
}
|
||||||
|
}
|
102
src/main/java/me/bvn13/sewy/CommandClientListener.java
Normal file
102
src/main/java/me/bvn13/sewy/CommandClientListener.java
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 Vyacheslav Boyko
|
||||||
|
|
||||||
|
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 me.bvn13.sewy;
|
||||||
|
|
||||||
|
import me.bvn13.sewy.command.AbstractCommand;
|
||||||
|
import org.apache.commons.lang3.SerializationUtils;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
|
import static java.lang.String.format;
|
||||||
|
import static me.bvn13.sewy.Sewy.SEPARATOR;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client listener describing protocol-oriented communication
|
||||||
|
*/
|
||||||
|
public class CommandClientListener extends AbstractClientListener implements AbstractCommandExecutor {
|
||||||
|
public CommandClientListener(Socket socket) {
|
||||||
|
super(socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread runner
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
for (Thread.yield(); !socket.isConnected() && !socket.isClosed(); Thread.yield()) {
|
||||||
|
}
|
||||||
|
while (socket.isConnected()) {
|
||||||
|
Thread.yield();
|
||||||
|
byte[] line = readBytes(SEPARATOR);
|
||||||
|
if (line == null || line.length == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final Object command = SerializationUtils.deserialize(line);
|
||||||
|
if (command == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!Sewy.getRegisteredDataTypes().contains(command.getClass())) {
|
||||||
|
log.error("Unexpected command received");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
log.debug("Command received: " + command);
|
||||||
|
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));
|
||||||
|
try {
|
||||||
|
out.write(SerializationUtils.serialize(response));
|
||||||
|
out.write(SEPARATOR);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to receive the data command-by-command incoming from clients
|
||||||
|
* You need to override it
|
||||||
|
*
|
||||||
|
* @param command serialized command to be checked by using
|
||||||
|
* <p>{@code instanceof ConcreteCommandClass}</p>
|
||||||
|
* @return server answer on client command
|
||||||
|
*/
|
||||||
|
public AbstractCommand onCommand(AbstractCommand command) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends command to opposite side
|
||||||
|
* @param command command to be sent
|
||||||
|
* @param <T> generic type
|
||||||
|
*/
|
||||||
|
public <T extends AbstractCommand> void send(T command) {
|
||||||
|
log.debug("Start to send command: " + command);
|
||||||
|
try {
|
||||||
|
out.write(SerializationUtils.serialize(command));
|
||||||
|
out.write(SEPARATOR);
|
||||||
|
out.flush();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,14 +0,0 @@
|
|||||||
package me.bvn13.sewy;
|
|
||||||
|
|
||||||
import java.net.Socket;
|
|
||||||
|
|
||||||
public class DefaultClientListener extends AbstractClientListener {
|
|
||||||
public DefaultClientListener(Socket socket) {
|
|
||||||
super(socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2020 Vyacheslav Boyko
|
Copyright 2022 Vyacheslav Boyko
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@ -19,7 +19,6 @@ import org.slf4j.Logger;
|
|||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Constructor;
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
@ -32,28 +31,47 @@ 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;
|
||||||
|
import static me.bvn13.sewy.ClientListenerFactory.createClientListenerConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TCP Server.
|
||||||
|
* Create the instance of this class to connect to {@link Client}
|
||||||
|
*/
|
||||||
public class Server {
|
public class Server {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(Server.class);
|
private final Logger log = LoggerFactory.getLogger(this.getClass());
|
||||||
|
|
||||||
private final ExecutorService executor = Executors.newCachedThreadPool();
|
private final ExecutorService executor = Executors.newCachedThreadPool();
|
||||||
final List<AbstractClientListener> clients = Collections.synchronizedList(new ArrayList<>());
|
private final List<AbstractClientListener> clients = Collections.synchronizedList(new ArrayList<>());
|
||||||
|
|
||||||
private ServerSocket socket;
|
private ServerSocket socket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param host host to bind in order to start listen to clients
|
||||||
|
* @param port port to start listen to
|
||||||
|
*/
|
||||||
public Server(String host, int port) {
|
public Server(String host, int port) {
|
||||||
this(host, port, DefaultClientListener.class);
|
this(host, port, CommandClientListener.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
/**
|
||||||
|
* @param host host to bind in order to start listen to clients
|
||||||
|
* @param port port to start listen to
|
||||||
|
* @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) {
|
||||||
this(host, port, defaultClientListenerConstructor(clientListenerClass));
|
this(host, port, createClientListenerConstructor(clientListenerClass));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param host host to bind in order to start listen to clients
|
||||||
|
* @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)})
|
||||||
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public Server(String host, int port, Function<Socket, AbstractClientListener> clientListenerConstructor) {
|
public Server(String host, int port, Function<Socket, CommandClientListener> clientListenerConstructor) {
|
||||||
|
log.debug("Starting server");
|
||||||
executor.execute(() -> {
|
executor.execute(() -> {
|
||||||
try (final ServerSocket server = new ServerSocket(port, 0, InetAddress.getByName(host))) {
|
try (final ServerSocket server = new ServerSocket(port, 0, InetAddress.getByName(host))) {
|
||||||
|
|
||||||
@ -73,7 +91,12 @@ public class Server {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops server gracefully
|
||||||
|
* Disconnects from every client
|
||||||
|
*/
|
||||||
public void stop() {
|
public void stop() {
|
||||||
|
log.debug("Stopping server");
|
||||||
final Iterator<AbstractClientListener> iterator = clients.iterator();
|
final Iterator<AbstractClientListener> iterator = clients.iterator();
|
||||||
while (iterator.hasNext()) {
|
while (iterator.hasNext()) {
|
||||||
final AbstractClientListener client = iterator.next();
|
final AbstractClientListener client = iterator.next();
|
||||||
@ -83,28 +106,12 @@ public class Server {
|
|||||||
executor.shutdown();
|
executor.shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isListening() {
|
/**
|
||||||
|
* To check whether the server is ready for new connections
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean isListening() {
|
||||||
return socket != null && socket.isBound();
|
return socket != null && socket.isBound();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private static Function<Socket, AbstractClientListener> defaultClientListenerConstructor(Class clientListenerClass) {
|
|
||||||
|
|
||||||
if (clientListenerClass.getGenericSuperclass() == null
|
|
||||||
|| !clientListenerClass.getGenericSuperclass().equals(AbstractClientListener.class)) {
|
|
||||||
throw new IllegalArgumentException("Wrong client listener of type: "+clientListenerClass.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
return (client) -> {
|
|
||||||
try {
|
|
||||||
final Constructor<AbstractClientListener> constructor = clientListenerClass.getDeclaredConstructor(Socket.class);
|
|
||||||
constructor.setAccessible(true);
|
|
||||||
return constructor.newInstance(client);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2020 Vyacheslav Boyko
|
Copyright 2022 Vyacheslav Boyko
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@ -15,23 +15,56 @@
|
|||||||
*/
|
*/
|
||||||
package me.bvn13.sewy;
|
package me.bvn13.sewy;
|
||||||
|
|
||||||
import java.io.Serializable;
|
import me.bvn13.sewy.command.AbstractCommand;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supporting class providing protocol restrictions
|
||||||
|
*/
|
||||||
public final class Sewy {
|
public final class Sewy {
|
||||||
|
|
||||||
|
static final byte SEPARATOR = '\n';
|
||||||
|
|
||||||
private static Sewy INSTANCE;
|
private static Sewy INSTANCE;
|
||||||
private static final ReentrantLock LOCK = new ReentrantLock();
|
private static final ReentrantLock LOCK = new ReentrantLock();
|
||||||
|
|
||||||
private final List<Serializable> registeredDataTypes = new CopyOnWriteArrayList<>();
|
private final List<Class<?>> registeredDataTypes = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
public static void register(Serializable clazz) {
|
/**
|
||||||
|
* Registers command in white list for further communications
|
||||||
|
* @param clazz command class
|
||||||
|
* @param <T> generic type
|
||||||
|
*/
|
||||||
|
public static <T extends AbstractCommand> void register(Class<T> clazz) {
|
||||||
getInstance().registeredDataTypes.add(clazz);
|
getInstance().registeredDataTypes.add(clazz);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Sewy() {}
|
/**
|
||||||
|
* Registers commands in white list for further communications
|
||||||
|
* @param classes array of command classes
|
||||||
|
* @param <T> generic type
|
||||||
|
*/
|
||||||
|
public static <T extends AbstractCommand> void register(Class<T>[] classes) {
|
||||||
|
for (Class<T> clazz: classes) {
|
||||||
|
register(clazz);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Sewy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
static List<Class<AbstractCommand>> getRegisteredDataTypes() {
|
||||||
|
final List<Class<AbstractCommand>> dataTypes = new ArrayList<>();
|
||||||
|
for (Class<?> registeredDataType : INSTANCE.registeredDataTypes) {
|
||||||
|
dataTypes.add((Class<AbstractCommand>) registeredDataType);
|
||||||
|
}
|
||||||
|
return dataTypes;
|
||||||
|
}
|
||||||
|
|
||||||
private static Sewy getInstance() {
|
private static Sewy getInstance() {
|
||||||
try {
|
try {
|
||||||
|
36
src/main/java/me/bvn13/sewy/SimpleClientListener.java
Normal file
36
src/main/java/me/bvn13/sewy/SimpleClientListener.java
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 Vyacheslav Boyko
|
||||||
|
|
||||||
|
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 me.bvn13.sewy;
|
||||||
|
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple client listener is to disable automatic receiving and sending data.
|
||||||
|
* Override {@link SimpleClientListener#run()} to implement business logic of communicating
|
||||||
|
*/
|
||||||
|
public class SimpleClientListener extends AbstractClientListener {
|
||||||
|
protected SimpleClientListener(Socket socket) {
|
||||||
|
super(socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread runner
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
25
src/main/java/me/bvn13/sewy/command/AbstractCommand.java
Normal file
25
src/main/java/me/bvn13/sewy/command/AbstractCommand.java
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 Vyacheslav Boyko
|
||||||
|
|
||||||
|
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 me.bvn13.sewy.command;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The very parent class every command should be inherited from
|
||||||
|
*/
|
||||||
|
public class AbstractCommand implements Serializable {
|
||||||
|
|
||||||
|
}
|
22
src/main/java/me/bvn13/sewy/command/PingCommand.java
Normal file
22
src/main/java/me/bvn13/sewy/command/PingCommand.java
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package me.bvn13.sewy.command;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
public class PingCommand extends AbstractCommand {
|
||||||
|
private final long time;
|
||||||
|
|
||||||
|
public PingCommand() {
|
||||||
|
this.time = Instant.now().toEpochMilli();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTime() {
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PingCommand{" +
|
||||||
|
"time=" + time +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
38
src/main/java/me/bvn13/sewy/command/PongCommand.java
Normal file
38
src/main/java/me/bvn13/sewy/command/PongCommand.java
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package me.bvn13.sewy.command;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
public class PongCommand extends AbstractCommand {
|
||||||
|
private final long time;
|
||||||
|
private long pingTime;
|
||||||
|
|
||||||
|
public PongCommand(PingCommand ping) {
|
||||||
|
this.pingTime = ping.getTime();
|
||||||
|
this.time = Instant.now().toEpochMilli();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTime() {
|
||||||
|
return time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getPingTime() {
|
||||||
|
return pingTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPingTime(long pingTime) {
|
||||||
|
this.pingTime = pingTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getLatency() {
|
||||||
|
return time - pingTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PongCommand{" +
|
||||||
|
"time=" + time +
|
||||||
|
", pingTime=" + pingTime +
|
||||||
|
", latency=" + getLatency() +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +1,40 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 Vyacheslav Boyko
|
||||||
|
|
||||||
|
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 me.bvn13.sewy;
|
package me.bvn13.sewy;
|
||||||
|
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple ECHOed client listener
|
||||||
|
* Writes into socket all the data received before
|
||||||
|
*/
|
||||||
public class EchoClientListener extends AbstractClientListener {
|
public class EchoClientListener extends AbstractClientListener {
|
||||||
public EchoClientListener(Socket socket) {
|
public EchoClientListener(Socket socket) {
|
||||||
super(socket);
|
super(socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thread runner
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
while (socket.isConnected()) {
|
while (socket.isConnected()) {
|
||||||
writeLine(readLine());
|
Thread.yield();
|
||||||
|
final String data = readLine();
|
||||||
|
writeLine(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,29 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2022 Vyacheslav Boyko
|
||||||
|
|
||||||
|
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 me.bvn13.sewy;
|
package me.bvn13.sewy;
|
||||||
|
|
||||||
|
import me.bvn13.sewy.command.AbstractCommand;
|
||||||
|
import me.bvn13.sewy.command.PingCommand;
|
||||||
|
import me.bvn13.sewy.command.PongCommand;
|
||||||
import org.junit.jupiter.api.Assertions;
|
import org.junit.jupiter.api.Assertions;
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
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;
|
||||||
|
|
||||||
public class ServerTest {
|
public class ServerTest {
|
||||||
|
|
||||||
private static final int START_PORT = 12345;
|
private static final int START_PORT = 12345;
|
||||||
@ -21,7 +41,7 @@ public class ServerTest {
|
|||||||
@ValueSource(ints = START_PORT + 2)
|
@ValueSource(ints = START_PORT + 2)
|
||||||
void givenServerRunning_whenClientConnects_thenServerCanStopClientListener(int port) throws InterruptedException {
|
void givenServerRunning_whenClientConnects_thenServerCanStopClientListener(int port) throws InterruptedException {
|
||||||
Server server = new Server("localhost", port);
|
Server server = new Server("localhost", port);
|
||||||
Client client = new Client("localhost", port);
|
Client<SimpleClientListener> client = new Client<>("localhost", port, SimpleClientListener.class);
|
||||||
Thread.sleep(1000);
|
Thread.sleep(1000);
|
||||||
Assertions.assertTrue(server.isListening());
|
Assertions.assertTrue(server.isListening());
|
||||||
Assertions.assertTrue(client.isConnected());
|
Assertions.assertTrue(client.isConnected());
|
||||||
@ -39,13 +59,8 @@ public class ServerTest {
|
|||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@ValueSource(ints = START_PORT + 4)
|
@ValueSource(ints = START_PORT + 4)
|
||||||
void serverStartedWithLambdaProvidedClientListener(int port) throws InterruptedException {
|
void serverIsAbleToStartWithLambdaProvidedClientListener(int port) throws InterruptedException {
|
||||||
Server server = new Server("localhost", port, (socket) -> new AbstractClientListener(socket) {
|
Server server = new Server("localhost", port, SimpleClientListener.class);
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Thread.sleep(1000);
|
Thread.sleep(1000);
|
||||||
Assertions.assertTrue(server.isListening());
|
Assertions.assertTrue(server.isListening());
|
||||||
server.stop();
|
server.stop();
|
||||||
@ -54,8 +69,8 @@ public class ServerTest {
|
|||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@ValueSource(ints = START_PORT + 5)
|
@ValueSource(ints = START_PORT + 5)
|
||||||
void simpleEchoClientServer(int port) {
|
void simpleEchoClientServer(int port) {
|
||||||
new Server("localhost", port, EchoClientListener.class);
|
new Server("192.168.0.153", port, EchoClientListener.class);
|
||||||
Client client = new Client("localhost", port);
|
Client<SimpleClientListener> client = new Client<>("192.168.0.153", port, SimpleClientListener.class);
|
||||||
client.writeLine("hello");
|
client.writeLine("hello");
|
||||||
String response1 = client.readLine();
|
String response1 = client.readLine();
|
||||||
Assertions.assertEquals("hello", response1);
|
Assertions.assertEquals("hello", response1);
|
||||||
@ -64,4 +79,37 @@ public class ServerTest {
|
|||||||
Assertions.assertEquals("olleh", response2);
|
Assertions.assertEquals("olleh", response2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(ints = START_PORT + 6)
|
||||||
|
void serverIsAbleToPingPong(int port) throws InterruptedException {
|
||||||
|
Sewy.register(PingCommand.class);
|
||||||
|
Sewy.register(PongCommand.class);
|
||||||
|
|
||||||
|
Server server = new Server("localhost", port, (socket) -> new CommandClientListener(socket) {
|
||||||
|
@Override
|
||||||
|
public AbstractCommand onCommand(AbstractCommand command) {
|
||||||
|
if (command instanceof PingCommand) {
|
||||||
|
return new PongCommand((PingCommand) command);
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException(command.toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
AtomicLong latency = new AtomicLong(0);
|
||||||
|
CommandClient client = new CommandClient("localhost", port, (socket) -> new CommandClientListener(socket) {
|
||||||
|
@Override
|
||||||
|
public AbstractCommand onCommand(AbstractCommand command) {
|
||||||
|
if (command instanceof PongCommand) {
|
||||||
|
latency.set(((PongCommand)command).getLatency());
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(command.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
client.send(new PingCommand());
|
||||||
|
Thread.sleep(1000);
|
||||||
|
Assertions.assertTrue(latency.get() > 0);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
11
src/test/resources/logback.xml
Normal file
11
src/test/resources/logback.xml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<configuration>
|
||||||
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} \(%class{0}.java:%line\) - %msg%n</pattern>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<root level="trace">
|
||||||
|
<appender-ref ref="STDOUT" />
|
||||||
|
</root>
|
||||||
|
</configuration>
|
Loading…
Reference in New Issue
Block a user