working on client side

main
Brett 2023-04-15 15:29:19 -04:00
parent 3b6269958b
commit 6a559bc5ab
5 changed files with 141 additions and 83 deletions

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
</set>
</option>
</component>
</project>

View File

@ -6,7 +6,7 @@ import java.io.*;
public class Main { public class Main {
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
Client gameClient = new Client(42069); Client gameClient = new Client("localhost");
} }
} }

View File

@ -2,21 +2,30 @@ package ca.cosc3p91.a4.util.network;
import ca.cosc3p91.a4.userinterface.GameDisplay; import ca.cosc3p91.a4.userinterface.GameDisplay;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException; import java.io.IOException;
import java.net.DatagramPacket; import java.net.DatagramPacket;
import java.net.DatagramSocket; import java.net.DatagramSocket;
import java.net.InetAddress; import java.net.InetAddress;
public class Client { public class Client implements Runnable {
GameDisplay view = new GameDisplay(); private GameDisplay view = new GameDisplay();
private DatagramSocket clientSocket;
private boolean running = true;
private Thread receiveThread;
public Client(String address) throws IOException {
InetAddress serverAddress = InetAddress.getByName(address);
clientSocket = new DatagramSocket();
receiveThread = new Thread(this);
receiveThread.start();
public Client(int port) throws IOException {
DatagramSocket clientSocket = new DatagramSocket();
InetAddress IPAddress = InetAddress.getByName("localhost");
String prompt; String prompt;
byte[] sendData = new byte[1024]; byte[] sendData = new byte[1024];
byte[] receiveData = new byte[1024]; byte[] receiveData = new byte[1024];
while (true) { while (running) {
if ((prompt = view.nextInput()) != null) { if ((prompt = view.nextInput()) != null) {
if (!prompt.isEmpty() && prompt.charAt(0) == '6') break; if (!prompt.isEmpty() && prompt.charAt(0) == '6') break;
sendData = prompt.getBytes(); sendData = prompt.getBytes();
@ -31,4 +40,25 @@ public class Client {
} }
clientSocket.close(); clientSocket.close();
} }
public void run(){
while (running){
try {
byte[] receiveData = new byte[Server.PACKET_SIZE];
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
clientSocket.receive(receivePacket);
DataInputStream stream = new DataInputStream(new ByteArrayInputStream(receivePacket.getData()));
byte packetID = stream.readByte();
long clientID = stream.readLong();
long messageID = stream.readLong();
} catch (Exception e){
e.printStackTrace();
}
}
}
} }

View File

@ -1,5 +1,8 @@
package ca.cosc3p91.a4.util.network; package ca.cosc3p91.a4.util.network;
import ca.cosc3p91.a4.util.Time;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.DataOutputStream; import java.io.DataOutputStream;
@ -26,32 +29,77 @@ public class Message {
return messageID; return messageID;
} }
public static class ReceivedMessage extends Message { public static class Received extends Message {
private final DataInputStream reader; private final DataInputStream reader;
private final byte[] data;
public ReceivedMessage(byte packetID, long clientID, long messageID, DataInputStream reader) { public Received(byte packetID, long clientID, long messageID, DataInputStream reader, byte[] data) {
super(packetID, clientID, messageID); super(packetID, clientID, messageID);
this.reader = reader; this.reader = reader;
this.data = data;
} }
public DataInputStream getReader(){ public DataInputStream getReader(){
return reader; return reader;
} }
public byte[] getData(){
return data;
}
} }
public static class SentMessage extends Message { public static class Sent extends Message {
private final DataOutputStream writer; private final DataOutputStream writer;
private final ByteArrayOutputStream data;
private boolean ack = false;
private final Time timeSent;
public SentMessage(byte packetID, long clientID, long messageID, DataOutputStream writer) { /**
* A message packet which will be sent to a client or the server, contains the standard message header and
* writes the header to the stream, make sure you don't write into the stream before constructing this!
*
* @param packetID type of this message
* @param clientID the client id, if this is going to the client it is unlikely to be used but should always be correct!
* @param messageID client specific message id, used to reference/acknowledge messages
* @param writer stream to write to
* @param data byte array stream which contains the byte[] used in packet construction
*/
public Sent(byte packetID, long clientID, long messageID, DataOutputStream writer, ByteArrayOutputStream data) {
super(packetID, clientID, messageID); super(packetID, clientID, messageID);
this.writer = writer; this.writer = writer;
this.data = data;
timeSent = Time.getTime();
// write the header to the stream, make sure you don't write into the stream before constructing this!
try {
writer.writeByte(packetID);
writer.writeLong(clientID);
writer.writeLong(messageID);
} catch (Exception e){
e.printStackTrace();
}
}
public void acknowledged(){
this.ack = true;
}
public boolean isAcknowledged(){
return ack;
} }
public DataOutputStream getWriter(){ public DataOutputStream getWriter(){
return writer; return writer;
} }
public Time getTimeSinceSent(){
return Time.getTime().difference(timeSent);
}
public ByteArrayOutputStream getData(){
return data;
}
} }
} }

View File

@ -1,7 +1,6 @@
package ca.cosc3p91.a4.util.network; package ca.cosc3p91.a4.util.network;
import ca.cosc3p91.a4.game.GameEngine; import ca.cosc3p91.a4.game.GameEngine;
import ca.cosc3p91.a4.util.Time;
import java.io.*; import java.io.*;
import java.net.*; import java.net.*;
@ -17,9 +16,9 @@ public class Server implements Runnable {
private final HashMap<Long, ConnectedClient> clients = new HashMap<>(); private final HashMap<Long, ConnectedClient> clients = new HashMap<>();
private long clientAssignmentID = 0; private long clientAssignmentID = 0;
private long lastMessageID = 0;
private final DatagramSocket socket; private final DatagramSocket socket;
private final Thread ioThread; private final Thread ioThread;
private long lastSentMessageID = 0;
private GameEngine mainEngine; private GameEngine mainEngine;
@ -33,21 +32,24 @@ public class Server implements Runnable {
public void run(){ public void run(){
while (running) { while (running) {
try {
byte[] receiveData = new byte[PACKET_SIZE]; byte[] receiveData = new byte[PACKET_SIZE];
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length); DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
try {
// BLOCKING! // BLOCKING!
socket.receive(receivePacket); socket.receive(receivePacket);
// read in the message header that is associated with every message.
DataInputStream stream = new DataInputStream(new ByteArrayInputStream(receivePacket.getData())); DataInputStream stream = new DataInputStream(new ByteArrayInputStream(receivePacket.getData()));
byte packetID = stream.readByte(); byte packetID = stream.readByte();
long clientID = stream.readLong(); long clientID = stream.readLong();
long messageID = stream.readLong();
ConnectedClient client = clients.get(clientID); ConnectedClient client = clients.get(clientID);
// the server must handle connection requests while the client's processing thread will handle all other messages
if (packetID == PacketTable.CONNECT){ if (packetID == PacketTable.CONNECT){
clients.put(++clientAssignmentID, new ConnectedClient(socket, clientID, receivePacket.getAddress(), receivePacket.getPort())); clients.put(++clientAssignmentID, new ConnectedClient(socket, clientID, messageID, receivePacket.getAddress(), receivePacket.getPort()));
} else if (packetID == PacketTable.DISCONNECT) { } else if (packetID == PacketTable.DISCONNECT) {
if (client == null) if (client == null)
throw new ServerException("Client disconnected with invalid client id! (" + clientID + ")"); throw new ServerException("Client disconnected with invalid client id! (" + clientID + ")");
@ -56,7 +58,7 @@ public class Server implements Runnable {
} else { } else {
if (client == null) if (client == null)
throw new ServerException("Client message with invalid client id! (" + clientID + ")"); throw new ServerException("Client message with invalid client id! (" + clientID + ")");
client.handleRequest(new ConnectedClient.ServerRequest(packetID, stream)); client.handleRequest(new Message.Received(packetID, clientID, messageID, stream, receivePacket.getData()));
} }
} catch (IOException | InterruptedException e) { } catch (IOException | InterruptedException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
@ -76,100 +78,88 @@ public class Server implements Runnable {
private static class ConnectedClient implements Runnable { private static class ConnectedClient implements Runnable {
private final InetAddress address; private final InetAddress address;
private final int port; private final int port;
private final ArrayList<ServerRequest> requests = new ArrayList<>(); private final Queue<Message.Received> pendingRequests = new PriorityQueue<>();
private final Queue<ServerRequest> pendingRequests = new PriorityQueue<>();
// could use read/write lock for some of this, as certain operations, mostly timeout check, won't modify data.
private final ReentrantLock requestLock = new ReentrantLock(); private final ReentrantLock requestLock = new ReentrantLock();
private final DatagramSocket socket; private final HashMap<Long, Message.Sent> sentMessages = new HashMap<>();
private final DatagramSocket serverSocket;
private final long clientID; private final long clientID;
private volatile boolean running = true; private volatile boolean running = true;
private final Thread processingThread; private final Thread processingThread;
public ConnectedClient(DatagramSocket socket, long clientID, InetAddress address, int port){ public ConnectedClient(DatagramSocket serverSocket, long clientID, long messageID, InetAddress address, int port){
this.socket = socket; this.serverSocket = serverSocket;
this.address = address; this.address = address;
this.port = port; this.port = port;
this.clientID = clientID; this.clientID = clientID;
processingThread = new Thread(this); processingThread = new Thread(this);
processingThread.start(); processingThread.start();
ByteArrayOutputStream bstream = new ByteArrayOutputStream();
DataOutputStream stream = new DataOutputStream(bstream);
sendMessage(new Message.Sent(PacketTable.ACK, clientID, messageID, stream, bstream));
} }
public void handleRequest(ServerRequest request){ public void handleRequest(Message.Received request){
if (request.getClientID() != this.clientID)
throw new RuntimeException("Server sent us a message, yet we are not the intended recipient!");
requestLock.lock(); requestLock.lock();
pendingRequests.add(request); pendingRequests.add(request);
requestLock.unlock(); requestLock.unlock();
} }
private void processRequest(ServerRequest request){ private void processRequest(Message.Received request){
try { try {
switch (request.getID()){ switch (request.getPacketID()){
case PacketTable.ACK: case PacketTable.ACK:
long messageID = request.getDataStream().readLong(); Message.Sent message = sentMessages.get(request.getMessageID());
if (message == null)
throw new RuntimeException("A message was acknowledged but does not exist!");
message.acknowledged();
break;
case PacketTable.MESSAGE:
System.out.println(request.getReader().readUTF());
break; break;
} }
} catch (IOException e) { } catch (Exception e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
public void run(){ public void run(){
while (running){ while (running){
// handle request processing without blocking the I/O thread
requestLock.lock(); requestLock.lock();
while (pendingRequests.size() > 0) { Message.Received request = pendingRequests.remove();
ServerRequest request = pendingRequests.remove();
processRequest(request); processRequest(request);
requests.add(request);
}
requestLock.unlock(); requestLock.unlock();
requests.removeIf(ServerRequest::isAck); for (Map.Entry<Long, Message.Sent> message : sentMessages.entrySet()){
for (ServerRequest request : requests){ if (message.getValue().getTimeSinceSent().get() > MAX_PACKET_ACK_TIME_SECONDS) {
// TODO: System.out.println("The server did not process our message, did they receive it?");
if (request.getTimeSinceReceived().get() > MAX_PACKET_ACK_TIME_SECONDS) // todo: resend message
System.out.println("A packet hasn't received a ack, it might have been lost!");
} }
} }
} }
}
public void sendMessage(Message.Sent message){
this.sentMessages.put(message.getMessageID(), message);
byte[] data = message.getData().toByteArray();
if (data.length > PACKET_SIZE)
throw new RuntimeException("Unable to send packet as it exceeds maximum packet size!");
DatagramPacket request = new DatagramPacket(data, data.length, address, port);
try {
serverSocket.send(request);
} catch (IOException e) {
e.printStackTrace();
}
}
public void halt() throws InterruptedException { public void halt() throws InterruptedException {
running = false; running = false;
processingThread.join(); processingThread.join();
} }
private static class ServerRequest {
private final byte id;
private final Time receiveTime;
private final DataInputStream receive;
// ack should be on messages send to the client, which the client acks!
private boolean ack = false;
public ServerRequest(byte id, DataInputStream receive){
this.id = id;
this.receive = receive;
receiveTime = Time.getTime();
}
public byte getID(){
return id;
}
public void acknowledged(){
this.ack = true;
}
public boolean isAck(){
return this.ack;
}
public DataInputStream getDataStream(){
return receive;
}
public Time getTimeSinceReceived(){
return Time.getTime().difference(receiveTime);
}
}
} }
} }