Added JavaGrinko's TcpController Library

- Started implementing it into the ArkOneController class
- Needs testing to verify it can communicate with the game
This commit is contained in:
2022-07-07 14:02:42 -05:00
committed by Julia Butenhoff
parent 1479835340
commit 08acb3ddc0
12 changed files with 435 additions and 2 deletions

View File

@@ -0,0 +1,35 @@
package com.icedberries.UBFunkeysServer.ArkOne;
import javagrinko.spring.tcp.Connection;
import javagrinko.spring.tcp.TcpController;
import javagrinko.spring.tcp.TcpHandler;
import java.io.IOException;
@TcpController
public class ArkOneController implements TcpHandler {
@Override
public void receiveData(Connection connection, byte[] data) {
//TODO: IMPLEMENT PLUGIN CHECKING AND FORWARDING HERE
//Currently just echos back the received data until implemented
String s = new String(data);
try {
connection.send(s.toUpperCase().getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void connectEvent(Connection connection) {
// Just log connections
System.out.println("[ArkOne][EVENT] Client connection from: " + connection.getAddress().getCanonicalHostName());
}
@Override
public void disconnectEvent(Connection connection) {
// Just log disconnections
System.out.println("[ArkOne][EVENT] Client disconnected: " + connection.getAddress().getCanonicalHostName());
}
}

View File

@@ -4,8 +4,7 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan(basePackages = "com.icedberries")
@SpringBootApplication(scanBasePackages = {"com.icedberries", "javagrinko.spring"})
public class UBFunkeysServerApplication {
public static void main(String[] args) {

View File

@@ -0,0 +1,31 @@
package javagrinko.spring.starter;
import javagrinko.spring.tcp.Server;
import javagrinko.spring.tcp.TcpControllerBeanPostProcessor;
import javagrinko.spring.tcp.TcpServer;
import javagrinko.spring.tcp.TcpServerAutoStarterApplicationListener;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(TcpServerProperties.class)
@ConditionalOnProperty(prefix = "javagrinko.tcp-server", name = {"port", "auto-start"})
public class TcpServerAutoConfiguration {
@Bean
TcpServerAutoStarterApplicationListener tcpServerAutoStarterApplicationListener() {
return new TcpServerAutoStarterApplicationListener();
}
@Bean
TcpControllerBeanPostProcessor tcpControllerBeanPostProcessor() {
return new TcpControllerBeanPostProcessor();
}
@Bean
Server server(){
return new TcpServer();
}
}

View File

@@ -0,0 +1,32 @@
package javagrinko.spring.starter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "tcp.server")
public class TcpServerProperties {
@Value("${tcp.server.port}")
private Integer port = 1234;
@Value("${tcp.server.auto-start}")
private Boolean autoStart = true;
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public boolean getAutoStart() {
return autoStart;
}
public void setAutoStart(boolean autoStart) {
this.autoStart = autoStart;
}
}

View File

@@ -0,0 +1,23 @@
package javagrinko.spring.tcp;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.InetAddress;
public interface Connection {
InetAddress getAddress();
void send(byte[] bytes) throws IOException;
void addListener(Listener listener);
void start();
void close() throws IOException;
interface Listener {
void messageReceived(Connection connection, byte[] bytes)
throws InvocationTargetException, IllegalAccessException;
void connected(Connection connection)
throws InvocationTargetException, IllegalAccessException;
void disconnected(Connection connection)
throws InvocationTargetException, IllegalAccessException;
}
}

View File

@@ -0,0 +1,13 @@
package javagrinko.spring.tcp;
import java.util.List;
public interface Server {
int getConnectionsCount();
void setPort(Integer port);
void start();
void stop();
List<Connection> getConnections();
void addListener(Connection.Listener listener);
}

View File

@@ -0,0 +1,96 @@
package javagrinko.spring.tcp;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class TcpConnection implements Connection {
private static Log logger = LogFactory.getLog(TcpConnection.class);
private InputStream inputStream;
private OutputStream outputStream;
private Socket socket;
private List<Listener> listeners = new CopyOnWriteArrayList<>();
TcpConnection(Socket socket) {
this.socket = socket;
try {
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public InetAddress getAddress() {
return socket.getInetAddress();
}
@Override
public void send(byte[] bytes) throws IOException {
outputStream.write(bytes);
logger.trace("Sent message");
if (logger.isTraceEnabled()) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02X ", b));
}
logger.trace(" ==> " + sb.toString());
}
}
@Override
public void addListener(Listener listener) {
listeners.add(listener);
}
@Override
public void start() {
new Thread(() -> {
while (true) {
byte buf[] = new byte[64 * 1024];
try {
int count = inputStream.read(buf);
if (count > 0) {
byte[] bytes = Arrays.copyOf(buf, count);
for (Listener listener : listeners) {
listener.messageReceived(this, bytes);
}
} else {
socket.close();
disconnectAll();
break;
}
} catch (IOException | IllegalAccessException | InvocationTargetException e) {
logger.error(e.getMessage(), e);
disconnectAll();
break;
}
}
}).start();
}
private void disconnectAll() {
for (Listener listener : listeners) {
try {
listener.disconnected(this);
} catch (InvocationTargetException | IllegalAccessException e) {
logger.error(e.getMessage(), e);
}
}
}
@Override
public void close() throws IOException {
socket.close();
}
}

View File

@@ -0,0 +1,11 @@
package javagrinko.spring.tcp;
import org.springframework.stereotype.Component;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Component
@Retention(RetentionPolicy.RUNTIME)
public @interface TcpController {
}

View File

@@ -0,0 +1,52 @@
package javagrinko.spring.tcp;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
@Component
public class TcpControllerBeanPostProcessor implements BeanPostProcessor {
private Map<String, Class> cache = new HashMap<>();
@Autowired
private Server server;
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
Class<?> beanClass = bean.getClass();
if (bean instanceof TcpHandler) {
cache.put(beanName, beanClass);
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (cache.containsKey(beanName)) {
TcpHandler tcpHandler = (TcpHandler) bean;
server.addListener(new Connection.Listener() {
@Override
public void messageReceived(Connection connection, byte[] bytes)
throws InvocationTargetException, IllegalAccessException {
tcpHandler.receiveData(connection, bytes);
}
@Override
public void connected(Connection connection) throws InvocationTargetException, IllegalAccessException {
tcpHandler.connectEvent(connection);
}
@Override
public void disconnected(Connection connection) throws InvocationTargetException, IllegalAccessException {
tcpHandler.disconnectEvent(connection);
}
});
}
return bean;
}
}

View File

@@ -0,0 +1,7 @@
package javagrinko.spring.tcp;
public interface TcpHandler {
void receiveData(Connection connection, byte[] data);
void connectEvent(Connection connection);
void disconnectEvent(Connection connection);
}

View File

@@ -0,0 +1,108 @@
package javagrinko.spring.tcp;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
@Component
public class TcpServer implements Server, Connection.Listener {
private static Log logger = LogFactory.getLog(TcpServer.class);
private ServerSocket serverSocket;
private volatile boolean isStop;
private List<Connection> connections = new CopyOnWriteArrayList<>();
private List<Connection.Listener> listeners = new CopyOnWriteArrayList<>();
public void setPort(Integer port) {
try {
serverSocket = new ServerSocket(port);
logger.info("Server start at port " + port);
} catch (IOException e) {
logger.error("Port " + port + " busy.", e);
}
}
@Override
public int getConnectionsCount() {
return connections.size();
}
@Override
public void start() {
new Thread(() -> {
while (!isStop) {
try {
Socket socket = serverSocket.accept();
if (socket.isConnected()) {
TcpConnection tcpConnection = new TcpConnection(socket);
tcpConnection.start();
tcpConnection.addListener(this);
connected(tcpConnection);
}
} catch (IOException | IllegalAccessException | InvocationTargetException e) {
logger.error(e.getMessage(), e);
}
}
}).start();
}
@Override
public void stop() {
isStop = true;
}
@Override
public List<Connection> getConnections() {
return connections;
}
@Override
public void addListener(Connection.Listener listener) {
listeners.add(listener);
}
@Override
public void messageReceived(Connection connection, byte[] bytes)
throws InvocationTargetException, IllegalAccessException {
if (logger.isTraceEnabled()) {
logger.trace("Received message from " + connection.getAddress().getCanonicalHostName());
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02X ", b));
}
logger.trace(" <== " + sb.toString());
}
for (Connection.Listener listener : listeners) {
listener.messageReceived(connection, bytes);
}
}
@Override
public void connected(Connection connection)
throws InvocationTargetException, IllegalAccessException {
logger.info("New connection! Ip: " + connection.getAddress().getCanonicalHostName() + ".");
connections.add(connection);
logger.info("Current connections count: " + connections.size());
for (Connection.Listener listener : listeners) {
listener.connected(connection);
}
}
@Override
public void disconnected(Connection connection)
throws InvocationTargetException, IllegalAccessException {
logger.info("Disconnect! Ip: " + connection.getAddress().getCanonicalHostName() + ".");
connections.remove(connection);
logger.info("Current connections count: " + connections.size());
for (Connection.Listener listener : listeners) {
listener.disconnected(connection);
}
}
}

View File

@@ -0,0 +1,26 @@
package javagrinko.spring.tcp;
import javagrinko.spring.starter.TcpServerProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
@Component
public class TcpServerAutoStarterApplicationListener implements ApplicationListener<ContextRefreshedEvent> {
@Autowired
private TcpServerProperties properties;
@Autowired
private Server server;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
boolean autoStart = properties.getAutoStart();
if (autoStart){
server.setPort(properties.getPort());
server.start();
}
}
}