2022-07-05 19:49:04 +00:00
|
|
|
package nl.andrewl.aos2_server;
|
|
|
|
|
|
|
|
import nl.andrewl.aos_core.Net;
|
2022-07-07 14:51:29 +00:00
|
|
|
import nl.andrewl.aos_core.model.Chunk;
|
2022-07-06 22:55:26 +00:00
|
|
|
import nl.andrewl.aos_core.net.*;
|
2022-07-07 14:51:29 +00:00
|
|
|
import nl.andrewl.aos_core.net.udp.PlayerUpdateMessage;
|
2022-07-05 19:49:04 +00:00
|
|
|
import nl.andrewl.record_net.Message;
|
|
|
|
import nl.andrewl.record_net.util.ExtendedDataInputStream;
|
|
|
|
import nl.andrewl.record_net.util.ExtendedDataOutputStream;
|
2022-07-07 14:51:29 +00:00
|
|
|
import org.joml.Vector3i;
|
|
|
|
import org.slf4j.Logger;
|
|
|
|
import org.slf4j.LoggerFactory;
|
2022-07-05 19:49:04 +00:00
|
|
|
|
|
|
|
import java.io.IOException;
|
2022-07-06 18:20:15 +00:00
|
|
|
import java.net.DatagramPacket;
|
|
|
|
import java.net.DatagramSocket;
|
|
|
|
import java.net.InetAddress;
|
|
|
|
import java.net.Socket;
|
2022-07-05 19:49:04 +00:00
|
|
|
|
2022-07-06 22:55:26 +00:00
|
|
|
/**
|
|
|
|
* Component which manages the establishing and maintenance of a connection
|
|
|
|
* to a single client. This involves waiting for the client to send their
|
|
|
|
* first {@link ConnectRequestMessage}, so that we can respond with either a
|
|
|
|
* {@link ConnectRejectMessage} or {@link ConnectAcceptMessage}. If the player
|
|
|
|
* is accepted, we proceed to register the player and begin receiving messages
|
|
|
|
* from them.
|
|
|
|
*/
|
2022-07-06 18:20:15 +00:00
|
|
|
public class ClientCommunicationHandler {
|
2022-07-07 14:51:29 +00:00
|
|
|
private static final Logger log = LoggerFactory.getLogger(ClientCommunicationHandler.class);
|
|
|
|
|
2022-07-05 19:49:04 +00:00
|
|
|
private final Server server;
|
|
|
|
private final Socket socket;
|
|
|
|
private final DatagramSocket datagramSocket;
|
|
|
|
private final ExtendedDataInputStream in;
|
|
|
|
private final ExtendedDataOutputStream out;
|
|
|
|
|
|
|
|
private InetAddress clientAddress;
|
2022-07-07 14:51:29 +00:00
|
|
|
private int clientUdpPort = -1;
|
|
|
|
private ServerPlayer player;
|
2022-07-05 19:49:04 +00:00
|
|
|
|
2022-07-06 18:20:15 +00:00
|
|
|
public ClientCommunicationHandler(Server server, Socket socket, DatagramSocket datagramSocket) throws IOException {
|
2022-07-05 19:49:04 +00:00
|
|
|
this.server = server;
|
|
|
|
this.socket = socket;
|
|
|
|
this.datagramSocket = datagramSocket;
|
|
|
|
this.in = Net.getInputStream(socket.getInputStream());
|
|
|
|
this.out = Net.getOutputStream(socket.getOutputStream());
|
|
|
|
}
|
|
|
|
|
|
|
|
public void shutdown() {
|
2022-07-06 18:20:15 +00:00
|
|
|
try {
|
2022-07-06 22:55:26 +00:00
|
|
|
if (!socket.isClosed()) socket.close();
|
2022-07-06 18:20:15 +00:00
|
|
|
} catch (IOException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
2022-07-05 19:49:04 +00:00
|
|
|
}
|
|
|
|
|
2022-07-06 22:55:26 +00:00
|
|
|
/**
|
|
|
|
* Used to set UDP port once we know it, since the client first sends their
|
|
|
|
* connection request, then we accept, and <em>then</em> the client begins
|
|
|
|
* the UDP communication.
|
|
|
|
* @param port The client's port.
|
|
|
|
*/
|
2022-07-06 18:20:15 +00:00
|
|
|
public void setClientUdpPort(int port) {
|
|
|
|
this.clientUdpPort = port;
|
2022-07-05 19:49:04 +00:00
|
|
|
}
|
|
|
|
|
2022-07-06 18:20:15 +00:00
|
|
|
private void handleTcpMessage(Message msg) {
|
2022-07-07 14:51:29 +00:00
|
|
|
log.debug("Received TCP message from client \"{}\": {}", player.getUsername(), msg.toString());
|
|
|
|
if (msg instanceof ChunkHashMessage hashMessage) {
|
|
|
|
Chunk chunk = server.getWorld().getChunkAt(new Vector3i(hashMessage.cx(), hashMessage.cy(), hashMessage.cz()));
|
|
|
|
if (chunk != null && hashMessage.hash() != chunk.blockHash()) {
|
|
|
|
sendTcpMessage(new ChunkDataMessage(chunk));
|
|
|
|
}
|
|
|
|
}
|
2022-07-06 18:20:15 +00:00
|
|
|
}
|
|
|
|
|
2022-07-06 22:55:26 +00:00
|
|
|
public void establishConnection() throws IOException {
|
2022-07-06 18:20:15 +00:00
|
|
|
socket.setSoTimeout(1000);
|
2022-07-05 19:49:04 +00:00
|
|
|
boolean connectionEstablished = false;
|
|
|
|
int attempts = 0;
|
2022-07-10 10:49:51 +00:00
|
|
|
while (!connectionEstablished && attempts < 10) {
|
2022-07-05 19:49:04 +00:00
|
|
|
try {
|
|
|
|
Message msg = Net.read(in);
|
2022-07-05 21:34:54 +00:00
|
|
|
if (msg instanceof ConnectRequestMessage connectMsg) {
|
2022-07-06 18:20:15 +00:00
|
|
|
// Try to set the TCP timeout back to 0 now that we've got the correct request.
|
|
|
|
socket.setSoTimeout(0);
|
2022-07-05 19:49:04 +00:00
|
|
|
this.clientAddress = socket.getInetAddress();
|
|
|
|
connectionEstablished = true;
|
2022-07-07 14:51:29 +00:00
|
|
|
this.player = server.getPlayerManager().register(this, connectMsg.username());
|
2022-07-06 18:20:15 +00:00
|
|
|
Net.write(new ConnectAcceptMessage(player.getId()), out);
|
2022-07-07 14:51:29 +00:00
|
|
|
log.debug("Sent connect accept message.");
|
2022-07-06 22:55:26 +00:00
|
|
|
|
2022-07-09 18:09:57 +00:00
|
|
|
sendTcpMessage(new WorldInfoMessage(server.getWorld()));
|
2022-07-17 14:24:41 +00:00
|
|
|
// Send join info for all players that are already connected.
|
|
|
|
for (var player : server.getPlayerManager().getPlayers()) {
|
|
|
|
if (player.getId() != this.player.getId()) {
|
|
|
|
sendTcpMessage(new PlayerJoinMessage(player));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Send chunk data.
|
2022-07-06 22:55:26 +00:00
|
|
|
for (var chunk : server.getWorld().getChunkMap().values()) {
|
|
|
|
sendTcpMessage(new ChunkDataMessage(chunk));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initiate a TCP receiver thread to accept incoming messages from the client.
|
|
|
|
TcpReceiver tcpReceiver = new TcpReceiver(in, this::handleTcpMessage)
|
2022-07-07 14:51:29 +00:00
|
|
|
.withShutdownHook(() -> server.getPlayerManager().deregister(this.player));
|
2022-07-06 22:55:26 +00:00
|
|
|
new Thread(tcpReceiver).start();
|
2022-07-05 19:49:04 +00:00
|
|
|
}
|
|
|
|
} catch (IOException e) {
|
2022-07-16 08:47:06 +00:00
|
|
|
log.warn("An IOException occurred while attempting to establish a connection to a client.", e);
|
2022-07-05 19:49:04 +00:00
|
|
|
}
|
|
|
|
attempts++;
|
|
|
|
}
|
|
|
|
if (!connectionEstablished) {
|
|
|
|
try {
|
2022-07-05 21:34:54 +00:00
|
|
|
Net.write(new ConnectRejectMessage("Too many connect attempts failed."), out);
|
2022-07-05 19:49:04 +00:00
|
|
|
} catch (IOException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
2022-07-07 14:51:29 +00:00
|
|
|
log.warn("Player couldn't connect after {} attempts. Aborting connection.", attempts);
|
2022-07-06 18:20:15 +00:00
|
|
|
socket.close();
|
2022-07-05 19:49:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-06 22:55:26 +00:00
|
|
|
public void sendTcpMessage(Message msg) {
|
|
|
|
try {
|
|
|
|
Net.write(msg, out);
|
|
|
|
} catch (IOException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-06 18:20:15 +00:00
|
|
|
public void sendDatagramPacket(Message msg) {
|
2022-07-05 19:49:04 +00:00
|
|
|
try {
|
|
|
|
sendDatagramPacket(Net.write(msg));
|
|
|
|
} catch (IOException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-07-06 18:20:15 +00:00
|
|
|
public void sendDatagramPacket(byte[] data) {
|
2022-07-05 19:49:04 +00:00
|
|
|
DatagramPacket packet = new DatagramPacket(data, data.length, clientAddress, clientUdpPort);
|
|
|
|
sendDatagramPacket(packet);
|
|
|
|
}
|
|
|
|
|
2022-07-06 18:20:15 +00:00
|
|
|
public void sendDatagramPacket(DatagramPacket packet) {
|
2022-07-05 19:49:04 +00:00
|
|
|
try {
|
2022-07-07 14:51:29 +00:00
|
|
|
if (clientUdpPort != -1) {
|
|
|
|
packet.setAddress(clientAddress);
|
|
|
|
packet.setPort(clientUdpPort);
|
|
|
|
datagramSocket.send(packet);
|
|
|
|
}
|
2022-07-05 19:49:04 +00:00
|
|
|
} catch (IOException e) {
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|